Andre's Blog
Perfection is when there is nothing left to take away
Switching from Mercurial to Git

I always loved Mercurial for its clean command line interface and its powerful revision query language, which are very well structured, compared to Git's adhoc command line approach that combines unrelated concepts in various command line elements, such as allowing commit message search in a revision specification (e.g. git log ":/^ABC" --), which backfires by reporting a bad revision, depending on whether the text was found or not. However, support for Mercurial services and tools has been declining for quite a while now and the time has come to switch to Git.

Local Git Server

Online Git services are great, but most limit the number of private repositories and, private or not, some source repositories are better kept truly private - in the safety of your local network. So, the first order of business for me was to set up a local Git server.

I don't need user-based security for my repositories and a simple Git server implemented by git-daemon sounded like a good candidate for my setup. All commands below are for the version of Fedora I use, which is v30.

Start with installing git and git-daemon, which are separate packages.

dnf install git git-daemon

You can access Git repositories in any location on your Linux server, but I prefer all of them to be under one root, similar to how I maintained my Mercurial repositories, so I picked /var/git as a base directory. Change the owner of that directory to any Git user you prefer. I just kept nobody, as suggested in the git-daemon docs.

mkdir /var/git
chown nobody:nobody /var/git

Check that git is configured as a valid network service on your machine:

grep ^git /etc/services

You should see something like this in the output:

git             9418/tcp                # git pack transfer service
git             9418/udp                # git pack transfer service

I'm running xinetd in my setup, which is a bit different from inetd described in git-daemon docs, but except for how configuration is specified, all arguments are the same, so if you want to tweak the configuration, check out the docs:

https://git-scm.com/docs/git-daemon

In order to add a Git service to xinetd configuration, create a file called git in /etc/xinetd.d/. The contents of the file looks like this (lines ending in ► are wrapped for display purposes and should be all on the same line):

service git
{
    port        = 9418
    socket_type    = stream
    protocol    = tcp
    wait        = no
    user        = nobody
    passenv        =
    server        = /usr/bin/git
    server_args    = daemon --inetd --export-all ►
                      --enable=upload-pack ►
                      --enable=upload-archive ►
                      --enable=receive-pack ►
                      --base-path=/var/git
}

Restart xinetd after adding the configuration file.

systemctl restart xinetd

Notice --base-path in the configuration, which points to the directory created earlier, which will contain all of Git repositories. Switch to that directory to create all repositories you want to host on this machine.

cd /var/git
git init --bare project-a 
chown -R nobody:nobody project-a

Creating a script with the last two lines in /var/git is a good idea to save typing as new projects are added.

You can also create additional directories under the base path to separate Git projects locally, such as /var/git/web/ui, /var/git/web/backend, etc. Just keep in mind that intermediate directories should be owned by nobody or whichever Git user you want to use.

If you have control over your DNS server, you may also want to add a meaningful name for the machine hosting Git repositories. I called mine git, but for the purposes of this blog will use git-host to differentiate it from the protocol scheme in the URL.

Git Client Setup

On a client machine you can clone Git repositories using the git scheme, like this:

git clone git://git-host/project-a

, which will clone the repository on git-host in the /var/git/project-a directory.

There is a bug in Git for Windows v2.28.0 that makes it hang indefinitely when trying to push anything to a repository on the server (usually after it says Writing objects: X% with some actual percentage value). The amount of data it is trying to push does not matter - could be for as little as a few bytes.

In order to work-around this bug until it is fixed, run this once on a Windows client machine:

git config --global sendpack.sideband false

Mercurial Repositories

Mercurial repositories that do not use sub-repositories or Mercurial Queues are fairly straightforward to convert.

Enable hg-git extension in your Mercurial configuration. Recent Mercurial installations already have hg-git and you just need to enable it, like this:

[extensions]
hggit =

Read more about hg-git here:

https://www.mercurial-scm.org/wiki/HgGit

hg-git pushes Mercurial bookmarks as Git branches, so you need to create a bookmark for every branch you use. Run hg branches to see a list of branches and create a bookmark for each one of them, like this:

hg bookmark -r default master
hg bookmark -r v1-0 v1-0-bm
hg bookmark -r v2-0 v2-0-bm

Bookmark names can be anything, as long as they are different from the branch names in Mercurial. You can rename them later in Git to form proper branch names.

At the very least, the default branch in Mercurial must be mapped to master in Git via the bookmark, as shown on the first line of the sample above.

Now you can push your Mercurial repository to Git and let hg-git do all the work.

hg push git://git-host/project-a

If there are no errors, test the new repository by cloning it into some location on your client. Be mindful of the project name not to collide with the current Mercurial name. 

git clone git://git-host/project-a new-home-for-project-a

Mercurial Sub-repositories

hg-git treats Mercurial sub-repositories as such during a push and doesn't do anything to convert them into Git repositories, even if there is one, so if you use sub-repositories, you will have to find some way to figure out how to deal with historical changes.

That is, main repositories track their links to Mercurial sub-repositories in .hgsubstate file, so if you wanted to reproduce all history, you would need to push Mercurial repositories quite carefully at the points where .hgsubstate was updated and reproduce the same links manually with a series of Git's submodule commands followed by rebase commands to reconstruct the original tree with a properly wired sub-repository states.

I spent some time on this and while I could get it going for a couple of levels, I eventually decided to cut my losses and just added the most recent submodule to the main repository, so latest builds still work and are wired to the correct submodule revision. If you need historical builds working for bug tracking, etc, you need to either take the tedious road of rebasing branches a few times or find a better solution if somebody came up with one.

Mercurial Queues

Mercurial Queues were quite helpful in many ways in the past, but Git does not natively offer support for such patch management, although there are 3rd-party add-ons for this.

I mostly used Mercurial Queues for maintaining patches for my changes in 3rd-party source code and decided to just switch to the good old vendor branches, so I didn't investigate patch management equivalents for Git. Just be mindful that you will need to find something if you intend to continue using patches in your development.

Comments:
Name:

Comment: