Ability to unmerge or revert a merge sensibly

Bug #152008 reported by Christopher Armstrong
42
This bug affects 6 people
Affects Status Importance Assigned to Milestone
Bazaar
Confirmed
Wishlist
Unassigned

Bug Description

This is a pretty important use case for Twisted, for example. We occasionally (enough that a very strange workflow would get really annoying) have situations where a branch has been reviewed and merged but we later decide needs to be reverted. The workflow for this situation is very bad, because if you revert the change (with a "bzr merge -r branchrev..branchrev-1"), it (rightly) doesn't get rid of the revision that included the branch tip, which (annoyingly) later causes attempts to merge the same branch be a no-op (or if more revisions are added, other Bad Things).

Right now the workaround is to do a series of arcane hacks to get the branch back into a working state, and AFAICT it leaves the branch in a state where history is lost or at least dissociated.

From what I hear the proper implementation a better system would be dependent on "cherry picking" support. I don't know anything about this, but here are a couple ideas for a UI:

1. Just what I did above, with a revert of the merge revision, except without having to "fix" the reverted branch so it can later be merged. Would cherry picking support automatically make this work?

2. Add a "bzr unmerge <other-branch>" command which reverts all changes from <other-branch> that were made to the current branch, and does whatever necessary to make it so <other-branch> is still mergable later.

Revision history for this message
John A Meinel (jameinel) wrote :

At the moment, this isn't representable in our data model. The problem is that history is immutable, and you have a revision which says it includes everything from the other branch.
In our current model, if "X" is in your history, than all ancestors of "X" are also included. This is vastly simplifies the work you have to do to find common ancestors between two branches. (Without it, you always have to search all of history to see if one of the ancestors was selectively not merged).

To further clarify, you aren't trying to unmerge the last commit, but instead one several commits ago, correct? The only way to do that (at the moment) is to uncommit back to just before the merge, and then recreate all the other commits (skipping the merge one). Which is something Jelmer's "rebase" plugin should be able to help with.

Or you could do something like:

cd existing
bzr branch -r -10 ../before_merge
cd ../before_merge
bzr merge -r -10..-1 ../existing # This "skips over" the merge revision, and cherry picks the rest
bzr commit -m "Cherry pick the changes since the merge"
cd ../existing
bzr pull --overwrite ../before_merge

This will lose the identities of the individual commits since the point you want to extract. Which is where "rebase" would actually try to merge and commit them one by one. So they would have new identities, but the textual changes and commit messages should be preserved.

You are actually looking for more of a "cherry-unpick" function (than a "cherry-pick"), and is certainly a use case we need to think carefully about.

Changed in bzr:
importance: Undecided → Wishlist
status: New → Triaged
Revision history for this message
John A Meinel (jameinel) wrote :

I did add a bit of discussion to:
http://bazaar-vcs.org/Specs/RecordCherryPick

Since I didn't know of a better place to put it.

I also realized something else, though. Which is that you can do:

cd mainline
bzr merge ../feature
bzr commit -m "merging feature" # revno 10
<hack hack, commit commit>
<oops, we don't want 'feature'>
bzr merge -r 10..9
bzr commit -m "removing feature" # revno 15
cd ../feature
bzr merge ../mainline -r 14
bzr commit -m "Getting into sync with mainline"
bzr merge ../mainline
bzr revert . # Remove the 'remove feature' changes
bzr commit -m "Override the feature removal"

This is a bit... difficult, but it records what you want.
The mainline still considers that it has reverted the feature, but the feature branch now has a revision which restores everything. It should even do the right thing for annotations (leaving them at the line that originally modified them, not the new merge/revert revision).

It is certainly possible that we could write a plugin to help automate this, as it is all pretty systematic. You could even do something like:

bzr unmerge ../feature

And have it do all the steps to both '.' and to '../feature', having it detect where in '.' feature was merged, revert those changes, etc.

The biggest problem is probably that you have to do the "bzr merge ../mainline -r 14". And bring the feature branch to the tip of mainline. Which could be problematic.

The other alternative was to rebase, which would look something like:

cd project
mv mainline old_mainline
bzr branch old_mainline mainline -r 9
bzr rebase ../mainline -r 10..-1

This creates new commits in mainline, which means that if someone is mirroring your branch, they need to "pull --overwrite" as the history has now diverged. This is a bit "cleaner" overall, but it does mean going back and hiding history, rather than annotating it to indicate that you want to do something different.

Revision history for this message
Alexander Belchenko (bialix) wrote : Re: [Bug 152008] Re: Ability to unmerge or revert a merge sensibly

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

John A Meinel пишет:
> I did add a bit of discussion to:
> http://bazaar-vcs.org/Specs/RecordCherryPick
>
> Since I didn't know of a better place to put it.
>
> I also realized something else, though. Which is that you can do:
>
> cd mainline
> bzr merge ../feature
> bzr commit -m "merging feature" # revno 10
> <hack hack, commit commit>
> <oops, we don't want 'feature'>
> bzr merge -r 10..9
> bzr commit -m "removing feature" # revno 15
> cd ../feature
> bzr merge ../mainline -r 14
> bzr commit -m "Getting into sync with mainline"

> bzr merge ../mainline
> bzr revert . # Remove the 'remove feature' changes

I'm not sure about last 2 commands. AFAIK, revert will remove pending merge,
so next commit will be pointless. Martin did some improvement in this
area to simply forget pending merges, but not changes. Something
like
bzr revert --forget-merge

> bzr commit -m "Override the feature removal"
>
>
> This is a bit... difficult, but it records what you want.
> The mainline still considers that it has reverted the feature, but the feature branch now has a revision which restores everything. It should even do the right thing for annotations (leaving them at the line that originally modified them, not the new merge/revert revision).
>
> It is certainly possible that we could write a plugin to help automate
> this, as it is all pretty systematic. You could even do something like:
>
> bzr unmerge ../feature
>
> And have it do all the steps to both '.' and to '../feature', having it
> detect where in '.' feature was merged, revert those changes, etc.
>
> The biggest problem is probably that you have to do the "bzr merge
> ../mainline -r 14". And bring the feature branch to the tip of mainline.
> Which could be problematic.
>
> The other alternative was to rebase, which would look something like:
>
> cd project
> mv mainline old_mainline
> bzr branch old_mainline mainline -r 9
> bzr rebase ../mainline -r 10..-1
>
> This creates new commits in mainline, which means that if someone is
> mirroring your branch, they need to "pull --overwrite" as the history
> has now diverged. This is a bit "cleaner" overall, but it does mean
> going back and hiding history, rather than annotating it to indicate
> that you want to do something different.
>

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHD8iDzYr338mxwCURAvZqAJsHwetsOWoQKmggCJ5Yzq5maFHykACfRKmh
WFDGXrWFM5EkM1GvGnApQR8=
=FxLv
-----END PGP SIGNATURE-----

Revision history for this message
Christopher Armstrong (radix) wrote :

Alexander:

Actually, the "bzr revert ." reverts all file changes but *keeps* the merge information. That's the whole point of the trick.

Revision history for this message
John A Meinel (jameinel) wrote :

Actually, you have it backwards.

"bzr revert ." reverts the changes to the tree *without* reverting the pending merges.

"bzr revert --forget-merge" forgets the pending merges without changing the tree.

What you *want* is to explicitly reject that the mainline has rejected your changes. Which is what "bzr merge; bzr revert ." does. It sets a pending merge, and then reverts the changes introduced by that merge.

Revision history for this message
Aaron Bentley (abentley) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Alexander Belchenko wrote:
>> bzr merge ../mainline
>> bzr revert . # Remove the 'remove feature' changes
>
> I'm not sure about last 2 commands. AFAIK, revert will remove pending merge,

No, not "revert .". Revert with no arguments will clear pending merges,
but if filenames are specified, revert will not clear pending merges.

Aaron
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHD8sP0F+nu1YWqI0RAq63AJ9XiZZqXYUnsW6Z+K68ITPHTN9PFwCeO4qx
1REbdae8wio+oAgICOL9oh4=
=bY46
-----END PGP SIGNATURE-----

Revision history for this message
Alexander Belchenko (bialix) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Christopher Armstrong пишет:
> Alexander:
>
> Actually, the "bzr revert ." reverts all file changes but *keeps* the
> merge information. That's the whole point of the trick.

Sorry then.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHD8zzzYr338mxwCURAhdnAJ4pPKBaya53b/dX/TXLKu/ziEZEewCfaeTl
RWiiWT2WUGA+JRDtKmMac9Q=
=wfvQ
-----END PGP SIGNATURE-----

Revision history for this message
Martin Pool (mbp) wrote : [merge] document revert . and revert --forget-merges

On 10/13/07, Alexander Belchenko <email address hidden> wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Christopher Armstrong пишет:
> > Alexander:
> >
> > Actually, the "bzr revert ." reverts all file changes but *keeps* the
> > merge information. That's the whole point of the trick.

bzrlib/builtins.py'
--- bzrlib/builtins.py 2007-10-02 08:21:43 +0000
+++ bzrlib/builtins.py 2007-10-15 01:56:02 +0000
@@ -3033,6 +3033,14 @@
     Any files that have been newly added since that revision will be deleted,
     with a backup kept if appropriate. Directories containing unknown files
     will not be deleted.
+
+ The working tree contains a list of pending merged revisions, which will
+ be included as parents in the next commit. Normally, revert clears that
+ list as well as reverting the files. If any files, are specified, revert
+ leaves the pending merge list alnone and reverts only the files. Use "bzr
+ revert ." in the tree root to revert all files but keep the merge record,
+ and "bzr revert --forget-merges" to clear the pending merge list without
+ reverting any files.
     """

Would that make it clearer? Is it too verbose?

--
Martin

Revision history for this message
Robert Collins (lifeless) wrote :

fixed in 0.92

Changed in bzr:
assignee: nobody → mbp
milestone: none → 0.92
status: Triaged → Fix Released
Revision history for this message
Robert Collins (lifeless) wrote :

I was on crack; misread this as reverting a pending merge.

Changed in bzr:
assignee: mbp → nobody
milestone: 0.92 → none
status: Fix Released → Triaged
Revision history for this message
James Westby (james-w) wrote : Re: [Bug 152008] [merge] document revert . and revert --forget-merges

On (15/10/07 01:56), Martin Pool wrote:
> bzrlib/builtins.py'
> --- bzrlib/builtins.py 2007-10-02 08:21:43 +0000
> +++ bzrlib/builtins.py 2007-10-15 01:56:02 +0000
> @@ -3033,6 +3033,14 @@
> Any files that have been newly added since that revision will be deleted,
> with a backup kept if appropriate. Directories containing unknown files
> will not be deleted.
> +
> + The working tree contains a list of pending merged revisions, which will
> + be included as parents in the next commit. Normally, revert clears that
> + list as well as reverting the files. If any files, are specified, revert
> + leaves the pending merge list alnone and reverts only the files. Use "bzr
> + revert ." in the tree root to revert all files but keep the merge record,
> + and "bzr revert --forget-merges" to clear the pending merge list without
> + reverting any files.
> """
>
> Would that make it clearer? Is it too verbose?
>

Yes, I think that is good text to have in the help.

--
  James Westby -- GPG Key ID: B577FE13 -- http://jameswestby.net/
  seccure key - (3+)k7|M*edCX/.A:n*N!>|&7U.L#9E)Tu)T0>AM - secp256r1/nistp256

Revision history for this message
Christopher Armstrong (radix) wrote :

I came across an interesting feature of monotone, and I think it may be the kind of operation that I would like:

"mtn disapprove"

"""
This command records a disapproval of the changes between id's ancestor and id. It does this by committing the inverse changes as a new revision descending from id. The new revision will show up as a new head and thus a subsequent merge will incorporate the inverse of the disapproved changes in the other head(s).

Conceptually, disapproves contract is that disapprove(A) gives a revision B such that whenever B is merged with a descendant D of A the merge will result in what D “would have looked like” if A had never happened.

Note that as a consequence of this contract the disapprove command only works if id has exactly one ancestor, since it hasn't been worked out how to generate such a descendant in the multi-ancestor case.
"""

From http://monotone.ca/docs/Tree.html

I can't tell for sure, but it looks like this is the kind of operation that I need in bzr. I don't know monotone's model, but it may be interesting to look at how they implement it.

Revision history for this message
John A Meinel (jameinel) wrote :

Reading closely it looks a whole lot more like it is:

bzr merge -r 10..9; bzr commit -m "revert 10"

Specifically the line:
  disapprove command only works if id has exactly one ancestor,

Sounds like it doesn't work for merges.

Revision history for this message
Christopher Armstrong (radix) wrote :

Is the bazaar team interested in fixing this at all? We're making baby-steps towards bazaar usage in the Twisted project, and the ability to revert merges (and then remerge) is something we need.

Revision history for this message
Andrew Bennetts (spiv) wrote :

We are interested in fixing this, although I don't think anyone is currently working on it.

A fairly cheap improvement someone could try would be making a plugin to automate the recipe John gives above. If the plugin works out well it might be something we could merge into the core.

Revision history for this message
Jonathan Lange (jml) wrote : Re: [Bug 152008] Re: Ability to unmerge or revert a merge sensibly

On Wed, Mar 11, 2009 at 12:04 PM, Andrew Bennetts
<email address hidden> wrote:
> We are interested in fixing this, although I don't think anyone is
> currently working on it.
>
> A fairly cheap improvement someone could try would be making a plugin to
> automate the recipe John gives above.  If the plugin works out well it
> might be something we could merge into the core.

"Someone"!

Revision history for this message
Andrew Bennetts (spiv) wrote :

Jonathan Lange wrote:
> "Someone"!

Thanks for volunteering ;)

Martin Pool (mbp)
Changed in bzr:
status: Triaged → Confirmed
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

I have the same exact problem. It was surprising to me to get a "Nothing to do." in the last merge of this sequence, as the code is obviously different and there are tons of things to do:

$ cd staging
$ bzr merge ../trunk
(...)
r1516

# oops, that was a mistake, too soon, let me revert
$ bzr revert -r 1515; bzr commit -m "revert -r 1515"

# days later, now we can merge
$ bzr merge ../trunk
Nothing to do.

The same happens if instead of a "bzr revert" I do "bzr merge -r 1516..1515 .".

Jelmer Vernooij (jelmer)
tags: added: check-for-breezy
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.