threads / discuss / 17897

git merge --abort

Subject: git merge --abort

## tl;dr

18 messages between Feb 19, 2009 and Feb 24, 2009.

replies: 17people: 5as markdown or json

John Tapsell· Feb 19, 2009, 10:05 UTC · lore
Hi,
 I hope you don't mind me generating lots of noise here..
  It's not obvious how to abort a merge between two trees.  Would
aliasing  "git merge --abort"  to "git reset --hard"  be sensible?  Is
there a better way to abort a merge?  Especially if you have
uncommitted changes.
  If that idea isn't liked, instead maybe we can add a note to 'git
merge --help' on how to abort.
John
Junio C Hamano· Feb 19, 2009, 10:58 UTC · re: John Tapsell · lore

Re: git merge --abort

John Tapsell <johnflux@gmail.com> writes:
>   It's not obvious how to abort a merge between two trees.  Would
> aliasing  "git merge --abort"  to "git reset --hard"  be sensible?
Not at all.  Especially when you have local changes.

There are two classes of users who would get themselves into a conflicted merge while they have local changes. One is people who know what they are doing (like Linus) run "git pull" while having small set of disposable local changes they do not mind losing, and they know how to recover. The other is recent CVS/SVN migrants who learned (incorrectly) that "git pull" is similar to "cvs update" from wrong sources that say "it is a way to get changes made by other people while you have a half-baked mess that is still not ready to be committed in your work tree" (which is not) [*1*].

"git reset --hard" is the last thing you would want to suggest to the latter class of people.

Immediately after a merge result in conflicts, there should be two kinds of paths that are different from HEAD:

 * The ones you had local modifications before you started the failed
   merge.  They should be left intact after "merge --abort".
 * The ones you did not have local modifications, but merge could not
   automatically resolve.  There may be files with conflicted marker in
   the work tree, or there may be not.

If you had local modifications to a path that would be involved in the merge, the merge *ought to* stop before even touching the index nor the work tree [*2*]. Merge would not start if you had any staged changes in the index. So the recovery strategy for "merge --abort" needs to only worry about the above two cases. The correct implementation would be roughly:

 - If the index does not have any unmerged entries, stop.  The merge did
   not do anything, and there is nothing to abort.
 - For each path that has unmerged entries in the index:
   - If the path does not exist in HEAD, drop the unmerged index entries
     for the path, without touching the work tree (if a file exists there,
     it must have existed as an untracked file before the merge started);
   - If the path does exist in HEAD, discard the unmerged index entries, 
     reset the path in the index from HEAD, and write that out to the work
     tree.
 - For each path whose entry is stage #0 in the index, if it is different
   from HEAD:
   - If it does not exist in HEAD, drop it from the index and remove the
     file from the work tree.  It is a file added by the failed merge and
     couldn't have existed as an untracked file before the merge started.
   - If it does exist in HEAD, reset the path in the index from HEAD, and
     write that out to the work tree.

But the above would be correct *only* immediately after a failed merge. The user could be giving up after having done any random things after a failed merge in an attempt to resolve, and at that point we cannot trust the state of the index nor the work tree. The simplest example to illustrate:

    $ edit goodbye.c ;# without "git add"
    $ git merge other
    Conflict in hello.c
    $ git add goodbye.c
    $ git merge --abort ;# ???

The user's "git add goodbye.c" will make the state of the index unusable for the above outlined algorithm to tell what was changed by the merge and what were already different before the merge.

So in general, even "merge --abort" implemented according to the above outline cannot be sold as "a safe procedure to recover to where you were before you started the last failed merge". There is no such thing, unless you really educate the user not to expect miracle.

If you mistakenly run "git merge" while your index is already unmerged (iow, after a failed merge before you resolved it nor resetted the index), the command aborts without touching the index nor the work tree. If you implement "merge --abort" as outlined above, it will try to abort the previous conflicted merge, not this round which did not do anything, but again, the user could have done any other random things in addition to the attempt to run the second "git merge".

Having said all that, I suspect
	$ git reset --merge HEAD
may do the right thing, if your git already has the option ;-)
[Footnote]

*1* CVS/SVN want to linearize so even if your local changes want to go directy on top of what you checked out, "cvs update" tries to replay your uncommitted changes on top of what comes as the latest from the central server, which could result in conflicts. With git, you do not have to risk losing your local changes that way. Instead, you can commit your local changes and then "git pull" will try to merge. The merge can conflict and leave the same mess as "cvs update" would leave when it tries to replay your uncommitted changes, but a _huge_ difference here is that you get only one chance to resolve that conflict with CVS/SVN (because nothing records your local changes before the "update") and if you screw that up, you are out of luck. With git, you have the local commit that records the changes you did on top of the old tip of the branch, and you can redo the merge.

*2* I say *ought to*, and I am reasonably sure resolve strategy works correctly, but I wouldn't be surprised if recursive strategy which is the default these days still have corner case bugs when the merge involves renames and/or D/F conflicts).

John Tapsell· Feb 19, 2009, 13:34 UTC · re: Junio C Hamano · lore

Re: git merge --abort

2009/2/19 Junio C Hamano <gitster@pobox.com>:
Show 6 quoted lines
> John Tapsell <johnflux@gmail.com> writes:
>
>>   It's not obvious how to abort a merge between two trees.  Would
>> aliasing  "git merge --abort"  to "git reset --hard"  be sensible?
>
> Not at all.  Especially when you have local changes.

Just to confirm that I've understood this - there's currently no way at the moment to 'cancel' an abort. In the example you gave:

Show 5 quoted lines
>    $ edit goodbye.c ;# without "git add"
>    $ git merge other
>    Conflict in hello.c
>    $ git add goodbye.c
>    $ git merge --abort ;# ???
There's no reliable way of getting back to the state before the merge?
Show 45 quoted lines
> The user's "git add goodbye.c" will make the state of the index unusable
> for the above outlined algorithm to tell what was changed by the merge and
> what were already different before the merge.
>
> So in general, even "merge --abort" implemented according to the above
> outline cannot be sold as "a safe procedure to recover to where you were
> before you started the last failed merge".  There is no such thing, unless
> you really educate the user not to expect miracle.
>
> If you mistakenly run "git merge" while your index is already unmerged
> (iow, after a failed merge before you resolved it nor resetted the index),
> the command aborts without touching the index nor the work tree.  If you
> implement "merge --abort" as outlined above, it will try to abort the
> previous conflicted merge, not this round which did not do anything, but
> again, the user could have done any other random things in addition to the
> attempt to run the second "git merge".
>
> Having said all that, I suspect
>
>        $ git reset --merge HEAD
>
> may do the right thing, if your git already has the option ;-)
>
>
> [Footnote]
>
> *1* CVS/SVN want to linearize so even if your local changes want to go
> directy on top of what you checked out, "cvs update" tries to replay your
> uncommitted changes on top of what comes as the latest from the central
> server, which could result in conflicts.  With git, you do not have to
> risk losing your local changes that way.  Instead, you can commit your
> local changes and then "git pull" will try to merge.  The merge can
> conflict and leave the same mess as "cvs update" would leave when it tries
> to replay your uncommitted changes, but a _huge_ difference here is that
> you get only one chance to resolve that conflict with CVS/SVN (because
> nothing records your local changes before the "update") and if you screw
> that up, you are out of luck.  With git, you have the local commit that
> records the changes you did on top of the old tip of the branch, and you
> can redo the merge.
>
> *2* I say *ought to*, and I am reasonably sure resolve strategy works
> correctly, but I wouldn't be surprised if recursive strategy which is the
> default these days still have corner case bugs when the merge involves
> renames and/or D/F conflicts).
>
Jay Soffian· Feb 19, 2009, 20:26 UTC · re: John Tapsell · lore

Re: git merge --abort

On Thu, Feb 19, 2009 at 8:34 AM, John Tapsell <johnflux@gmail.com> wrote:
> There's no reliable way of getting back to the state before the merge?

Sure there is. Commit or stash before you merge, so that your index and working copy are clean.

j.
John Tapsell· Feb 20, 2009, 04:47 UTC · re: Jay Soffian · lore

Re: git merge --abort

2009/2/19 Jay Soffian <jaysoffian@gmail.com>:
Show 5 quoted lines
> On Thu, Feb 19, 2009 at 8:34 AM, John Tapsell <johnflux@gmail.com> wrote:
>> There's no reliable way of getting back to the state before the merge?
>
> Sure there is. Commit or stash before you merge, so that your index
> and working copy are clean.
Could a stash be done automatically by the merge command, for just a case?
John
Junio C Hamano· Feb 20, 2009, 05:24 UTC · re: John Tapsell · lore

Re: git merge --abort

John Tapsell <johnflux@gmail.com> writes:
Show 8 quoted lines
> 2009/2/19 Jay Soffian <jaysoffian@gmail.com>:
>> On Thu, Feb 19, 2009 at 8:34 AM, John Tapsell <johnflux@gmail.com> wrote:
>>> There's no reliable way of getting back to the state before the merge?
>>
>> Sure there is. Commit or stash before you merge, so that your index
>> and working copy are clean.
>
> Could a stash be done automatically by the merge command, for just a case?

It cuts both ways. For people who work on a well organized project (i.e. highly modularized) and tend to keep local changes in the work tree while doing a lot of merges, running "stash" every time would (1) remove the local change from the work tree, which he has to remember to manually unstash after resolving conflicts in the merge (which would not have conflicted with the local change anyway), which is an additional work for no real gain, and (2) clutter his stash. My gut feeling is that it is a change that affects the way the end user has to work that is sufficiently different and disruptive for no real gain.

If you read the original message more carefully, you will notice that the suggested "git merge --abort" would break down *only* if the user messes with the state conflicted merge left. And an unmanageable conflicts are much rare compared to most merges that autoresolve, so you should optimze for the common case while giving a way to gain safety only when needed.

Probably a much better workflow, if we add "merge --abort", would be:
    $ edit ;# unrelated local changes are still here
    $ git pull ;# or merge or whatever
    ... oops, large conflict ...
    ... look and see if it can easily be resolved ...
    ... otherwise
    $ git merge --abort
    $ git stash
    $ git pull ;# or whatever, try again
    ... the same conflict but this time you only need to worry
    ... about the merge itself
    ... resolve, review, test to convince yourself that your
    ... resolution is good and then...
    $ git commit
    $ git stash pop
John Tapsell· Feb 20, 2009, 08:13 UTC · re: Junio C Hamano · lore

Re: git merge --abort

2009/2/20 Junio C Hamano <gitster@pobox.com>: <snip>

Show 6 quoted lines
>    $ edit ;# unrelated local changes are still here
>    $ git pull ;# or merge or whatever
>    ... oops, large conflict ...
>    ... look and see if it can easily be resolved ...
>    ... otherwise
>    $ git merge --abort

Can I just confirm - at this stage, "git merge --abort" would be a "git reset --hard HEAD" ?

So could "git pull/merge" detect if there were local changes, and if there are tell the user something along the lines that they have to either abort the merge, or be unable to abort later?

Show 8 quoted lines
>    $ git stash
>    $ git pull ;# or whatever, try again
>    ... the same conflict but this time you only need to worry
>    ... about the merge itself
>    ... resolve, review, test to convince yourself that your
>    ... resolution is good and then...
>    $ git commit
>    $ git stash pop
Junio C Hamano· Feb 20, 2009, 08:33 UTC · re: John Tapsell · lore

Re: git merge --abort

John Tapsell <johnflux@gmail.com> writes:
Show 11 quoted lines
> 2009/2/20 Junio C Hamano <gitster@pobox.com>:
> <snip>
>>    $ edit ;# unrelated local changes are still here
>>    $ git pull ;# or merge or whatever
>>    ... oops, large conflict ...
>>    ... look and see if it can easily be resolved ...
>>    ... otherwise
>>    $ git merge --abort
>
> Can I just confirm - at this stage, "git merge --abort" would be a
> "git reset --hard HEAD" ?

Not at all. Please re-read my previous message that begins with "Not at all".

John Tapsell· Feb 20, 2009, 08:42 UTC · re: Junio C Hamano · lore

Re: git merge --abort

2009/2/20 Junio C Hamano <gitster@pobox.com>:
Show 15 quoted lines
> John Tapsell <johnflux@gmail.com> writes:
>
>> 2009/2/20 Junio C Hamano <gitster@pobox.com>:
>> <snip>
>>>    $ edit ;# unrelated local changes are still here
>>>    $ git pull ;# or merge or whatever
>>>    ... oops, large conflict ...
>>>    ... look and see if it can easily be resolved ...
>>>    ... otherwise
>>>    $ git merge --abort
>>
>> Can I just confirm - at this stage, "git merge --abort" would be a
>> "git reset --hard HEAD" ?
>
> Not at all.  Please re-read my previous message that begins with "Not at all".

Doh sorry. Yeah it has to be that long algorithm that you outlined. Can it be done with a series of currently existing commands? Can I persuade someone else to implement it ? :-)

John
Bryan Donlan· Feb 21, 2009, 07:28 UTC · re: Junio C Hamano · lore

Re: git merge --abort

On Fri, Feb 20, 2009 at 12:24 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 20 quoted lines
> John Tapsell <johnflux@gmail.com> writes:
>
>> 2009/2/19 Jay Soffian <jaysoffian@gmail.com>:
>>> On Thu, Feb 19, 2009 at 8:34 AM, John Tapsell <johnflux@gmail.com> wrote:
>>>> There's no reliable way of getting back to the state before the merge?
>>>
>>> Sure there is. Commit or stash before you merge, so that your index
>>> and working copy are clean.
>>
>> Could a stash be done automatically by the merge command, for just a case?
>
> It cuts both ways.  For people who work on a well organized project
> (i.e. highly modularized) and tend to keep local changes in the work tree
> while doing a lot of merges, running "stash" every time would (1) remove
> the local change from the work tree, which he has to remember to manually
> unstash after resolving conflicts in the merge (which would not have
> conflicted with the local change anyway), which is an additional work for
> no real gain, and (2) clutter his stash.  My gut feeling is that it is a
> change that affects the way the end user has to work that is sufficiently
> different and disruptive for no real gain.

Perhaps a better approach would be to stash the pre-merge state in the reflog, then? That is, manufacture a pre-merge commit containing all files changed in the working copy, and add it to the reflog prior to performing a merge. git merge --abort can then simply check whether the top reflog entry is a pre-merge state, and if so, reset --hard to it, then reset the index to the parent of our pre-merge commit.

This would also nicely handle the case where the user tries some random things before deciding to abort the merge.

Jakub Narebski· Feb 21, 2009, 08:34 UTC · re: Bryan Donlan · lore

Re: git merge --abort

Bryan Donlan <bdonlan@gmail.com> writes:
Show 31 quoted lines
> On Fri, Feb 20, 2009 at 12:24 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> John Tapsell <johnflux@gmail.com> writes:
>>> 2009/2/19 Jay Soffian <jaysoffian@gmail.com>:
>>>> On Thu, Feb 19, 2009 at 8:34 AM, John Tapsell <johnflux@gmail.com> wrote:
>>>>>
>>>>> There's no reliable way of getting back to the state before the merge?
>>>>
>>>> Sure there is. Commit or stash before you merge, so that your index
>>>> and working copy are clean.
>>>
>>> Could a stash be done automatically by the merge command, for just a case?
>>
>> It cuts both ways.  For people who work on a well organized project
>> (i.e. highly modularized) and tend to keep local changes in the work tree
>> while doing a lot of merges, running "stash" every time would (1) remove
>> the local change from the work tree, which he has to remember to manually
>> unstash after resolving conflicts in the merge (which would not have
>> conflicted with the local change anyway), which is an additional work for
>> no real gain, and (2) clutter his stash.  My gut feeling is that it is a
>> change that affects the way the end user has to work that is sufficiently
>> different and disruptive for no real gain.
> 
> Perhaps a better approach would be to stash the pre-merge state in the
> reflog, then? That is, manufacture a pre-merge commit containing all
> files changed in the working copy, and add it to the reflog prior to
> performing a merge. git merge --abort can then simply check whether
> the top reflog entry is a pre-merge state, and if so, reset --hard to
> it, then reset the index to the parent of our pre-merge commit.
> 
> This would also nicely handle the case where the user tries some
> random things before deciding to abort the merge.

Perhaps this is the case fo "feature that waits for a user", namely 'git stash --no-reset', which would save a state just in case, perhaps in a separate area and not refs/stash (ORIG_STASH perhaps?).

What do you think about this idea?
-- 
Jakub Narebski
Poland
ShadeHawk on #git
Junio C Hamano· Feb 21, 2009, 09:18 UTC · re: Jakub Narebski · lore

Re: git merge --abort

Jakub Narebski <jnareb@gmail.com> writes:
> Perhaps this is the case fo "feature that waits for a user", namely
> 'git stash --no-reset', which would save a state just in case, perhaps
> in a separate area and not refs/stash (ORIG_STASH perhaps?).

Isn't that Nana's "git stash --keep" patch posted a few weeks ago sitting in "pu"?

Jakub Narebski· Feb 21, 2009, 10:18 UTC · re: Junio C Hamano · lore

Re: git merge --abort

On Sat, 21 Feb 2009, Junio C Hamano wrote:
Show 8 quoted lines
> Jakub Narebski <jnareb@gmail.com> writes:
> 
> > Perhaps this is the case fo "feature that waits for a user", namely
> > 'git stash --no-reset', which would save a state just in case, perhaps
> > in a separate area and not refs/stash (ORIG_STASH perhaps?).
> 
> Isn't that Nana's "git stash --keep" patch posted a few weeks ago sitting
> in "pu"?
Almost exactly.

When using it as a safety measure (perhaps enabled via configuration variable, similarly to core.safecrlf or diff.autoRefreshIndex) we would probably want to not save it in 'refs/stash' stack, but in single-use ORIG_STATE (similar to HEAD reflog vs. ORIG_HEAD). And of course have "git merge --abort" (or even "git pull --abort") as a porcelain.

-- 
Jakub Narebski
Poland
John Tapsell· Feb 23, 2009, 12:41 UTC · re: Jakub Narebski · lore

Re: git merge --abort

2009/2/21 Jakub Narebski <jnareb@gmail.com>:
Show 17 quoted lines
> On Sat, 21 Feb 2009, Junio C Hamano wrote:
>> Jakub Narebski <jnareb@gmail.com> writes:
>>
>> > Perhaps this is the case fo "feature that waits for a user", namely
>> > 'git stash --no-reset', which would save a state just in case, perhaps
>> > in a separate area and not refs/stash (ORIG_STASH perhaps?).
>>
>> Isn't that Nana's "git stash --keep" patch posted a few weeks ago sitting
>> in "pu"?
>
> Almost exactly.
>
> When using it as a safety measure (perhaps enabled via configuration
> variable, similarly to core.safecrlf or diff.autoRefreshIndex) we would
> probably want to not save it in 'refs/stash' stack, but in single-use
> ORIG_STATE (similar to HEAD reflog vs. ORIG_HEAD). And of course have
> "git merge --abort" (or even "git pull --abort") as a porcelain.

It sounds like we have some sort of plan then. Will Nana's patch be committed into mainline git? Then we can add the --abort porcelain

Junio C Hamano· Feb 24, 2009, 01:36 UTC · re: John Tapsell · lore

Re: git merge --abort

John Tapsell <johnflux@gmail.com> writes:
> It sounds like we have some sort of plan then.  Will Nana's patch be
> committed into mainline git?  Then we can add the --abort porcelain

I do not know what plan you are talking about, but that's not how the development works. If something is merged to 'pu', and you have a cool feature you would want to take advantage of it, you can build your cool feature on top of that particular topic. If the result looks reasonable they would cook for a while in 'next' for further polishing and then finally go to 'mainline'.

I personally did not think "--keep" would need to be be part of a reasonable "merge --abort" implementation, but I may have missed some description of a viable design discussed on the list.

Jakub Narebski· Feb 24, 2009, 01:53 UTC · re: Junio C Hamano · lore

Re: git merge --abort

Junio C Hamano wrote:
Show 15 quoted lines
> John Tapsell <johnflux@gmail.com> writes:
> 
> > It sounds like we have some sort of plan then.  Will Nana's patch be
> > committed into mainline git?  Then we can add the --abort porcelain
> 
> I do not know what plan you are talking about, but that's not how the
> development works.  If something is merged to 'pu', and you have a cool
> feature you would want to take advantage of it, you can build your cool
> feature on top of that particular topic.  If the result looks reasonable
> they would cook for a while in 'next' for further polishing and then
> finally go to 'mainline'.
> 
> I personally did not think "--keep" would need to be be part of a
> reasonable "merge --abort" implementation, but I may have missed some
> description of a viable design discussed on the list.
My idea was that merge would do the following:
  $ <save stash into MERGE_STASH or similar, no reset>
  $ <do a merge>
Then we have two possibilities:
  # merge failed with conflicts
  $ git merge --abort (would unstash MERGE_STASH and delete it)
  # we created merge conflict
  $ <MERGE_STASH is removed together with MERGE_HEAD>
-- 
Jakub Narebski
Poland
Junio C Hamano· Feb 24, 2009, 02:01 UTC · re: Jakub Narebski · lore

Re: git merge --abort

Jakub Narebski <jnareb@gmail.com> writes:
Show 26 quoted lines
> Junio C Hamano wrote:
>> John Tapsell <johnflux@gmail.com> writes:
>> 
>> > It sounds like we have some sort of plan then.  Will Nana's patch be
>> > committed into mainline git?  Then we can add the --abort porcelain
>> 
>> I do not know what plan you are talking about, but that's not how the
>> development works.  If something is merged to 'pu', and you have a cool
>> feature you would want to take advantage of it, you can build your cool
>> feature on top of that particular topic.  If the result looks reasonable
>> they would cook for a while in 'next' for further polishing and then
>> finally go to 'mainline'.
>> 
>> I personally did not think "--keep" would need to be be part of a
>> reasonable "merge --abort" implementation, but I may have missed some
>> description of a viable design discussed on the list.
>
> My idea was that merge would do the following:
>
>   $ <save stash into MERGE_STASH or similar, no reset>
>   $ <do a merge>
>
> Then we have two possibilities:
>
>   # merge failed with conflicts
>   $ git merge --abort (would unstash MERGE_STASH and delete it)

Here "would unstash" needs to follow something else, namely, make your work tree free of local changes. How? "reset --hard"?

>   # we created merge conflict
>   $ <MERGE_STASH is removed together with MERGE_HEAD>

You mean "created a merge without conflict", right? That part is easy to guess and understand.

In fact, when you run more than one strategies, something similar to this already happens internally. The C version may be harder to follow, but you can check the last scripted version contrib/examples/git-merge.sh and find two functions, savestate/restorestate pair, that does exactly that.

It way predates --keep patch, by the way.
Jakub Narebski· Feb 24, 2009, 09:51 UTC · re: Junio C Hamano · lore

Re: git merge --abort

Junio C Hamano wrote::
> Jakub Narebski <jnareb@gmail.com> writes:
>> Junio C Hamano wrote:
>>> I personally did not think "--keep" would need to be be part of a
>>> reasonable "merge --abort" implementation, but I may have missed some
>>> description of a viable design discussed on the list.

First, a description of state: here we assume that you have changes to tracked files in working area that are neither in HEAD, nor in index, and that you can have changes in index which are neither in HEAD nor in working area.

If HEAD == index == working area then stashing is not necessary.
Show 12 quoted lines
>> My idea was that merge would do the following:
>>
>>   $ <save stash into MERGE_STASH or similar, no reset>
>>   $ <do a merge>
>>
>> Then we have two possibilities:
>>
>>   # merge failed with conflicts
>>   $ git merge --abort (would unstash MERGE_STASH and delete it)
> 
> Here "would unstash" needs to follow something else, namely, make your
> work tree free of local changes.  How?  "reset --hard"?
Yes. "git merge --abort" would be equivalent to
  $ git reset --hard ORIG_HEAD
  $ git stash pop --ref=MERGE_STASH
  $ rm $GIT_DIR/MERGE_STASH
Show 6 quoted lines
> 
>>   # we created merge conflict
>>   $ <MERGE_STASH is removed together with MERGE_HEAD>
> 
> You mean "created a merge without conflict", right?  That part is easy to
> guess and understand.
Yes. I meant here: "created merge _commit_" (not "conflict").
Show 7 quoted lines
> 
> In fact, when you run more than one strategies, something similar to this
> already happens internally.  The C version may be harder to follow, but
> you can check the last scripted version contrib/examples/git-merge.sh and
> find two functions, savestate/restorestate pair, that does exactly that.
> 
> It way predates --keep patch, by the way.

Well, we have "git reset --merge ORIG_HEAD" which from what I understand does at least part of "git merge --abort", but I am not sure if it covers all cases (like dirty index in addition to dirty tree).

-- 
Jakub Narebski
Poland

← back to recent threads