CVS to SVN

FIXME: This page is really a case study in converting one project. Although this is very useful information it doesn't read as a howto to help others do the conversion. It should be preserved as a case study but this page should be written with the mindset of documenting a recommended procedure for a user to follow to convert their project.

Additionally the --fs-type=fsfs has now long been the default and can be removed from the description. The bdb backend has long been deprecated in favor of the fsfs.

Tools

There are a couple tools to convert CVS to SVN:

cvs2svn

Here are an experiment I did with cvs2svn. We probably need to use the latest (not Debian sarge's) version.

The first step is to mirror the CVS repository on your local host using the Savannah rsync access - you'll probably need to convert the repository a couple times before getting exactly what you want:

rsync -av rsync://cvs.sv.gnu.org/sources/yourproject/ yourproject-cvs/

or if you want untampered transmission through SSH:

rsync -av YOU@cvs.sv.gnu.org:/sources/yourproject/ yourproject-cvs/

Note that if you work on Unix only, you're likely to only work with "text" files: CVS does _not_ set files as binary (cvs admin -kb) automatically (not by analyzing content, nor by extension, unless extensions are mentioned in CVSROOT/cvswrappers or ./.cvswrappers, and that's only at import/add time, this has to be manually fixed later. For example, I had to run either of those to fix the Savane PNGs:

# Fix a remote repository:
cvs co -d... myworkingcopy
cd myworkingcopy
find -name "*.png" -o -name "*.xcf" | xargs cvs admin -kb

# Fix a rsync'd repository:
find -name "*.png,v" -o -name "*.xcf,v" | xargs rcs -kb

It took me 30 minutes to convert the (26M tar.gz) Savane repository, while people at Gna! mention several hours. It's probably faster to convert repositories in your personal computer, but this is yet to prove.

Default conversion creates a layout like:

\
|-trunk
  |-CVSROOT
  |-modulename1
  |-modulename2
  `...
|-branches
`-tags

Here is the command:

cvs2svn --use-cvs --fs-type=fsfs --username=YOUR_SAVANNAH_ACCOUNT --cvs-revnums -s SVN_REPOSITORY CVS_REPOSITORY \
 --mime-types=/etc/mime.types > out.txt 2>err.txt

--use-cvs: use CVS instead of RCS, which is necessary in some rare cases; since we want a generic command line, let's use it, even if slower

--mime-types: adds a better content-type than application/octet-stream; not necessary strictly speaking

--fs-type=fsfs: the BDB (Berkley DB) format is apparently not a good one; FSFS is a bit more transparent and has some improvements. Savane uses it.

It is possible to get a different layout using a several-step import:

\
|-CVSROOT
  |-trunk
  |-branches
  `tags
|-modulename1
  |-trunk
  |-branches
  `tags
|-modulename2
  |-trunk
  |-branches
  `tags
`...

Importing in several steps may cause searches by date to provide bogus results, because SVN assumes that if revno1 < revno2, date_revno1 < date_revno2, which typically isn't necessary the case when importing different modules one after the other (with ever incrementing revnos). Here're the commands (not tested - uses svn2svn --dumpfile and svnadmin --parent-dir ... load):

svn init ...
for modname in cvs-repo/*; do
 cvs2svn --use-cvs --cvs-revnums --fs-type=fsfs --username=YOUR_SAVANNAH_ACCOUNT \
  --mime-types=/etc/mime.types --dumpfile=$modname.dump CVS_REPOSITORY/$modname
 svnadmin --parent-dir $modname load /path/to/svnrepo < $modname.dump
done

Of course, in both cases we may want to filter CVSROOT out, but user still can hide it thereafter.

Yet another last layout is:

modulename1 # trunk
modulename1-branches
modulename1-tags
modulename2
modulename2-branches
modulename2-tags

You may or may not like this. The best way for Savannah would be to provide an automated way to complete the whole conversion process (beware of DoS though).

You can reorganize the repository layout after the conversion, using svn mv. However, this makes it more difficult to search through the project's history later on, because the path will have changed and SVN doesn't follow renames automatically.

Documentation on http://cvs2svn.tigris.org/cvs2svn.html. Don't miss --tmpdir and --dumpfile. Also check the FAQ: http://cvs2svn.tigris.org/faq.html

https://gna.org/task/index.php?func=detailitem&item_id=2229 details issues during the conversion of the Savane project repository itself.

Commands that I ran to convert Savane. Note: eventually the other Savane maintainer kept his own, less exhaustive, conversion that he did a few days earlier. As such I have no idea whether this multi-modules import would have caused history lookup errors as mentioned above:

cvs2svn --use-cvs --fs-type=fsfs --username=beuc --cvs-revnums --mime-types=/etc/mime.types \
 -s savane-svn savane-cvs/savane > out.txt 2>err.txt

cvs2svn --use-cvs --fs-type=fsfs --username=beuc --cvs-revnums --mime-types=/etc/mime.types \
 --dumpfile CVSROOT.dump --dump-only savane-cvs/CVSROOT
svn mkdir file://`pwd`/savane-svn/CVSROOT -m "Added CVSROOT directory."
svnadmin --parent-dir CVSROOT load savane-svn < CVSROOT.dump

cvs2svn --use-cvs --fs-type=fsfs --username=beuc --cvs-revnums --mime-types=/etc/mime.types \
 --dumpfile moved_out.dump --dump-only savane-cvs/moved_out
svn mkdir file://`pwd`/savane-svn/moved_out -m "Added moved_out directory."
svnadmin --parent-dir moved_out load savane-svn < moved_out.dump

cvs2svn --use-cvs --fs-type=fsfs --username=beuc --cvs-revnums --mime-types=/etc/mime.types \
 --dumpfile devel.dump --dump-only savane-cvs/devel
svn mkdir file://`pwd`/savane-svn/devel -m "Added devel directory."
svnadmin --parent-dir devel load savane-svn < devel.dump

Note: Riccardo Mottola reported that he needed to use --fallback-encoding=utf-8 in order for the tool to complete due to the character contents of one file. Example:

cvs2svn --use-cvs --username=rmottola --cvs-revnums --fallback-encoding=utf-8 -s gap-svn gap

Importing at Savannah

Now you need to send a SVN dump at Savannah. See: SvnImportExistingRepo