hg backout [OPTION]... [-r] REV
Revert/undo the effect of an earlier changeset.
Backout works by applying a changeset that's the opposite of the changeset to be backed out. That new changeset is committed to the repository, and eventually merged.
0.1. Here's some more detail from Matt about the inner workings (and slightly adapted by experience)
(see also this email thread)
Let <startrev> be the revision we're at when we start the backout.
Backout is basically four steps rolled into one:
hg update -C -r <rev-to-backout>
hg revert --all -r <parent of rev-to-backout>
hg update -C -r <startrev>
There's a fifth step that is done automatically if you specify --merge :
hg merge (merges <startrev> with the newly committed rev from 3.)
And there's a sixth, manual step:
hg commit (the result of merging)
When step 3 (commit) aborts, you're left with the first two steps completed and you can either:
hg commit yourself to complete it, and/or
hg update -C to abandon the process
Step 4 assures the parents of the committed merge changeset are in the right order. That is : parent1 = <startrev>, and parent2 = <the new backout rev>.
0.2. An example:
$ hg init borepo $ cd borepo $ echo line1 > file.txt $ echi line2 >> file.txt $ hg ci -Am "add file"
Edit file.txt, so it contains:
line1 line1a line2
Commit and add a few more changesets:
$ hg ci -m "add line1a" $ echo line3 >> file.txt $ hg ci -m "add line3" $ echo line4 >> file.txt $ hg ci -m "add line4"
Which produces the following (somewhat shortened) graph:
@ changeset: 3:36b1c0649d3e | tag: tip | summary: add line4 | o changeset: 2:2612107e45fe | summary: add line3 | o changeset: 1:1f33c361852e | summary: add line1a | o changeset: 0:e3e45b087239 summary: add file
Now we backout changeset 1:1f33c361852e.
$ hg backout -r 1
The graph is now:
o changeset: 4:c3daad6d657d | tag: tip | parent: 1:1f33c361852e | summary: Backed out changeset 1f33c361852e | | @ changeset: 3:36b1c0649d3e | | summary: add line4 | | | o changeset: 2:2612107e45fe |/ summary: add line3 | o changeset: 1:1f33c361852e | summary: add line1a | o changeset: 0:e3e45b087239 summary: add file
We merge and commit, yielding the final graph:
@ changeset: 5:236d8d74edf8 |\ tag: tip | | parent: 3:36b1c0649d3e | | parent: 4:c3daad6d657d | | summary: merge backout | | | o changeset: 4:c3daad6d657d | | parent: 1:1f33c361852e | | summary: Backed out changeset 1f33c361852e | | o | changeset: 3:36b1c0649d3e | | summary: add line4 | | o | changeset: 2:2612107e45fe |/ summary: add line3 | o changeset: 1:1f33c361852e | summary: add line1a | o changeset: 0:e3e45b087239 summary: add file
And file.txt looks like this, nicely eliminating the 'line1a' from rev 1.
line1 line2 line3 line4
When we try this using the separate steps, and we omit step 4, we get a slightly different graph. Note the reversed order of the parents in changeset 5:0eeac5ff9c76.
@ changeset: 5:0eeac5ff9c76 |\ tag: tip | | parent: 4:cbca219e80e1 | <--- | | parent: 3:f82e9468d652 | | | summary: merge backout | | | o changeset: 4:cbca219e80e1 | | parent: 1:0cf85b44002c | | summary: Backed out changeset 0cf85b44002c | | o | changeset: 3:f82e9468d652 | | summary: add line4 | | o | changeset: 2:24ceac6b9018 |/ summary: add line3 | o changeset: 1:0cf85b44002c | summary: add line1a | o changeset: 0:73af1be51d81 summary: add file
Backout of a Merge Changeset
Imagine a situation where we merge two changesets. The first changeset has a b in a given file, the other has x y in the same file. The merge gives an conflict which we resolves:
We work a little on the top branch to add c in the file. We then discover that the merge was bad. We back it out:
The new red changeset is the backout, it has removed teh Y and Z lines from the bad merge, but it kept the new c line that was added after the merge.
We make some more changes on the bottom branch and merge it again:
The idea is that the new merge changeset should contain the x y z lines along with the a b c lines.
However, what happens is that the merge back out the x and y lines! The greatest common ancestor of the a b c changeset and the x y z changeset is the green changeset with x y.
The file thus looks the same in y and z. In e, the file looks like it did in b because of the backout. In the merge between e and z, the merge algorithm concludes that the file was edited in changeset e and so the merge result is the file as it looks in e. The changes made in x are silently discarded!