Re: git merge --abort

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



John Tapsell <johnflux@xxxxxxxxx> 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).
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux