Mercurial: Why So Unhelpful?
Published by marco on
I’ve been using Mercurial for a little over a year now, but I’m still kind of a newbie because:
- I don’t use it every day
- I use it only for private projects, so there aren’t many merge issues
For earthli.com development, I have two repositories: One for the web site content itself and another for the earthli WebCore, the backend for the web site. For each of these projects, I have the following repositories:
- Local repository
- Server repository (development)
- Server repository (production)
I usually make changes to the local version, then upload to the “dev” repsitory and test them on the server before pushing them to production. Sometimes, though, I have to make a quick fix on the production server itself when a user is blocked by a bug. Most of these fixes are one-liners in a single file and I try to push them back to “dev” immediately. Since I work alone, I’m not so accustomed to downloading from the server repository before working locally again. Couple all of this with the fact that I do actual web site development on earthli.com quite sporadically and you have a recipe for files left lying around in various states:
- Uncommitted in either “prod” or “dev”
- Committed in “prod” but not pushed to “dev”
- Committed in “dev” but not pushed to “prod” (actually, that’s ok)
Uncommitted or unpushed files from the local repository are absolutely OK, though I still sometimes forget that I need to push to the server in addition to committing in order to actually see my changes. In fact, a full check-in looks like this:
…test local local:$ hg commit local:$ hg push local:$ ssh earthli earthli:# cd earthli/dev earthli:# hg update …test dev earthli:# hg push earthli:# cd earthli/prod earthli:# hg update …test prod
This is actually all well and good and works just great as long as I always work in this order: Local => dev => prod. It’s when I make one of the aforementioned changes directly to “prod” that I end up having to merge changes.
Mercurial was totally designed to handle merging. I’m also quite familiar with the concept. But, with my local development on OS X and the servers running Debian, there is a paucity of visual support for merging. After years of administering OS X and Debian boxes, I’m also quite comfortable with the command line and am at least an advanced newbie at
vi. Those are all not problems. What I don’t like to do is resolve merge conflicts on the command line or using a text editor and hand-editing conflict markers. Fortunately, that happens exceedingly rarely, as I haven’t managed to make conflicting changes in different repositories too often.
But I chose both my server and development environments and will have to live with command-line support only for now. I just wish that command-line support was a bit more helpful.
Here’s a recent example to illustrate my frustration. I was trying to push changes from “dev” to “prod” and was told that the push would create multiple heads at the target. I totally grokked that: It meant that there were changes in “prod” that were not in “dev”, so pushing from “dev” to “prod” would necessarily make another line of changes that would have to be merged in “prod” in order to have a single “tip”. That wouldn’t have been the end of the world, but I appreciated Mercurial informing me of the situation as well as offering that using the
-f flag would allow me to force the push, if I so desired. I did not, however, so desire, because I never want to merge in prod. I always want to make all my mistakes in “dev”, get everything working as expected and then push to “prod”. The only commands I want to use from “prod” are:
hg updateto copy the latest changes from the repository to the local hard disk (from which the web server reads)
hg committo commit the rare emergency bug-fix to the “prod” repository.
Ok, so instead I’ll
hg pull from the “prod” repository to the “dev” one, making two heads there.
earthli:# hg pull ../prod pulling from ../prod searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) (run ‘hg heads’ to see heads, ‘hg merge’ to merge) earthli:#
So far, so good. That’s exactly what I expected to happen. Let’s check the heads, as Mercurial has so nicely suggested:
earthli:# hg heads changeset: 16:9197ac29588a tag: tip parent: 14:cad4ff88973d user: firstname.lastname@example.org date: Sun Jan 24 20:18:54 2010 +0100 summary: Removed a bunch of pages from the search site changeset: 15:f7c0e21d2e18 user: email@example.com date: Fri Apr 02 18:05:06 2010 +0200 summary: − Fixed crash on dieting form earthli:#
Again, those are two most recent changes I made in each branch. Mercurial also helpfully suggested I use
hg merge to merge the changes. Let’s try that.
earthli:# hg merge abort: outstanding uncommitted changes earthli:#
Well, I think I know what that means: The repository for the web site includes configuration files, one of which specifies local paths. Naturally, that file differs for the “prod” and “dev” deployments. In the local copy of “dev”, I had updated that file, but hadn’t checked in the changes so that they would never get transferred to “prod” by accident. I can only assume that Mercurial is complaining about that file, but let’s check the status to be sure.
earthli:# hg status M lib/plugins/com.earthli.webcore.init.php M site/robots.txt earthli:#
lib/plugins/com.earthli.webcore.init.php is the configuration file and
site/robots.txt is the change I made to “prod” that needs to be merged in. But what’s Mercurial complaining about? That it can’t merge in the changes to
site/robots.txt while there are outstanding changes to other, completely unrelated files? That seems to be the case. Why in the name of all that is holy should that be the case? Perhaps I’ve misunderstood something?
At any rate, I only know the
hg status command, but it only shows me that two files are modified and not that they belong to two different heads. The conflict is not evident here, which is a shame. A newbie merging mode would be nice that would hold my hand a bit more and tell me what to do here.
The first thing I tried was to commit only the file that came in with the
# hg commit site/robots.txt abort: cannot partially commit a merge (do not specify files or patterns)
Aaaarrrgggggh. Now what? I feel like a total moron. Mercurial is scolding me for trying to “partially commit a merge” even though, as far as I can tell, that single file is the only file that was merged. I guess it makes sense that I can’t commit that file because it hasn’t technically been merged yet. But do I have to commit or revert all local changes before I can do any merging? That would seem to be the case. It may technically not be the case, but Mercurial is not being very forthcoming on other options.
In the words of Homer Simpson: “urge to kill rising.”
The status of my repository is that two files have been modified (as shown above). However, one of them has a special status: that it is unmerged. Out of curiosity, I tried to commit and was greeted by the following commit message in good old
HG: Enter commit message. Lines beginning with ‘HG:’ are removed. HG: – HG: user: firstname.lastname@example.org HG: branch merge HG: branch ‘default’ HG: changed lib/plugins/com.earthli.webcore.init.php
As I suspected, the file I actually want to commit is not commitable at this time, and the one I definitely don’t want to commit can be committed. Now that’s a fine how-do-you-do.
Then, I had a killer idea: Most version control systems have a way of ignoring files or file patterns. The two other systems with which I’m familiar both use an “ignore” command or some file with the word “ignore” in it. I checked the Mercurial help for the word “ignore” and found nothing. I searched online and found a bunch of stuff about an
.hgignore file, which I didn’t get to work correctly and other tips that said to use an
hgignore file (without the leading dot) and that didn’t work either. I’ll probably look into this again later, because I really need to ignore those configuration files.
So, here I am with a problem that boils down to copying a single file from one directory to another. The version control system I’m using is offering to check in a file I want it to ignore and not allowing me to merge the file I need until I take care of the file it refuses to ignore. What I ended up doing was reverting the changes to the configuration file, executing the merge, then pushing the changes from “dev” back to “prod” and, finally, restoring the altered configuration file under “dev”, where it will bite me in the ass the next time, probably in six months, when I can go through this all again.
Part of the problem, as I see it, is that non-trivial—heck, even trivial—merging and diffing and resolving are goddamned hard using only command-line tools. Doing this kind of stuff without more graphical input is an accident waiting to happen. Naturally, there will be geniuses who claim this not to be the case, but that doesn’t help the rest of us—the bottom-feeders: Someone should be trying to help us get our work done, too. The other problem is that Mercurial assumes that you know exactly what your files are doing at all times without any extra help. In the example above, why does the call to
hg status not show me that my file is unmerged? It clearly knows this.
Ah, well. I imagine I’ll get used to it at some point or some guru will point out what I’m doing wrong. I just feel that this is some of the polish that’s missing from some of these super-tools: They get so much of the technical stuff working really well, then hide it behind an inscrutable, unhelpful interface. I’ve done it myself too many times: Built the basic framework and left making an actually intuitively useful application on top of it as an exercise for the next programmer. It really wouldn’t be that hard to build in a mode that showed—maybe even with ASCII art—what was going on and what the poor newb’s options were.
One could argue that a command-line interface for a DVCS is, by definition, a hardcore programmer’s tool, but why do we hardcore programmers always have to suffer? Why do we get tools that force us to use every bit of brain power we have just to accomplish what should be very simple tasks? Don’t we ever get a break? Or should we just be grateful that there are tools like Mercurial at all? Maybe that’s it.
a) Use the shelve or the attic extension to temporarily undo local changes to a repo. These basically keep shelved changes stowed away in a patch file so you can reapply them later. AFAIK attic actually uses Hg’s merge-forward logic to reapply to the original ancestor of the shelf, then merges forward to where you really want them applied now. This is more resilient than trying to apply patches with fuzz. I do this too, with two small shell scripts, which I’ll gladly send you if you want them. If you want to get super-sophisticated, check out mq with guarded patches. But before you do any of this, check out suggestion c).
b) Read `man hgignore`. But beware that a file, once in the current manifest, is tracked by hg regardless of what .hgignore says. This is actually a feature.
c) Consider moving non-default configuration to a secondary, untracked file, which earthli should look for but silently skip if missing. This will much better guard you from accidentally committing temporary config changes.
d) Consider using an update hook to automatically update to tip on your servers when something gets pushed (no ssh’ing anymore).
e) Look into the graphlog extension. Shows some ASCII art.
* Since there is `hg merge -f` to force a merge over uncommitted changes, then why is there no way to commit just the effects of the merge? Might be a little tricky, though, as Hg conceptually does whole-tree merges, not single-file ones.
* Why does `hg status` not show resolve status? (I guess it’s because of backwards compat for scripts. Hg should have a dedicated parseable format that is _not_ the default UI output from the start…) Try `hg resolve -l` instead.
And I wonder why you even got a modified robots.txt with uncommitted changes in the working copy. `hg merge` should not have touched robots.txt in this case.
What I don’t see is your gripe about merging from the command line. Aren’t you merging on your dev-box? Don’t you have visual tools for the actual file-merges there? You can even use p4merge. (http://mercurial.selenic.com/wiki/MergeProgram)
Sadly, TortoiseHg is not yet available for OS X. I use it on Ubuntu and simply love the way it lets me work from the command line and seamlessly switch into GUI mode as needed.