Can you afford to replace the repository with a clone up to A7 and B8?
In a pinch, you can export the repository to a bunch diffs, edit history, and then glue back together just what you want - into a new repository, so no risk of damage. Probably not too bad for your example, but I don't know what the real history looks like.
I referenced this page while performing a simpler operation:
http://strongdynamic.blogspot.com/2007/08/expunging-problem-file-from-mercurial.html
So you want to merge just some changesets from B into A? Backing out changesets like you have been doing is a really bad idea as you have already suffered.
You should either use the transplant extension or have a third branch where you make common changes to merge into both A and B.
I too would love a reasonable solution to this problem. It's caused me no end of trouble when developers inadvertently merge two separate branches and I've had to figure out how to undo the damage, and so far seems to be the biggest achilles heel I've run into with mercurial.
I think I found a solution which permanently fixes the bad merge, and which does not require you to manually check any diffs. The trick is to go back in history and generate commits parallel to the bad merge.
So we have repository with separate branches per maintained version of a single product. Like the situation posed in the question, all changes made on a branch of an earlier version (ie. the bugfixes for in that version) must all eventually be merged to the branches of the later versions.
So specifically, if something is checked in on BRANCH_V8, it must be merged to BRANCH_V9.
Now one of the developers makes following mistake : he merges all changes from BRANCH_V9 into BRANCH_V8 (ie. a merge in the wrong direction). Furthermore, after that bad merge he performs some extra commits before he notices his mistake.
So the situation is as shown in the graphical log below.
o BRANCH_V8 - 13 - important commit right after the bad merge | o BRANCH_V8 - 12 - wrong merge from BRANCH_V9 |\ | o BRANCH_V8 - 11 - adding comment on BRANCH_V8 (ie. last known good state) | | o | BRANCH_V9 - 10 - last commit on BRANCH_V9 | |
We can fix this mistake as follows:
- update your local directory to the last good state of BRANCH_V8:
hg update 11
- Create a new child of that last good state :
- change some file
$EDITOR some/file.txt
(this is necessary because Mercurial does not allow empty commits) - commit these changes
hg commit -m "generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9"
The situation now looks as follows :o BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9 | | o BRANCH_V8 - 13 - important commit right after the bad merge | | | o BRANCH_V8 - 12 - wrong merge from BRANCH_V9 |/| o | BRANCH_V8 - 11 - adding comment on BRANCH_V8 | | | o BRANCH_V9 - 10 - last commit on BRANCH_V9
- change some file
Merge the newly generated head with the revision in which the bad merge happened, and throw away all changes before committing. Do not simply merge the two heads, because you will then lose the important commit which happened after the merge as well!
- merge :
hg merge 12
(ignore any conflicts) - throw away all changes :
hg revert -a --no-backup -r 14
- commit the changes :
hg commit -m "throwing away wrong merge from BRANCH_V9"
The situtation now looks like :o BRANCH_V8 - 15 - throwing away wrong merge from BRANCH_V9 |\ | o BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9 | | +---o BRANCH_V8 - 13 - important commit right after the bad merge | | o | BRANCH_V8 - 12 - wrong merge from BRANCH_V9 |\| | o BRANCH_V8 - 11 - adding comment on BRANCH_V8 | | o | BRANCH_V9 - 10 - last commit on BRANCH_V9 | |
Ie. there are two heads on BRANCH_V8: one which contains the fix of the bad merge, and the other containing the left-over important commit on BRANCH_V8 which happened right after the merge.
- merge :
- Merge the two heads on BRANCH_V8 :
- merge :
hg merge
- commit :
hg commit -m "merged two heads used to revert from bad merge"
- merge :
The situation in the end on BRANCH_V8 is now corrected, and looks like this:
o BRANCH_V8 - 16 - merged two heads used to revert from bad merge |\ | o BRANCH_V8 - 15 - throwing away wrong merge from BRANCH_V9 | |\ | | o BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9 | | | o | | BRANCH_V8 - 13 - important commit right after the bad merge |/ / o | BRANCH_V8 - 12 - wrong merge from BRANCH_V9 |\| | o BRANCH_V8 - 11 - adding comment on BRANCH_V8 | | o | BRANCH_V9 - 10 - last commit on BRANCH_V9 | |
Now the situation on BRANCH_V8 is correct. The only problem remaining is that the next merge from BRANCH_V8 to BRANCH_V9 will be incorrect, as it will merge in the 'fix' for the bad merge as well, which we do not want on BRANCH_V9. The trick here is to merge from BRANCH_V8 to BRANCH_V9 in separate changes :
- First merge, from BRANCH_V8 to BRANCH_V9, the correct changes on BRANCH_V8 from before the bad merge.
- Second merge in the merge mistake and its fix, and, without needing to check anything, throw away all changes
- Thirdly merge in the remaining changes from BRANCH_V8.
In detail:
- Switch your working directory to BRANCH_V9 :
hg update BRANCH_V9
- Merge in the last good state of BRANCH_V8 (ie. the commit you generated to fix the bad merge). This merge is a merge like any regular merge, ie. conflicts should be resolved as usual, and nothing needs to be thrown away.
- merge :
hg merge 14
- commit :
hg commit -m "Merging in last good state of BRANCH_V8"
The situation is now :@ BRANCH_V9 - 17 - Merging in last good state of BRANCH_V8 |\ | | o BRANCH_V8 - 16 - merged two heads used to revert from bad merge | | |\ | +---o BRANCH_V8 - 15 - throwing away wrong merge from BRANCH_V9 | | | | | o | | BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9 | | | | | | o | BRANCH_V8 - 13 - important commit right after the bad merge | | |/ +---o BRANCH_V8 - 12 - wrong merge from BRANCH_V9 | |/ | o BRANCH_V8 - 11 - adding comment on BRANCH_V8 | | o | BRANCH_V9 - 10 - last commit on BRANCH_V9 | |
- merge :
- Merge in the bad merge on BRANCH_V8 + its fix, and throw away all changes :
- merge :
hg merge 15
- revert all changes :
hg revert -a --no-backup -r 17
- commit the merge :
hg commit -m "Merging in bad merge from BRANCH_V8 and its fix and throwing it all away"
Current situation :@ BRANCH_V9 - 18 - Merging in bad merge from BRANCH_V8 and its fix and throwing it all away |\ | o BRANCH_V9 - 17 - Merging in last good state of BRANCH_V8 | |\ +-----o BRANCH_V8 - 16 - merged two heads used to revert from bad merge | | | | o---+ | BRANCH_V8 - 15 - throwing away wrong merge from BRANCH_V9 | | | | | | o | BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9 | | | | +-----o BRANCH_V8 - 13 - important commit right after the bad merge | | | o---+ BRANCH_V8 - 12 - wrong merge from BRANCH_V9 |/ / | o BRANCH_V8 - 11 - adding comment on BRANCH_V8 | | o | BRANCH_V9 - 10 - last commit on BRANCH_V9 | |
- merge :
- Merge in the left-over changes from BRANCH_V8 :
- merge :
hg merge BRANCH_V8
- commit :
hg commit -m "merging changes from BRANCH_V8"
- merge :
In the end the situation looks like this:
@ BRANCH_V9 - 19 - merging changes from BRANCH_V8 |\ | o BRANCH_V9 - 18 - Merging in bad merge from BRANCH_V8 and its fix and throwing it all away | |\ | | o BRANCH_V9 - 17 - Merging in last good state of BRANCH_V8 | | |\ o | | | BRANCH_V8 - 16 - merged two heads used to revert from bad merge |\| | | | o---+ BRANCH_V8 - 15 - throwing away wrong merge from BRANCH_V9 | | | | | | | o BRANCH_V8 - 14 - generating commit on BRANCH_V8 to rectify wrong merge from BRANCH_V9 | | | | o | | | BRANCH_V8 - 13 - important commit right after the bad merge |/ / / o---+ BRANCH_V8 - 12 - wrong merge from BRANCH_V9 |/ / | o BRANCH_V8 - 11 - adding comment on BRANCH_V8 | | o | BRANCH_V9 - 10 - last commit on BRANCH_V9 | |
After all these steps, in which you do not have to check any diff manually, BRANCH_V8 and BRANCH_V9 are correct, and future merges from BRANCH_V8 to BRANCH_V9 will be correct as well.