{"thread":{"id":"41902","subject":"Merge driver not called for locally modified files?","startedAt":"2016-04-02T14:14:21Z","lastAt":"2016-04-02T16:21:38Z","messageCount":2,"participants":["Gioele Barabucci","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"282513","messageId":"56FFD3BD.1060002@svario.it","threadId":"41902","inReplyTo":null,"subject":"Merge driver not called for locally modified files?","fromName":"Gioele Barabucci","fromEmail":"gioele@svario.it","sentAt":"2016-04-02T14:14:21Z","receivedAt":"2016-04-02T14:14:21Z","isPatch":false,"sender":{"key":"gioele@svario.it","avatar":null},"body":"Hello,\n\nit seems to me that merge drivers are not called while merging commits\nthat touch locally modified (but uncommited) files. Is this correct?\n\nI made a (simple) merge driver for files in the `pw` format. [1] This\ndriver works correctly when a file is modified by multiple commits.\nHowever, if a file has only been modified locally (and not committed),\nthen merging a commit that modifies the same file raises the following\nerror:\n\n    $ git merge --ff-only 78d4f09\n    Updating 5180202..78d4f09\n    error: Your local changes to the following files would\n    be overwritten by merge:\n\t.local/share/pw/passwords\n    Please, commit your changes or stash them before you can merge.\n    Aborting\n\n>From my experiments, it looks like the merge driver is not called at all.\n\nIs it possible to configure git so that the merge driver is called also\nwhile merging locally modified files?\n\nRegards,\n\n[1] https://github.com/gioele/pw/blob/master/bin/git-pw-merge\n\n-- \nGioele Barabucci <gioele@svario.it>\n"},{"id":"282518","messageId":"xmqqwpogf0bx.fsf@gitster.mtv.corp.google.com","threadId":"41902","inReplyTo":"56FFD3BD.1060002@svario.it","subject":"Re: Merge driver not called for locally modified files?","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2016-04-02T16:21:38Z","receivedAt":"2016-04-02T16:21:38Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Gioele Barabucci <gioele@svario.it> writes:\n\n> it seems to me that merge drivers are not called while merging commits\n> that touch locally modified (but uncommited) files. Is this correct?\n\nYes.  \"git merge\" first notices this situation and stops before it\nhas to decide which merge driver to use.\n\nWhen you try to merge commit 'B' when you are at commit 'A', and\nhave some local changes, and these two branches were forked from a\ncommon ancestor 'X', the history may look like this:\n\n                1\n               /\n        X--o--A\n         \\\n          --o--B\n\nwhere '1' is a hypothetical commit that would result if you were to\nmake a commit with all your local changes, i.e. diff(1,A) is your\nuncommitted changes.  As usual, time flows from left to right.\n\nWhen you merge branch 'B' into your history, you would want to end\nup with this history (tentatively ignoring what is in the working\ntree):\n\n        X--o--A--M\n         \\      /\n          --o--B\n\nwhere 'M' is the merge between 'A' and 'B', and the change diff(M,A)\nmust represent what happened between 'X' and 'B' that did not happen\nbetween 'X' and 'A'.  When A and B are independent and without\nconflict, that is roughly the same as diff(B,X), in other words, M\nis roughly the same as patch(A,diff(B,X)).\n\nAs you haven't committed your local changes, diff(1,A) must not\nparticipate in computing the result M of this merge.  After this\nmerge is done, the blob in M is checked out to the working tree, but\ndoing so by overwriting the working tree files would lose your local\nchanges, and that is the reason why you see this error message:\n\n>     error: Your local changes to the following files would\n>     be overwritten by merge:\n> \t.local/share/pw/passwords\n>     Please, commit your changes or stash them before you can merge.\n\nWhat you would want at the very end with is like this:\n\n                1  2\n               /  /\n        X--o--A--M\n         \\      /\n          --o--B\n\nwhere '2' is a hypothetical commit that would result if you were to\ncherry pick '1' on top of 'M', after making 'M' according to\nthediscussion above (i.e. ignoring the local changes you made since\n'A').  But just like you did not have '1' because you were not ready\nto record your changes based on 'A' as a commit, you are not ready\nto actually make this commit '2', so you would want your head to be\nat 'M' and the state of '2' in your working tree, leaving diff(2,M)\nas the local uncommitted change.\n\nHowever.\n\n\"git merge\" does not do the \"create the hypothetical commit '1'\" to\nstore away the local changes, and it does not do the \"cherry pick\n'1' to create the hypothetical commit '2'\" to forward-port the local\nchanges on top of the merge result 'M'.\n\nThis is primarily because there are two distinct steps in the above\nhypothetifcal \"enhanced\" merge.  Creating 'M' may conflict and you\nwould have to resolve it, while \"git merge\" somehow need to remember\nit has to further do the \"cherry pick of '1'\" on the result (but\nthere is no facility to do so in the system).  And after you resolve\nthe conflict to help it create the merge result 'M', it has to\nsomehow remember that it needs to \"cherry-pick --no-commit '1'\", and\nhave the user resolve the conflict.  As the presence of '1' is not\nmade explicit to the user (we do not even create '1'), when the\nlatter step of patch(M,diff(1,A)) fails in conflicts, it is hard for\nthe user to attempt to resolve them starting from scratch, which\nlikely leads to \"I lost the local change\" when in fact it is more\nlike \"I had some local change, but because the merge result was\nvastly different from what I had when I started the local change, I\nwas unable to forward-port them and instead I had to redo it from\nscratch\".  It is not a good user experience.\n\n> Is it possible to configure git so that the merge driver is called also\n> while merging locally modified files?\n\nNo.  But you _can_ do that\n\n                1  2\n               /  /\n        X--o--A--M\n         \\      /\n          --o--B\n\nthing manually, by following the advice you received from the error\nmessage, by creating '1' yourself.\n\n\t$ git merge 78d4f09 ;# should fail\n\n        $ git checkout -b store-local-changes-away\n        $ git commit -a -m 'local changes'\n        $ git checkout @{-1} ;# come back to the original branch\n\t\t\t      # at this point, \"git status\" would report\n\t\t\t      # there is no local changes, hence ...\n\n        $ git merge 78d4f09   ;# ...this should succeed\n\n\t$ git cherry-pick --no-commit store-local-changes-away\n\nThe last step may conflict (this is what I called 'the latter step'\nin the explanation) but at least you have the exact state of '1'\nrecorded and you know what branch (i.e. store-local-changes-away)\ncontains the changes, so you can resolve the conflicts in your\nworking tree without fearing \"git reset --hard\" to clear the slate\nin order to start and retry the conflict resolution from scratch\nlosing your precious local modification.\n\nAnd after you are done, you can\n\n\t$ git branch -D store-local-changes-away\n\nto conclude the whole thing.\n\nYou could simplify this somewhat by using \"git stash save\" before\nyou run the merge and \"git stash pop\" after, but because using real\ncommit and branch to store the local changes away is easier to see\nand understand, that is what the error message you saw suggests.\n"}]}