18

Is the most reliable method to go one-by-one, using the backout command for each of many changesets, or is there a way to create one big reversal changeset to cover a whole bunch of [edit: non-contiguous] changesets.

If one-by-one, does order matter? (Should one go last-to-first?)

Does the best method differ if there are merges among different sub-projects along the way?

Does this tend to go smoothly in your experience? :-)

Joshua Goldberg
  • 5,059
  • 2
  • 34
  • 39
  • I see different possible commands that might be relevant. diff -r can take multiple revisions, but I'm confused about the exact semantics. export -r takes multiple changesets. backout takes a single one. I'm not sure about the pros/cons of each – Joshua Goldberg May 21 '12 at 20:43
  • export doesn't have a --reverse flag, my mistake there. – Joshua Goldberg May 21 '12 at 20:52

5 Answers5

9

If you have no merges along the way, you can either back out every individual change (in reverse order), or, if there are many of them, do it with one big inverse patch.

If you have good changesets atop the ones you need to back out, better commit the inverse patch on top of the most recent bad changeset, then rebasing them onto the tip of the branch.

1 -- 2 -- A -- B -- C -- 3 -- 4
                     \
                      C'B'A'

$ hg up C
$ hg diff -r C:2 > backout.diff
$ hg import --no-commit backout.diff
$ hg ci -m "Backout A, B, C"
$ hg up 4
$ hg rebase -s C'B'A -d .

There will be problems if you want to back out merge changesets, see this wiki page for more information.

In such a case, if possible, consider re-doing the branch and stripping the old lineage. Otherwise, you might have to abandon the branch altogether, salvaging the good changesets via graft or transplant.

Helgi
  • 5,428
  • 1
  • 31
  • 48
  • Does this work with non-contiguous changesets? I'm getting the impression it won't, from the lack of clear support on http://stackoverflow.com/questions/5435567/mercurial-diff-multiple-changesets-at-same-time I.e., what will happen if I say `hg diff -r 'keyword(xyz)'` where the keyword selects a bunch of non-contiguous changesets relating to project xyz? – Joshua Goldberg May 21 '12 at 21:06
  • @JoshuaGoldberg: No, it will spit out individual diffs. However, you can do something like `hg diff -r "100:90" -I src/subdir` to narrow down your diff to a set of files or dirs. You can specify several `-I`s or `-X` (exclude). – Helgi May 21 '12 at 21:17
7

There is --collapse option for rebase.

Helgi's answer can be upgraded into:

1 -- A -- 2 -- B -- 3 -- C -- 4 -- 5
                          \
                           C' -- B' -- A'

$ hg update --clean C
$ hg backout --rev C --message "Backed out changeset: C"
$ hg backout --rev B
$ hg commit --message "Backed out changeset: B"
$ hg backout --rev A
$ hg commit --message "Backed out changeset: A"
$ hg rebase --collapse --source C' --dest 5
$ hg commit --message "Backed out C, B, A"

which will result in the following

1 -- A -- 2 -- B -- 3 -- C -- 4 -- 5 -- C'B'A'

However, backing out in separate branch may result in [logical] conflict in the subsequent merge.

1 -- A -- 2 -- B -- 3 -- X  --  4 
                     \           \
                      B' -- A' -- M

if X depends on A or B, then M will have conflict (at least logical conflict).

Community
  • 1
  • 1
Afriza N. Arief
  • 7,696
  • 5
  • 47
  • 74
  • 1
    This is great, not just for --collapse. Using the separate backouts (last-to-first) allows for non-contiguous changesets. Since this is likely to be done in a complex situation, I think I would favor a simple merge rather than rebaseing. The branch with the reverse changesets could be either collapsed (with any of various methods) or not. One more caveat to repeat from Helgi's answer, is that you cannot use backout if there are any merges to reverse. ["The only safe way to deal with a bad merge is to abandon the branch."](http://mercurial.selenic.com/wiki/Backout#Backout_of_a_Merge_Changeset) – Joshua Goldberg Sep 10 '14 at 15:52
  • I should add, `diff --reverse`, as used in my own answer, is also not good for merges. Given that neither will work with merges, I think backout/commit is much more convenient than my solution. – Joshua Goldberg Sep 10 '14 at 16:09
  • @JoshuaGoldberg the consequence of simply merging the individual backouts is that, as you mentioned, you cannot `backout` your backout merge.. so for me, it is preferable to `rebase` them.. – Afriza N. Arief Sep 12 '14 at 02:26
4

What I came up with is inelegant, but got the job done, despite that the changes I needed to back out were interspersed with other work and had some internal branching. Here's what I did. (Comments and improvements are welcome.)

Got a list of all of the changesets (which I then used to generate the commands below):

hg log -r 'keyword(xyz)' --template '{rev}\n'

Generated a patch for each changeset:

hg diff -p -U 8 --reverse -c 15094 > 15094.rev.patch
hg diff -p -U 8 --reverse -c 15095 > 15095.rev.patch
...

Then, applied each reverse patch. Here the order matters, last-to-first:

hg import -m "reversing changeset 15302" 15302.rev.patch
hg import -m "reversing changeset 15292" 15292.rev.patch
...

This process was interrupted several times for merges that didn't go through automatically, where I had to manually apply changes to a file from its .rej file and then manually commit, before picking up the imports where it had left off.

Finally (in another clone... did I mention I did this all in a clone?) I compressed the whole set of reverse changesets into one changeset using hg histedit -o and its fold command.

Now I've got a single changeset that I should be able to reverse and apply if I decide to put the work back in at a later date (Although if I cross that bridge, I might apply the "forward" patches piecemeal again in order to get better blame/annotate information)

Joshua Goldberg
  • 5,059
  • 2
  • 34
  • 39
  • From `help diff`: "diff may generate unexpected results for merges, ..." It only gives the diff to one parent, so like backout, this process is not good for merges. – Joshua Goldberg Sep 10 '14 at 16:07
1

This is how you can do it with TortoiseHg. Of course you can do the same with the command line.

Given this history, where you wan't to get rid of changeset A, B and C:

1 -- 2 -- A -- B -- C -- 3 -- 4

First update to revision 2.

Then rebase the first of any later revisions you wan't to keep - in this case revision 3.

Your history now looks like this:

1 -- 2 -- A -- B -- C
      \
       3 -- 4

Now update to revison 4.

And finally use "Merge with local" to merge revision C onto revision 4.

At this point it is crucial that you select the option "Discard all changes from merge target (other) revision".

The description may not be the most logical, but it means that you merge the old tip C back to the default branch - but without the changesets A, B and C.

The result is:

1 -- 2 -- A -- B -- C --
      \            /
       3    --    4

Commit and you're done.

pberggreen
  • 928
  • 6
  • 13
  • This is helpful, but in a lot of cases this tactic wouldn't be available --- if the changes have already been pushed. Rebase isn't an option in that case. Useful for local changes, though, thanks for the idea. – Joshua Goldberg Feb 09 '16 at 17:11
  • Yeah, you're right: It won't work after the changes have been pushed. – pberggreen Aug 29 '16 at 11:26
0

If you don't want the "backout" changesets in your history, you could also do something else:
Make a clone of your repository, but only up to the last changeset that you don't want to get rid of.

See Mercurial: Fix a borked history for an example how to do this.

If your repository was a local one, that's all you have to do.
But if the bad changesets were already pushed to a central repository, you'd need server access to delete the repository there and replace it by your clone.
Plus, if someone else already pulled from the repo with the bad changesets, they need to delete and re-clone (otherwise the bad changesets are in the central repo again as soon as one of the other people pushes again).
So it depends on the circumstances whether this solution is a good one for you...

Community
  • 1
  • 1
Christian Specht
  • 35,843
  • 15
  • 128
  • 182