Mercurial_ The Definitive Guide - Bryan O'Sullivan [65]
Figure 9-3. Backing out a change using the hg backout command
After the hg backout command has completed, it leaves the new “backout” changeset as the parent of the working directory.
$ hg parents
changeset: 2:b0ecc05c2a5d
user: Bryan O'Sullivan date: Tue May 05 06:44:22 2009 +0000 summary: third change Now we have two isolated sets of changes. $ hg heads changeset: 3:b47000db73c8 tag: tip parent: 1:60b1c4c78499 user: Bryan O'Sullivan date: Tue May 05 06:44:22 2009 +0000 summary: back out second change changeset: 2:b0ecc05c2a5d user: Bryan O'Sullivan date: Tue May 05 06:44:22 2009 +0000 summary: third change Let’s think about what we expect to see as the contents of myfile now. The first change should be present, because we’ve never backed it out. The second change should be missing, as that’s the change we backed out. Since the history graph shows the third change as a separate head, we don’t expect to see the third change present in myfile. $ cat myfile first change To get the third change back into the file, we just do a normal merge of our two heads. $ hg merge abort: outstanding uncommitted changes $ hg commit -m 'merged backout with previous tip' $ cat myfile first change Afterwards, the graphical history of our repository looks like Figure 9-4. Figure 9-4. Manually merging a backout change Why hg backout Works As It Does Here’s a brief description of how the hg backout command works. It ensures that the working directory is “clean,” i.e., that the output of hg status would be empty. It remembers the current parent of the working directory. Let’s call this changeset orig. It does the equivalent of a hg update to sync the working directory to the changeset you want to back out. Let’s call this changeset backout. It finds the parent of that changeset. Let’s call that changeset parent. For each file that the backout changeset affected, it does the equivalent of a hg revert -r parent on that file, to restore it to the contents it had before that changeset was committed. It commits the result as a new changeset. This changeset has backout as its parent. If you specify --merge on the command line, it merges with orig, and commits the result of the merge. An alternative way to implement the hg backout command would be to hg export the to-be-backed-out changeset as a diff, then use the --reverse option to the patch command to reverse the effect of the change without fiddling with the working directory. This sounds much simpler, but it would not work nearly as well. The reason that hg backout does an update, a commit, a merge, and another commit is to give the merge machinery the best chance to do a good job when dealing with all the changes between the change you’re backing out and the current tip. If you’re backing out a changeset that’s 100 revisions back in your project’s history, the chances that the patch command will be able to apply a reverse diff cleanly are not good, because intervening changes are likely to have “broken the context” that patch uses to determine whether it can apply a patch (if this sounds like gibberish, see Understanding Patches for a discussion of the patch command). Also, Mercurial’s merge machinery will handle files and directories being renamed, permission changes, and modifications to binary files, none of which patch can deal with. Changes That Should Never Have Been Most of the time, the hg backout command is exactly what you need if you want to undo the effects of a change. It leaves a permanent record of exactly what you did, both when committing the original changeset and when you cleaned up after it. On rare occasions, though, you may find that you’ve committed a change that really should not be present in the repository at all. For example, it would be very unusual, and usually considered a mistake, to commit a software project’s object files as well as its source files. Object files have almost no