Why Mercurial?

Why not use the near universal other version control system?

  • I’m willing to give Linus the benefit of any doubt. But Microsoft? Emphatically no. So: git maybe; hg yes; GitHub requires serious financial compensation and will never be trusted with the only copy of my productive output, or trade secrets, etc.

  • When I git clone repos temporarily to have look at them and then try to get rid of them with rm -r that fails with a lot of confirmation prompts for files git has created with u-w (0440 or 0444) access. WTF is that about? Are Github users just so prone to accidentally deleting some files out of their repos that they need to be protected from themselves? It’s just weird. Mercurial does not have this absurd problem.

  • Holy shit this is annoying. And then you have to worry about emails being scraped, etc. Just leave email out of it!

*** Please tell me who you are.
Run
  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"
  • Let’s say I create a directory, and do the normal stuff.

mkdir /tmp/gittest && cd /tmp/gittest
git init
git add README.nerd
git commit

Ok, great. Now I have a trusted collaborator who I want to be able to clone this. They actually can get this far on a remote host.

git clone ${HOST}:/tmp/gittest
cd gittest
vi README.nerd
git add README.nerd
git commit

Cool. So now I want to push this change back to the original that the main developer is working on in its primary location. But what happens when I try git push? I get a huge overly complex error message with multiple strange possible work-arounds. The gist of the problem is described in the error message like so.

remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: is denied, because it will make the index and work tree inconsistent
remote: with what you pushed, and will require 'git reset --hard' to match
remote: the work tree to HEAD.

So I don’t have time to become an expert in what that means. Asking ChatGPT to cut through the cruft, I arrive at the following conclusion.

Mercurial doesn't have this issue, as it handles pushing to a non-bare
repository with a checked-out working directory more gracefully. When
you push to such a Mercurial repository, it updates the repository
data, and the next time someone performs an hg update on the remote,
it will synchronize the working directory with the latest changes.

So, in terms of this specific issue, Mercurial would give you an
easier out-of-the-box experience for the topology you're considering.

I would expect git to be fine if you have a central (non-working!) repo living at https://kernel.org or https://github.com but if that’s not how you’re doing things, Mercurial can be the difference between possible and not. I actually couldn’t believe that git was deficient in this way (without onerous esoteric configuration tweaks) but it certainly fails as shown and research to overcome this limitation — perhaps there for some good reason in another context — turned up nothing for me. Maybe I’m just overlooking the easy way to get Git to behave like I want, but I am 100% certain that this putative easy way is not easier than simply using Mercurial.

  • Here is a brilliant article about distributed source control systems in general with an emphasis on Git and a clear look at the pros and cons of different approaches.

Set Up

Before starting make sure this is setup.

$HOME/.hgrc
[ui]
username = Chris X Edwards <cxe-hg@xed.ch>
editor = /usr/bin/vim

Starting A Project With Some Files To Track

$ cd coolware # Be in the directory you want to manage.
$ hg init     # Creates .hg for this directory.
$ hg status   # Show all non-ignored files.
$ hg add      # Add all 'unknown' files.
$ hg add coolfile1 coolfile2  # Or add specific files.
$ hg ci -u xed -m "Initial coolness" # 1st commit.
$ hg parents  # Show currently checked out revision.
$ hg status -A # Show status of all tracked files.

Complete Example Of General Use

$ mkdir coolware
$ cd coolware
$ echo "/* The coolware main program */" > coolware.c
$ hg init
$ hg add coolware.c
$ hg ci -u xed -m "Initial coolness." coolware.c
$ cd ..
$ hg clone coolware coolware-dev
$ cd coolware-dev
$ echo "/* A very cool program. */" >> coolware.c
$ hg ci -u xed -m "Writing cool software." coolware.c
$ cd ../coolware
$ hg pull ../coolware-dev/
pulling from ../coolware-dev/
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
(run 'hg update' to get a working copy)
$ cat coolware.c
/* The coolware main program */
$ hg up
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cat coolware.c
/* The coolware main program */
/* A very cool program. */

Cloning A Repository

If a project lives somewhere and you want it somewhere else, this is a possible technique for doing that:

hg clone ssh://xed@xalab.example.edu/../sysadmin/safe/chemmgr/

Note the weird ssh URL format, the host is separated from the path by the path separator (/). Also note that the path is relative to the home directory. In this example, I’m showing how to lift a repository out of someone else’s home directory as an example of how to get around this overly helpful feature.

Here is how to specify absolute paths.

hg clone ssh://user@server//home/projects/alpha/

If you need to use a non-standard port. Here SSH listens to 2020.

hg clone ssh://xed@70.59.33.247:2020/pathfromhome/myproject/

If you started the project behind a firewall and want to establish it on your main well-connected server, do something like this from inside the project’s local directory (which will have been set up for hg with hg init etc):

hg clone . ssh://xed@remote.xed.ch//home/xed/project/goodtimes_project/
ssh xed@remote.xed.ch
cd ~/project/goodtimes_project
hg up

Updating Changes From Elsewhere

Say you made some changes on a work machine called w and then you came home to a machine called h that you wanted the changes to also be on. First you have to pull the work repository (or you could have pushed before you left).

hg pull ssh://xed@w/path/aproject

But that’s not all. Nothing will seem different. The h repository will now be aware that changes have been made, but to actually apply them to the local repository, you need to update that with:

hg up

Cloning And Updating A Repository To Another Location

Although these decentralized version control systems have a lot of flexibility, the normal thing is that you have a central storage location and several people work on the same code in several different locations. That is what was just previously described.

But what if you’re working on the code mostly in one location but want to use the source control to distribute it — and of course changes — to multiple hosts. For example, if I have a repo of my unix configuration files, I may want to push that out to various hosts when I’m setting them up. Since that will be constantly being improved in arbitrary places (any host) the system needs to be flexible. This can be done in the following way. Start by creating the remote copy of the repo like this.

cd ..  # From inside repo skelx
hg clone skelx ssh://testuser@192.168.1.250//home/testuser/X/skelx

It can then be updated from within the repo to the remote location like this.

hg push ssh://testuser@192.168.1.250//home/testuser/skelx

Obviously there are topology problems if I make different changes to this repo from two remote systems and never coordinate.

Once the remote repo is in action on the less important host, you can merge any changes made there back to the secure main host with a pull.

hg pull ssh://$HOST:$PORT//home/testuser/X/skelx

This prevents the need to log in to the secure central machine from a potentially insecure situation. In summary, push your centralized changes from your secure machine and collect miscellaneous changes on the remote machines by pulling. Avoid authenticating back to your secure machine from random accounts you may have access to.

What Version Is This

You may want to know what version you’re up to.

hg log

You may want to even slip this into source code somehow. I’ve used this to generate a that information the way I wanted it.

hg log -l1 --template '{date|shortdate} - Version: {rev}'

More details about how you can format such things here.

What Files Changed

Sometimes you see evidence of changes when you pull in a repository that someone else (or you elsewhere) is working on.

adding file changes
added 2 changesets with 4 changes to 3 files
new changesets d8d47651e7a3:1d1218fa8546
...
3 files updated, 0 files merged, 0 files removed, 0 files unresolved

It is clear that some files changed, but which ones? To find out, use this command.

hg status --change .

Reorganizing Things

The worst thing about all of the version control systems is that they are philosophically self-defeating in the sense that if you knew the final organizational structure of all your project’s files, you probably would also know the final exact syntax of the entire project. But the whole point of a version control system is that it manages your lack of this foresight. However, renaming and moving files into completely new directory trees can be a pain.

Simple Renaming

For a simple mistaken filename, this works fine.

hg rename xt2png.c xy2png.c

Note that apparently on some platforms there can be issues with simply changing case. As described here you may need to do something like this.

hg rename xy2png.c tmp
hg rename tmp XY2png.c

This sequence of events should preserve history.

Changing Directory Layout

Let’s say I had a program called foo that was being tracked. It was a minor thing designed to be a proof of concept. Now we want to rewrite foo from scratch with all the right things done from the beginning. We don’t want to lose our earlier work, but we want to move existing foo work into a directory called foo-prototype to refer to it. Here is what can work.

mkdir foo-prototype
hg mv foo.c foo-prototype/foo.c
touch foo.c  # Starting the new one.
hg add foo.c

What Files Are Being Tracked

Sometimes I lose track of which files are being tracked. Check with:

hg manifest

Where Did This Stuff Come From?

Sometimes I have something on a fancy server somewhere and I pull it in and that’s all well and good. Mercurial makes pushes automatic so it’s easy to forget where that originally was being stored. If I need to check it out into a different place how can I figure out where it came from in a place I’m currently using it?

$ hg paths
default = ssh://xed@myisp.com/hg/mycoolproject

What Went Wrong?

Sometimes something is done, often to fix a bug, and you wind up making something else not work. You want to go back to the place you "fixed" and revisit that but you can’t remember where it was or what you did. First thing to do is to check the commit logs. I like to reverse them so the newest is last.

hg log | tac

This produces a lot of stuff, but if you can find the commit that was around the problem, look for it’s "changeset" id which should look something like: 87:c472394a44cc. The 87 is important here. To see the changes that were made on this commit:

hg diff -r 87

To see just what files were worked on use:

hg log --template '{files}\n' -r 87

Oops - Revert To Previous

The best summary I have seen is here.

Basically hg revert --all will undo whatever mess you’ve just made, but you can also use --rev to specify a particular revision number.

A good sequence of actions is something like this.

hg log somecode.py # Useful for seeing what's been going on with the file.
hg annotate somecode.py # Lists currently tracked code with rev # that caused it.
hg diff somecode.py # Show only additions/deletions between previous version and current.
hg revert --rev 2 somecode.py # Resets current code back to change set #2.
vi somecode.py # Try some better strategy with the file.
hg ci somecode.py # Check it back in heading in a new direction.

What if you just want to take a look at a previous version to get an idea what you might have messed up?

hg cat sourcecode.cxx -r 117 > sourcecode-117.cxx

Push Creates New Remote Head

Sometimes you do a hg push and you get this mysterious message telling you something is wrong.

abort: push creates new remote head c473eca09ada!
(merge or see 'hg help push' for details about pushing new heads)

This probably means that you and the remote team did some changes at the same time. The answer is to merge them.

hg pull
hg merge
hg commit -m "= merge"
hg push

And it should be fine. Obviously if your merge conflicts, that needs to be sorted out.

Mercurial is pretty cool about how it handles binary files. Basically no special treatment for binary files is needed and it the system should be fine.

I wasn’t sure about symlinks, but indeed symlinks are tracked as symlinks. So that’s good to have confirmed.

Magic Web Server

Despite the mania for Github, Mercurial is really easy to get on the web and it always has been. The good thing is that you do not need to entrust management of your code to a proprietary company. You can easily host your own repositories. To do so, just be in the tracked directory and do this.

hg serve

That’s it. Note that this automatically starts its own web server on the spot. It is not daemonized but a process that displays all connection attempts. It can also be put into a cgi-bin directory but that’s a different topic. Now point a web browser to your host like this.

http://host.xed.ch:8000/

And you can browse away at the change history and the tracked files. The default permissions are read only. To get the whole project, just do something like this from the command line of the receiving system.

hg clone http://host.xed.ch:8000 project

Using One SSH Hosting Account For Multiple Contributors

Let’s say you have a small group working on a project. They would all like a place which is on 24/7 which can be the main coordinating repository for the group. By renting a $5/month SSH host (or use AWS for free if you’re brave) you can give people personalized access to the repository using SSH keys. Basically the way it works is that all the participants generate an SSH key pair. The public keys are rounded up and put into the hosting service account’s ~/.ssh/authorized_keys file. Instead of giving everyone complete access you can limit the other participants to just being able to work with mercurial. You do this by using the command= directive on the SSH key. Here is an example from the hosting service account’s authorized_keys file.

hosting.service.com:/home/mainaccount/.ssh/authorized_keys
command="hg -R hg/ourproject serve --stdio",no-port-forwarding,\
no-X11-forwarding,no-agent-forwarding \
ssh-rsa AAAAB3NzaC...full/public/key...zYFiidCx amigo@amigoshost

This is all a one line entry, i.e. replace the \ and returns with nothing. Note that the main repo lives in ourproject in the directory ~/hg.

To use this key from a client somewhere, you’ll need the corresponding private key and the password to decrypt it. Then you must tell mercurial that you don’t want ordinary SSH, you want special key SSH. Like this.

hg clone --ssh "ssh -i /home/amigo/.ssh/ourproject_rsa" \
ssh://mainaccount@hosting.service.com/hg/ourproject

If there are problems check my SSH notes especially the part about "Specifying Commands".