Re: Merge driver not called for locally modified files?

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

 



Gioele Barabucci <gioele@xxxxxxxxx> writes:

> it seems to me that merge drivers are not called while merging commits
> that touch locally modified (but uncommited) files. Is this correct?

Yes.  "git merge" first notices this situation and stops before it
has to decide which merge driver to use.

When you try to merge commit 'B' when you are at commit 'A', and
have some local changes, and these two branches were forked from a
common ancestor 'X', the history may look like this:

                1
               /
        X--o--A
         \
          --o--B

where '1' is a hypothetical commit that would result if you were to
make a commit with all your local changes, i.e. diff(1,A) is your
uncommitted changes.  As usual, time flows from left to right.

When you merge branch 'B' into your history, you would want to end
up with this history (tentatively ignoring what is in the working
tree):

        X--o--A--M
         \      /
          --o--B

where 'M' is the merge between 'A' and 'B', and the change diff(M,A)
must represent what happened between 'X' and 'B' that did not happen
between 'X' and 'A'.  When A and B are independent and without
conflict, that is roughly the same as diff(B,X), in other words, M
is roughly the same as patch(A,diff(B,X)).

As you haven't committed your local changes, diff(1,A) must not
participate in computing the result M of this merge.  After this
merge is done, the blob in M is checked out to the working tree, but
doing so by overwriting the working tree files would lose your local
changes, and that is the reason why you see this error message:

>     error: Your local changes to the following files would
>     be overwritten by merge:
> 	.local/share/pw/passwords
>     Please, commit your changes or stash them before you can merge.

What you would want at the very end with is like this:

                1  2
               /  /
        X--o--A--M
         \      /
          --o--B

where '2' is a hypothetical commit that would result if you were to
cherry pick '1' on top of 'M', after making 'M' according to
thediscussion above (i.e. ignoring the local changes you made since
'A').  But just like you did not have '1' because you were not ready
to record your changes based on 'A' as a commit, you are not ready
to actually make this commit '2', so you would want your head to be
at 'M' and the state of '2' in your working tree, leaving diff(2,M)
as the local uncommitted change.

However.

"git merge" does not do the "create the hypothetical commit '1'" to
store away the local changes, and it does not do the "cherry pick
'1' to create the hypothetical commit '2'" to forward-port the local
changes on top of the merge result 'M'.

This is primarily because there are two distinct steps in the above
hypothetifcal "enhanced" merge.  Creating 'M' may conflict and you
would have to resolve it, while "git merge" somehow need to remember
it has to further do the "cherry pick of '1'" on the result (but
there is no facility to do so in the system).  And after you resolve
the conflict to help it create the merge result 'M', it has to
somehow remember that it needs to "cherry-pick --no-commit '1'", and
have the user resolve the conflict.  As the presence of '1' is not
made explicit to the user (we do not even create '1'), when the
latter step of patch(M,diff(1,A)) fails in conflicts, it is hard for
the user to attempt to resolve them starting from scratch, which
likely leads to "I lost the local change" when in fact it is more
like "I had some local change, but because the merge result was
vastly different from what I had when I started the local change, I
was unable to forward-port them and instead I had to redo it from
scratch".  It is not a good user experience.

> Is it possible to configure git so that the merge driver is called also
> while merging locally modified files?

No.  But you _can_ do that

                1  2
               /  /
        X--o--A--M
         \      /
          --o--B

thing manually, by following the advice you received from the error
message, by creating '1' yourself.

	$ git merge 78d4f09 ;# should fail

        $ git checkout -b store-local-changes-away
        $ git commit -a -m 'local changes'
        $ git checkout @{-1} ;# come back to the original branch
			      # at this point, "git status" would report
			      # there is no local changes, hence ...

        $ git merge 78d4f09   ;# ...this should succeed

	$ git cherry-pick --no-commit store-local-changes-away

The last step may conflict (this is what I called 'the latter step'
in the explanation) but at least you have the exact state of '1'
recorded and you know what branch (i.e. store-local-changes-away)
contains the changes, so you can resolve the conflicts in your
working tree without fearing "git reset --hard" to clear the slate
in order to start and retry the conflict resolution from scratch
losing your precious local modification.

And after you are done, you can

	$ git branch -D store-local-changes-away

to conclude the whole thing.

You could simplify this somewhat by using "git stash save" before
you run the merge and "git stash pop" after, but because using real
commit and branch to store the local changes away is easier to see
and understand, that is what the error message you saw suggests.
--
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]