What I Learnt Migrating to Git from Mercurial (updated June 2020)
I’ve used Mercurial for seven years now and I am reasonably comfortable with it. Most of the time I’m using it at a fairly simple level and I’ve rarely had a major issue with it though I did once have to help a novice user who had become incredibly tangled. My only real issue with it is that I haven’t found a way to squash commits that I am comfortable using. Other than that I find it straightforward to use, it has a choice of nice GUI’s to use and it’s easy to explain to new or casual users.
I use named branches which these days are created using HGFlow which uses the well documented generalized Driessen’s branching model. When work is complete they get closed and merged back. As I have a number of machines I sometimes work on one branch using one machine and a different branch on another machine. In effect, this is the same as multiple developers and as long as I remember to push to my central repos all is well.
I’ve noticed recently, particularly with some of the tools that I use, that Git is becoming increasingly embedded and it’s becoming harder to get the same tooling for Mercurial. I’m not particularly talking about integration into IDE’s, I don’t really mind dropping out of an IDE to perform some version control functions as it makes me pause for thought for a second. I’m thinking more of the automated tools such as GitVersion and GitReleaseNotes although I do also have to throw GitLens into the mix. These don’t really have a mercurial equivalent — or not one I have found that I can use. Even BitBucket seems much more Git focused than it ever did before. As a result, with some reluctance I have decided to start migrating to Git. My favoured Git Gui is Fork which I have been using for a while e.g. when I needed to clone something from Github, though I do also use SourceTree.
So the first question I had to ask myself was, do I need to keep the history. In some cases the answer was no, for a handful of .NET Core/.NET Standard libraries, it was easier to just copy the code over into a new Git Repo. For the remainder, I have used HG-Git in the past with limited success so I decided to take each repo in turn.
I structure my repos so that the Git ones are all in the same subfolder and similarly for the Mercurial ones and they are kept separate. This was to my advantage as it meant I could use HG-git to simply push to a git repo. As I have a central store for all my repos I first pushed to a bare git repo there so that I could clone to my machine. For those who don’t know what a bare repo is, it’s one that isn’t a working repo, it’s just used to push and pull from. From the perspective of a Mercurial user this is a rather quaint and arguably pointless concept.
So to push I need to install HG-Git somewhere, in my case I just cloned the repo and use that, so that it’s trivial to pull down changes. For each repo I want to convert I add the following to .hgrc
hggit = [path-to]/hg-git/hggit
Now set the remote to the bare git repo and push. It’s that simple. HOWEVER, as with all these things there are a few gotcha’s. Firstly everything seems to get pushed to a branch called HG. This surprised me as I was expecting all the HGFlow branches to still exist. In fact when I tried to run GitFlow, it said there wasn’t a master branch and I had to create one before it would work.
Also, in my case the hgignore file wasn’t converted. This was another surprise however I just renamed it to gitignore and it seems to work as expected. So now I have all my history in unnamed branches (Git doesn’t have named branches) so at least it’s there if I need it but to find anything it might be simpler to look it up in Mercurial so I can track it down in Git.
Git has some concepts with regards to branching that seem alien to me, though I am getting more used to them. The biggest issue is the fact it wants to only pull/push a single branch. I’m quite happy to pull them all, particularly as the remote ones are now well pruned and tidied. I’m not sure I fully understand the concept of tracking branches either, but that may come in time.
The next issue, and I guess this is just temporary while I am converting, is figuring out how to easily tell which repo is Mercurial and which is Git from UpSource, SourceTree and BitBucket. This isn’t a showstopper but it has been a touch frustrating at times.
The final issue is getting all the git repos onto all of my machines. There isn’t any sort of mass clone that I am aware of. I tend to just do this on an ad-hoc basis which is what I was doing anyway with the mercurial ones.
I must admit I have been reasonably surprised how smoothly it’s gone so far. Converting UpSource and TeamCity has been relatively straightforward. Team Explorer in VS now makes more sense, GitLens is doing very nice things in VSCode. I once managed to avoid the dreaded Detached Head (which I still don’t really understand, in particular what I am expected to do to resolve it). Other than that, so far, I am pleasantly surprised, though I do find the obsession with branch tidying a little tiresome.
It’s now some weeks on since I wrote this and a few things have become slightly more refined so I thought I would give an update. The first thing that happened was that I updated TortoiseHG and during that process I realised that I didn’t need to do all that I mentioned above as HG-Git is included. What happens now is that instead of installing HG Git as described above — which should still work — I now just update the .hgrc file as follows
and that’s it! Note that I don’t need to provide the path to HG Git as Tortoise knows it, I also need to add in the hgext.bookmarks line.
The next step is to use the following command
hg bookmark -r default master
You must do this even if you don’t use bookmarks otherwise you seem to end up with an empty repo. You are now ready to simply
So now, my workflow for converting repos is as follows. Firstly create a bare remote git repo. This is on my file server where all my repos are stored. I use this as a central access point for all my repos. As it happens, all my git ones are in the same sub folder to make it easier for me to find them. As a result of this — the repo can be bare. This is an alien concept to Mercurial, it simply seems to mean that the repo is not a working one, it’s just a central store. Conveniently, git seems to change the icon of the folder which makes these easier to spot in file explorer.
Now that I have my remote git repo, I set the paths in .hgrc so that this is the new default. This means that I can now just HG Push from the command line and it should go smoothly. Then I clone it down to my machine using Fork. This gives me the best of both worlds and I can chose whether to use Git or HG if I need to and of course it maintains the history.
In some cases I setup the remote repo in the cloud e.g. Github. This means I can easily access them from my Mac, which sometimes can’t see my network servers.
The next step for me is to add the new remote git repo into Upsource. In most cases I replace the mercurial one with the new git one and Upsource just deals with it. I also now have the option of updating TeamCity to use the new git repo which is a little more time consuming but worthwhile If I can generate release notes and semantic versioning.
I’ve just tried to follow this on a new machine and for some reason TortoiseHG insisted that the remote git repo didn’t exist. As it was a new machine and hence a new TortoiseHG install I knew the answer should be a simple one. It turned out to be even simpler than I thought. As I don’t really use TortoiseHG now I hadn’t enabled any extensions. All I needed was to enable the HG git extension and it worked as expected.
Update 2 — a pull is not a pull
Things were going reasonably well, I was starting to get my head around how git clients represent merging, I don’t particularly like it but I have a better grasp of it now. I think I had reached the point where I knew just enough to be dangerous, Git makes you (or it did me) free and loose with commits because you know you can fix them later. Although this is part of the reason for trying it over Mercurial, I do think it goes too far, equally though I think I have learnt some better behaviour with regards to commits and am now getting better with Gitflow/HGFlow
Then it happened, as it seems to with Git, you’re just getting a bit comfortable with it when it throws you completely. This time it was pulls (mercurial type pulls). As I have only been working on one machine since I started with git, pulls have been pretty straightforward, the only commits that I haven’t created were ones from TeamCity which were pretty straightforward to deal with. Then I used another machine to create a feature branch and pushed it so I could access it on my original machine. So back on my original machine, all looks fine, I’m thinking all I need to do is a pull of the remote branch and all will be well but that wants to merge into an existing branch. I don’t think that’s what I want. Then I remember Fetch is the equivalent of HG Pull, but that doesn’t help either. Eventually I discover that when you know what to do it’s pretty simple, as I expected, it’s just that it’s not very obvious — at least not to me. What you actually need to do is checkout the remote branch. This creates a local copy of the branch and tracks the remote (I don’t really see the point of tracking but I’m sure there’s a reason behind it). All I need to do now is remember to use checkout, not the more intuitive to me, pull.
Update 3 — Git is slow
I’ve been regularly using git for a few months now. My preference is to use a Gui, primarily Fork. I’ve tried a few others but this is the one I like even though I don’t yet understand some of the options, one day I will look at them. There are a couple of small things that Fork doesn’t handle as well as SourceTree so I sometimes find myself using that. More recently I’ve occasionally started using the command line too and I suspect I will be pushed more to that if I learn all the obscure switches etc.
The biggest problem I have with Fork is that it can be slow, specifically when pushing/pulling/fetching. This was a suprise as there are many claims that it’s faster than Mercurial. I assumed that this was something about Fork so I tried with SourceTree which was an improvement as it lets you pull multiple branches at one time. This was slightly better but still not great. Having also tried the command line, I discovered that was also slow, so I can only assume it doesn’t like windows shares over a vpn (which works fine in Mercurial).
Somewhat bizarrely, it pushes much faster to Github/BitBucket over https. The problem there is the authentication. The gui’s all seem to bomb out, often with no indication of what’s wrong, so the only way I can push to Github is to use the command line which correctly asks for a user name and password. Although this may well be a windows only issue, I’ve not really tried on the Mac. I’ve also seen suggestions that git in Visual Studio can be slow under certain circumstances e.g. large repos which is what I have, so I have largely avoided that.
Now that I am a bit more confident with git I have made notes on some of the features that I find useful
Update 4 — Merging
It’s now quite a few months since I first wrote this article and I am comfortable with the basic operations of Git. I can do most of the things I want to without issue. In particular I like that I can easily edit existing commits and this has changed my workflow. What I do now is commit even more often to local branches than I was in Mercurial. The difference is that most of these amend the previous commit, so I build up a commit over time. When I am happy with it I can then squash it before merging into another branch and deleting the local branch. This works well for me and makes the history a bit simpler to read.
Most of the time these local branches merge as expected and all is well. I’m still using Fork as my GUI and am happy to pay for it when it stops being free which is what the website is currently warning about. For me it’s worth the low cost. I have it configured to use VS Code as my merge tool. With Mercurial I used to use Code Compare. I prefer VS Code for this as it understands the code and makes it easier to spot any merge issues in syntax etc. Having said that I still do get occasional issues and I’m not sure if it’s me doing something wrong or not. One recent attempted merge failed horribly whatever I tried, the results of the merge were totally unusable. Thankfully it’s easy (in Fork at least) to back out from a potential merge issue as there is an abort option. In Mercurial I would back out by updating to a different commit and discarding changes and I presume Git has something similar.
In this particular case it was fortunately only two classes that were affected ie a class and its corresponding unit tests class.I ended up resolving this by effectively doing a manual merge. I opened the source files in notepad so that they wouldn’t change when the underlying files did. I then started the merge in Fork and used the source files in conjunction with the merge file in VS Code to make the code sensible again. I’m just glad I didn’t have more than a couple of files to do this with.
The problem I have is that I’m not sure what causes this sort of issue. It’s not something I remember happening a lot in Mercurial. As a result of this I started looking at the merge options within Git. I’ve usually used the default option which was a fast forward merge. This produces merges that look like those in Mercurial and although they may not always be quite as straightforward to read as they might be. I can make sense of them. As it happens I have started working more with another developer and this means I have to be more careful about this sort of thing in the sense that it’s not just me that needs to understand it any more. With all this in mind I have started to look at how other merging strategies work. I found the guide at BitBucket which explains some of the options quite well. The full list of options is well documented on the Git site.
The conclusion to all this is that I am still not fully confident about merging in Git, though I know it mostly works as I expect, this seems to be another case of Git biting me when I am starting to get comfortable with it. I think I need to spend more time understanding the merge options. I also don’t fully understand what (if anything) is the issue with merge commits, they are just another commit. I’m just glad I never need to merge multiple heads at once — and can’t really think why I would want to.
Update 5 — One year on
It’s now almost exactly one year since I wrote the first version of this article. There are a few things that I have noticed that are of interest. Firstly I feel fairly comfortable with Git now, I use Mercurial no more than once a month — I only really have one repo that’s not worth converting and that will soon be obsolete anyway. I use Sourcetree less and less, I find myself using Fork, which is no longer quite free but is worth every penny. I also use the command line for the odd command, strictly speaking I should make mysef use it more but I do like the Fork gui. It’s not that I’m afraid of the command line, I’ve been using that for years, it’s more the liklihood that I will forget a switch or unintentionally use the wrong one.
I’ve been using GitFlow for most of the time, it’s one of the reasons I moved to Git, and it works well for me, in particular it’s easy for me to keep track of releases in my repo. I’m also using Github and have some open source of which the first is an extension library for .net I’m not sure I could have done this as easily, and for free with Mercurial especially now that it’s no longer supported by BitBucket, though there are other options they aren’t as well supported or known as GitHub which means they are less likely to integrate well into my existing infrastructure which is largely based on Jetbrains tools.
The only thing I haven’t fully got my head around, as I’m not certain I can see the point, is keeping repos ultra tidy. I am getting better at it but sometimes it’s an interactive rebase too far. I guess this comes back to the merging that I mentioned earlier. I’m still using VS Code for merging and it handles it well for me. I particularly like that I can abort part way through if its not going well. I also sometimes find it easier to rebase a couple of commits at a time. I’m also starting to get braver with Git, I’ve been trying some of the features that I haven’t touched before. I’m getting some unexpected results in the sense that it doesn’t do quite what I expect. I guess there is a lot to learn yet if I want but for now I can confidently use it on a day to day basis. I’ve started using stashes a lot more and I’ve even rolled back a commit. Overall this is going relatively well.
Finally, this article has proved to be my most popular by a long way with an average of a few views per day. While that may not be huge, it seems to indicate that there are still quite a few Mercurial users out there that are thinking of taking the plunge. I find that rather sad as I still think Mercurial is a better VCS, in the same way that Betamax was better than VHS. Having said that if you’re one of these people, I would suggest you try it with a small group of repos, if you like gui’s find one you like — there are a vast amount to choose from — and just take the plunge, in all probability, like me, you won’t turn back.