Why would I want to use Mercurial or any other DVCS client with a Subversion repository?

  • It lets us keep SVN as our central repository
  • Some team members prefer not to use a DVCS for whatever reason so it lets them carry on using SVN without interruption.
  • It allows me to work and commit changes (but not push!), search history and switch between branches completely disconnected. I can continue to work during network outages or while traveling when I don’t have connectivity.
  • You get full, fast history search.
  • Switching between branches is easy and fast.
  • Any automated processes which use SVN (i.e. automated builds and deployments) can continue to operate while everyone moves to DVCS.
  • It’s much easier to perform merges than regular SVN (via export/import patch queues – which I detail later)

Why not use git-svn?

I personally prefer Mercurial with hgsubversion since, in my mind, the tooling in Windows is currently much more mature and I already have a hgsubversion workflow which is simple, robust and effective.

That being said, many people are happy with using git-svn and if you’re evaluating options, it may be worth giving it a go too!

General Overview

My local Mercurial repo contains the full history of the project with a full graph of branches – I use it to search full history, switch between branches, export/import patches, commit and push. I keep everything in a single C:\Source\my-project folder. no need for \trunk or \branches. There are many ways which you can use Mercurial with a SVN repository, each has it’s own caveats and edge-cases. This article is intended to record a way which I have found to be robust and hassle free.

I use TortoiseHg which you can get from http://tortoisehg.bitbucket.org/

TortoiseHg includes the command line client, so you don’t need to install it separately.

This guide assumes knowledge of Mercurial and SVN concepts and workflows and is intended to be a kind of a summary of things to keep in mind when using Mercurial to work with a SVN repository.

To interact with the SVN repository, I only need to use TortoiseHG’s hg workbench http://tortoisehg.bitbucket.org/manual/2.3/workbench.html . Generally I’ll keep the window open at all times on my second monitor, minimizing it when not needed.

More reading: http://tortoisehg.bitbucket.org/manual/2.3/workbench.html

Getting hgsubversion

You can get it from https://bitbucket.org/durin42/hgsubversion/overview/

This guide assumes that you will clone it to: C:\Apps\Mercurial\hgsubversion

I’m using hgsubversion at revision #821 (f28e0f54a6ef) so if in doubt and latest doesn’t seem to be working for you, try updating to this specific version.

More reading: http://mercurial.selenic.com/wiki/HgSubversion

Configuration

My mercurial.ini file in C:\Users\Matt looks like:

[extensions]
hgsubversion = C:\Apps\Mercurial\hgsubversion\hgsubversion
mq =
rebase =
hgext.bookmarks =
hgext.graphlog =
mercurial_keyring=

[ui]
username = mattbutton

[tortoisehg]
ui.language = en

[diff]
git = True

my hgrc file in C:\Source\my-project.hg looks like:

[paths]
default = svn+https://path-to-my-project-repository

[tortoisehg]
postpull = update
autoresolve = False
closeci = True

[ui]
username = mattbutton

my .hgignore in C:\Source\my-project for ASP.NET MVC development in Visual Studio on Windows looks like:

syntax: glob

obj
[Bb]in
_Resharper.*
*.csproj.user
*.resharper.user
*.resharper
*.suo
*.cache
*~
*.swp
*.db
build
GlobalAssemblyInfo.cs
*.sqlite
#ignore thumbnails created by windows
Thumbs.db
#Ignore files build by Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
*.dbmdl
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
bin/[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
*.ReSharper

Cloning the Repository

It’s best to clone the entire repository – not just trunk. This way you can take advantage of switching between branches and full history search.

If you’re cloning a large repository with thousands of changesets, you can expect the initial clone to take a few hours.

I recommend that you zip up the repository after the initial clone and keep it as a backup in case something happens to the working repository. This way, you can extract it somewhere and pull without having to go through the time consuming initial clone of the SVN repository.

Import/Export Patch

It’s important that git patches are enabled. Add the following to your mercurial.ini

[diff]
git = True

Without this setting, if you add a new file, commit, then create a patch based on the commit, you’ll discover that the new file is not included in the patch. Enabling git diffs will avoid this problem altogether.

More reading: http://mercurial.selenic.com/wiki/GitExtendedDiffFormat

Branching and Merging

Do not ever use Mercurial for merging when dealing with a SVN repository. SVN only accepts a linear history, thus HG SVN cannot push merge changesets to a SVN repository and you’ll only end up with errors if you attempt this. There are two ways that you can get the same result without a merge.

If you’re working off of the trunk and you want to push your new changes, use the rebase function and deal with any merge conflicts. This will take all of your changesets which you haven’t yet pushed, and append them to the SVN head. You’ll then be able to push a linear history.

** TODO: add note about rebase onto SVN head

The general command line workflow for this is:

hg pull
hg rebase --svn
hg push

If you want to merge changes from one branch to trunk or vice-versa, the export/import patch functionality.

More reading: http://blog.kalleberg.org/post/2337246985/merging-a-mercurial-repository-back-into-subversion

Removing Unversioned Files

When switching between branches, you may end up with files which don’t belong in the revision which you’ve switched to. This may cause problems in your build process. To remove any unversioned files, you can use the ‘purge’ extension:

hg purge --all

When Things Go Wrong and You Can’t Push

Sometimes you’ll have issues pushing to the SVN repository. Perhaps an error like ”

If your changeset can’t be pushed and you’re getting an odd error which isn’t the usual change conflict, a workflow to fix this is:

  1. Pull the latest revisions
  2. Export the changesets which aren’t pushing as a series of patches
  3. Strip the changesets which aren’t pushing
  4. Import the patches onto the head
  5. Finalize the MQ
  6. Push.

Strip will remove the changeset and all it’s descendants.

NB: Strip rewrites history so you should only use it on changesets which haven’t been pushed. You should never attempt to strip a changeset which has been pushed to SVN.

There are other methods such as hg collapse, however I see these as being quite risky and error prone since you’re making destructive. The export/import patch method has been reliable and problem-free for me.

More reading:

http://mercurial.selenic.com/wiki/Strip

http://mercurial.selenic.com/wiki/CollapseExtension