I always loved Mercurial for its clean command line interface and 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
xinetd Service (older Fedora)
Update 2023-10-29: Older Fedora distributions provided xinetd, which worked out well for git-daemon, but later distributions no longer have this service and one needs to set up a systemd service to run git-daemon.
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.
systemd Service
Only the git-daemon package can be installed for this setup.
dnf install git-daemon
Create a service descriptor file that looks like this (lines ending in ► are wrapped for display purposes and should be all on the same line).
Description=Git Daemon
After=network.target
[Service]
User=nobody
Group=nobody
WorkingDirectory=/var/git
ExecStart=/usr/bin/git daemon ►
--export-all ►
--enable=upload-pack ►
--enable=upload-archive ►
--enable=receive-pack ►
--base-path=/var/git
Restart=no
[Install]
WantedBy=multi-user.target
Reload the systemd daemon to pick up the new service.
systemctl daemon-reload
systemctl enable git-daemon
systemctl start git-daemon
Open Git ports in the firewall. Make sure this machine is not exposed outside your local network.
firewall-cmd --permanent --add-port=9418/tcp
firewall-cmd --reload
Note that latest versions of Git will report this cryptic error if an actual user, other than nobody, runs any command against any of the server repositories.
fatal: detected dubious ownership in repository at '/var/git/abc'
You can either set up Git Daemon with a different user or temporarily take ownership if you want to run any Git commands on the server.
Creating Repositories
A new empty Git repository may be created on the server as follows.
cd /var/git git init --bare project-a chown -R nobody:nobody project-a
Creating a script with the last two lines using a script argument 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. Note that the target Git repository must be initialized as a bare repository or it will report that refs/heads/master failed to update.
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 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 and patches, 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.