What's the best way to back out multiple changesets in mercurial? - mercurial

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? :-)

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.

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).

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)

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.

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...

Related

How to copy working directory to a new commit

I have the following commit structure:
a->b->c
Now c is an experiment, and it diverges significantly from b (to the point where any changes to b cause conflicts in c). I'd like to keep c, but want to stop maintaing the chain. Because there are so many conflicts between b and c, simple rebasing would be a bunch of work.
So I want to make
a->b
\->c'
where c' is simply a copy of the workspace in c. In git I would use git reset --soft a and just make a new commit for my c', is there something equivalent I can do in mercurial?
Sounds like you want to use revert --all
pseudo commands (letters a,c mean revids of commits ):
update -r a
revert --all -r c
commit -m "new c"
strip -r c
a guide to using revert all using thg GUI can be found here
So I'm going to post what I ended up doing, but hope I get a better answer.
Make a "do nothing" commit on a (in my case, I added a line). We need the fake because rebasing onto a from b will stop saying nothing to rebase.
a->b->c
\->f
hg rebase -s b -d f --keep clones the whole branch:
a->b->c
\->f->b'->c'
hg histedit, fold c' and drop f.
a->b->c
\->c''
hg prune c
a->b
\->c''

What is the rebase command used in hg pull --rebase

Typically, in HG my workflow is to exclusively use:
hg pull --rebase
If I wanted to run this in two commands, how would I do it?
hg pull
hg rebase <probably with some options?>
What hg pull --rebase does is to indeed first do a hg pull and then hg rebase with default arguments on top of that (you can look at the code in rebase.py in the Mercurial distribution in function pullrebase()), but only if any new revisions were pulled in. If no rebasing is necessary, hg pull --rebase will update to the new branch tip instead. So, hg pull && hg rebase is approximately correct, but doesn't quite capture some corner cases (no new revisions, no rebase necessary).
By default, hg rebase will use the parent of the working directory as the base revision of the rebase and the most recent revision of the current branch (i.e. usually what you just pulled in) as the destination. In short, it's equivalent to hg rebase -b . -d 'last(branch(.))'.
What does "base revision" mean in this context? It means that Mercurial will go and look for the least common ancestor of the base revision and the destination. Then it will rebase everything up to, but not including that least common ancestor on top of the destination. I.e., specifying the base revision allows you to pick pretty much any revision on the branch [1] that you want to rebase and let Mercurial figure out which revisions belong to that branch.
Note that because the rebase is based on the parent of the current working directory, this means that if your current checkout is not what you have been working on, then hg pull --rebase may surprise you by actually trying to rebase a different branch (it will usually fail, because those revisions are generally part of the public phase, but it's something you need to be aware of if you're working with so-called non-publishing repositories and don't use named branches).
[1] Branch in this context refers to an anonymous or topological branch, not a named branch. See hg help glossary for further details.
If you want to rebase by hand (bad idea in common), you have to
Read hg help rebase before
Understand usable for you options of rebase (at least -s and -d)
Use these options
Let's see at toy-repos:
Repo A
A>hg log -T "{rev} {desc}\n"
1 A2
0 A1
with 2 changesets A1 and A2 was cloned to repos B and C (B for pull --rebase A, C for clean pull A)
and two additional changesets was added to A and B+C after clone in order to test your use-case (diverged history)
A>hg log -T "{rev} {desc}\n"
3 A2++
2 A2+
1 A2
0 A1
B>hg log -T "{rev} {desc}\n"
3 B2
2 B1
1 A2
0 A1
C state is identical to B
B>hg pull --rebase
...
B>hg log -T "{rev} {desc}\n" -G
# 5 B2
|
o 4 B1
|
o 3 A2++
|
o 2 A2+
|
o 1 A2
|
o 0 A1
I.e. result of rebased pull is "linear history with local changes on top of remote changes", compared to just pull from C
C>hg log -T "{rev} {desc}\n" -G
# 5 A2++
|
o 4 A2+
|
| o 3 B2
| |
| o 2 B1
|/
o 1 A2
|
o 0 A1
or, in GUI
and in order to get B from C, you have to rebase 2 (-s 2) to new parent 5 (-d 5), but short hg rebase -b 2 will work also and will have the same effect

'Forgetting' a dead-end branch

I've got a mercurial repository. It was on rev A. I made some changes, committed (to rev B), and pushed. However, later, I realised I didn't want to make those changes. I updated back to rev A, and made some alternative changes, to rev C.
C
| -
| B
|/
A
However, now I can't push rev C, because it complains that it would create a new remote head (which it would). How do I make the remote mercurial simply forget about rev B and all the changes therein, so I can push rev C and carry on from there?
Editing History is hard. Once you push a changeset to a public repository, it can no longer be easily pruned from the history.
The most direct solution to your problem is:
hg update <tip of branch you want to forget>
hg commit --close-branch -m "close unwanted branch"
hg update <tip of branch you want to keep>
Push all your changes. As you noted, you will need to use --force since multiple heads on the branch now exist.
If you really need to prune the branch, then read EditingHistory again. If it still seems feasible, you can use one of the methods described in PruningDeadBranches.
Personally I'd close the branch and force the push (as Tim Henigan describes), as it leaves the DAG in a state which is truthful. There is another option though. Doing a dummy merge. This is a merge, but one where you ignore the incoming changes.
hg update C
hg -y merge --tool=internal:fail B
hg revert --all --rev .
hg resolve -a -m
hg ci
The end result is
M
|\
C |
| |
| B
|/
A
... but M doesn't contain any of B's changes.
Use the backout command to reverse the B change.
hg up B
hg backout B
Now, you're working directory is in the same state it was before B, which I'll call A'.
After you backout, make your real change and commit. This is what your history will look like:
C
|
A'
|
B
|
A
Mercurial branches are permanent and registered in commit objects themselves. There are some (not too easy) methods for closing and/or removing branches, mainly listed here. I have even already used some of them before. Note that those are somewhat desperate solutions for people who did use branches thinking they can be temporary.
However, if you really want temporary branches, use bookmarks.
Other people have some good answers for getting rid of 'B', but just to put it out there you can always do:
hg push --rev C
which will push C but not B and not complain about new heads.

Mercurial: Picking commits to be included in a release

I have a local mercurial repository (for now) within which I have already made several commits, each commit is a self contained bug fix. Is it possible to pick which of the bug fixes (commits) I want to be included when it is time to build a release version of my application.
To elaborate, assuming A, B, C, D, and E are commits I have already done to my repository and each of them relates to a bug fix like so:
A <- B <- C <- D <- E <- working dir
I need to be able to for example pick which of the bug fixes will go into the release version (this depends on the time allocated for deployment as well as testing outcomes). So for example I might get a report saying the release should only contain bug fixes A, C and D.
Is it possible to construct a release version containing only the A, C and D commits (Keeping in mind that each commit is self contained and does not depend on the other commits to actually be there)?
Probably having a branch for each bug fix and then merging into a release branch is the easiest way to accomplish this (or is it not?), but the current situation at hand is as described above with no branches.
This isn't the normal work mode of Mercurial (or git). A repository can only contain a changeset if it also contains all of that changeset's ancestors. So you can't get D into a repo without also having A, B, and C in there.
So here's:
What you Should have Done
Control the parentage of your changesets. Don't make C the parent of D just because you happen to have fixed D after C. Before you fix a bug hg update to the previous release.
Imagine A was a release and B, C, and D, were all bug fixes. If you do a loop like this:
foreach bug you have:
hg update A
... fix bug ...
hg commit
hg merge # merges with the "other" head
then you'll end up with a graph like this:
---[A]----[B2]--[C2]--[D2]----
| / / /
+-[B] / /
| / /
+-----[C] /
| /
+---------[D]
and now if you want to create a release with only, say, B and D in it you can do:
hg update B
hg merge D
and that creates a new head that has A + B + D but no C.
Tl;Dr: make a change's parent be as early in history as you can, not whatever happens to be tip at the time.
What you can do Now
That's the ideal, but fortunately it's no big thing. You can never bring exactly D across without bringing C (because C's hash is part of the calculation of D's hash), but you can bring the work that's in D into a new head easily enough. Here are some ways, any of which will work:
hg export / hg import
hg transplant
hg graft (new in 2.0)
hg rebase (only possible if you haven't yet pushed)
Any of those will let you bring that patch/delta that's in D over -- it will have a different hash ID and when some day you merge D in for real (using merge) you'll have duplicate work in two different changesets, but merge will figure it all out.
If this was my tree and it hasn't been pushed anywhere, I'd (assuming an empty patch queue and MQ enabled):
hg qimport -g -r B: # import revisions B and later into mq as "git" style patches
hg qpop -a # unapply them all
hg qpush --move C # Apply changes in C (--move rearranges the order)
hg qpush --move D # Apply changes in D
hg qfin -a # Convert C & D back to changesets
hg push <release server> # Push them out to the release branch
Then you can hg qpush -a; hg qfin -a to get B & E back into changesets.
Final Result:
---A---C---D---B---E
Advantages:
Nobody needs know you didn't do things in this order to start with (evil grin)
You could modify any of the change-sets whilst doing this
Alternatively, with graft in 2.0:
hg update -r A # Goto rev A (no need to do anything special for A)
hg graft C # Graft C on to a new anonymous branch
hg graft D # Graft D
This will give you
---A---B---C---D---E
\
--C'--D' <-You are here
An hg push -r D' should just push the new, cherry-picked, head.
You can then hg merge to get one head again with B and E included.
Advantages:
Non destructive, so true history is kept, and no chance of loss if you muck up
hg tags the new changesets with the hash of the original version, so totally trackable
Probably a little simpler.
While it's somehow strange way and release-policy, you can do it in different form. You have to manipulate with two main objects: changesets and branches
Version 1
You use two branches (default + f.e "release 1.0"). Default branch is mainline of your work - all changesets commited to this branch. At release-time, you branch first needed-for-release changeset into (new) branch, transplant or graft rest of needed in release changesets from default to this branch, head of release 1.0 will be prepared for release this way.
Next release will differ only in new branch name
Version 2
One branch used, MQ extension added. Variations:
all changesets are MQ-pathes and only needed for release are applied to repo
changesets are changesets, only unwanted for release converted to mq-pathes, later qfinish'ed and returned to repo

Mercurial: how can I see only the changes introduced by a merge?

I'm trying to get in the habit of doing code reviews, but merges have been making the process difficult because I don't know how to ask Mercurial to "show only changes introduced by the merge which were not present in either of its parents."
Or, slightly more formally (thanks to Steve Losh):
Show me every hunk in the merge that wasn't present in either of its parents, and show me every hunk present in either of its parents that isn't also present in 3.
For example, assume I have a repository with two files, a and b. If "a" is changed in revision 1, "b" is changed in revision 2 (which is on a separate branch) and these two changes are merged in revision 3, I'll get a history which looks like this:
# changeset: 3
|\ summary: Merged.
| |
| o changeset: 2
| | summary: Changing b
| |
o | changeset: 1
|/ summary: Changing a
|
o changeset: 0
summary: Adding a and b
But if I ask to see the changes introduced by revision 3, hg di -c 3, Mercurial will show me the same thing as if I asked to see the changes introduced in revision 1, hg di -c 1:
$ hg di -c 3
--- a/a
+++ b/a
## -1,1 +1,1 ##
-a
+Change to a
$ hg di -c 1
--- a/a
+++ b/a
## -1,1 +1,1 ##
-a
+Change to a
But, obviously, this isn't very helpful - instead, I would like to be told that no new changes were introduced by revision 3 (or, if there was a conflict during the merge, I would like to see only the resolution to that conflict). Something like:
$ hg di -c 3
$
So, how can I do this?
ps: I know that I can reduce the number of merges in my repository using rebaseā€¦ But that's not my problem - my problem is figuring out what was changed with a merge.
The short answer: you can't do this with any stock Mercurial command.
Running hg diff -c 3 will show you the changes between 3 and its first parent -- i.e. the changeset you were at when you ran hg merge.
This makes sense when you think of branches as more than just simple changesets. When you run hg up 1 && hg merge 2 you're telling Mercurial: "Merge changeset 2 into changeset 1".
It's more obvious if you're using named branches. Say changeset 2 in your example was on a named branch called rewrite-ui. When you run hg update 1 && hg merge rewrite-ui you're effectively saying: "Merge all the changes in the rewrite-ui branch into the current branch." When you later run hg diff -c on this changeset it's showing you everything that was introduced to the default branch (or whatever branch 1 happens to be on) by the merge, which makes sense.
From your question, though, it looks like you're looking for a way to say:
Show me every hunk in this changeset that wasn't present in either of its parents, and show me every hunk present in either of its parents that isn't also present in 3.
This isn't a simple thing to calculate (I'm not even sure I got the description right just now). I can definitely see how it would be useful, though, so if you can define it unambiguously you might convince one of us Mercurial contributors that read SO to implement it.
In order to do code reviews you really want to see just the changes in the project that you are reviewing. To that end we use a new branch for each story and use pull requests to spotlight the changes, having merged all changes into the story branch before creating the pull request. We host our code on bitbucket and the review / pull request tools are really very good, offering a side by side diff.
Pull requests with side-by-side diffs