CVS2Mercurial — Or how to escape from servitude
For a long time now, we have been suffering from the limitations and errors of CVS. For almost as long we have been dreaming of a fast, stable and simple version control system (VCS) to replace it.
The disadvantages of CVS are well known: the codebase is ancient, its concepts are outdated and day-to-day work with the system is error-prone. Today we're finally daring to part from CVS and looking for a modern versioning solution. The first question is: do we really need all the functionality of a distributed VCS? We think we do, but to keep this article on topic we'll not go deeper into our reasons for chosing a distributed over a centralised VCS. Let's just assume that we're making an educated decision here.
While we were struggling with CVS, we have been observing the developments in software-world. SVN redeemed CVS as the top-dog. Linux parted from Bitkeeper. Linus Torvalds wrote Git. At the same time Mercurial was born..
Finally the steady mention of Mercurial on the PHP Conference in October 2009 in Karlsruhe piqued our curiosity.
So what did we look at?
… has redeemed CVS as top-dog. Countless projects use SVN. But as time goes by the euphoria decreases. The main disadvantage is that it has a centralist philosophy like CVS.
Also we dislike the concept of branches in SVN. A source-copy for every branch within the repository? Excuse us, but no.
… is a mighty tool. Even huge repositories, like the Linux kernel, use it without problems. The distributed philosophy allows to work quickly and flexibly. Hooray! But git is based on a large amount of separated tools and hence has a bad usage experience.
… is the solution (for us, at least)! Mercurial is a distributed VCS which has a special focus on speed and usability, which fits our needs perfectly. Brilliant!
The journey is the reward
So, now that we have our candidate, one question remains: How to escape from CVS?
A way of failure...
We already tested the native convert extension  of Mercurial during the PHP Conference. But every branch we had ever started in CVS was an open branch in Mercurial. At that time we didn't know that CVS doesn't provide sufficient information for closing branches. Also, every branch was incomplete after theconversion. It only contained files that were modified during the lifetime of the branch.
Because of that we decided to take the way via SVN as described in . The detour via „hgimportsvn <path_to_svn_repository> && hgpullsvn“ turned out to be very time expensive and simply wrong. The whole repository was organised in SVN-structure now. Every branch was a copy of the source tree within the repository. This is a structure we definitely do not want.
A glimpse of light within the darkness...
All we could do was to start again and try to understand the convert-extension better. After a new turn of research, we found out that the right way is indeed the detour via SVN but with „hg convert“ („If ConvertExtension fails to import your repository, try using cvs2svn to convert first to Subversion, and then import from Subversion.“ ):
cvs2svn --retain-conflicting-attic-files --encoding=utf8 --fallback-encoding=iso8859-1 -s [TARGET_DIR] [PATH_TO_CVS_REPO]
hg convert --datesort [PATH_TO_SVN_REPO] [DESTINATION_DIR]
Note that you need the actual CVS repository, not just the checkout.
After the conversion, branches are complete, but all are open. Even the ones that were finished long ago in CVS. Mercurial just couldn't close them automatically because of the lack of information in CVS. Here we needed to do some manual fixes: It is possible to close a branch manually by using the command „hg up -C <branch_name> && hg commit –close-branch“. So we could decide by ourselves whether to close a branch or not.
Migrating from an ancient VCS to a modern one shows vast differences in the concepts and requires a lot of know-how and trial-and-error work. Most of the pitfalls can be avoided by using the convert extension of mercurial. Only in some parts – especially branches – you have to do some work by yourself.