Directory/file conflicts are more difficult than they need to be for users to resolve (and to un-resolve). Simplify that process by doing to the index what we do to the working tree: renaming the file in the directory/file conflict to a different location. This also avoids leaving cruft untracked files around if the user decides to abort the merge. >From one angle this proposal might appear surprising, so extended rationale for this change can be found below (and if it seems surprising, some of the below may need to be moved into the commit message for the patch). == What git does, prior to this series == Let's say there's a directory/file conflict for path 'foo'. One might see git report: CONFLICT (file/directory): There is a directory with name foo in BRANCH2. Adding foo as foo~BRANCH Further, at this point, git will record: * foo~BRANCH in the working tree * foo/ in the working tree * foo at higher order stage in the index * foo/* entries found in the index (at stage 0) == User experience resolving directory/file conflicts == Let's say the user wants to resolve by just moving 'foo' (the file) to somewhere else. Here's five different things a user might try at this point (not sequentially, but rather competing ideas they might try), along with commentary about how each fails to resolve the conflict: $ git mv foo other # Moves the directory instead, oops $ mv foo~BRANCH other $ git add other # Still leaves 'foo' conflicted in the index $ git mv foo~BRANCH other # Error: "Not under source control" $ git add -u # Removes conflict entry for 'foo' from index, but doesn't add # new one and leaves foo~BRANCH around untracked $ git rm foo # Doesn't work ("foo: needs merge...fatal: git rm: 'foo': Is a directory) == User experience un-resolving directory/file conflict == If the user decides they don't like the merge and run 'git merge --abort', the abort fails due to a separate bug being fixed here: https://public-inbox.org/git/20180713163331.22446-1-newren@xxxxxxxxx/ However, even once the fixes there are part of git, a 'git merge --abort' will leave behind new untracked files that were created by the merge attempt (in the case above, one named foo~BRANCH). This is suboptimal. == Correct solution; old and new == Currently, this is what a user needs to run to resolve this conflict: $ mv foo~BRANCH other $ git add other $ git rm --cached foo If git would record foo~BRANCH at a higher stage in the index instead of recording foo there, then we could shorten this to: $ git add foo~BRANCH $ git mv foo~BRANCH other If we could also teach git-mv to quit reporting "not under version control" for index entries with higher order stages (and instead rename them while keeping them as higher order stages), then we could also allow those two commands to be reversed: $ git mv foo~BRANCH other $ git add other While this change to what git records in the index might feel like a lie, it does make it easier for the user and we already have a precedent for treating user convience as more important than trying to represent how we got to the current state, as shown in the following analogy: == Rename analogy == If one side of history renames A->B but there are content conflicts, one choice for the contents for the index would be: <mode> <original sha> 1 A <mode> <side-one sha> 2 A <mode> <side-two sha> 3 B However, that's not what git does. In particular, this choice would require the user to run both 'git add B' and 'git rm --cached A' to resolve the conflict. Further, it prevents the user from running commands such as git checkout [--ours|--theirs|--conflict|-m] B git diff [--ours|--theirs|--base] B This would also make it harder to pair up entries from 'git ls-files -u' (especially if there are many entries between A and B), since nothing marks them as related anymore. So, instead, git records the following in the index: <mode> <original sha> 1 B <mode> <side-one sha> 2 B <mode> <side-two sha> 3 B This might seem like a lie if you view the index as a place to record how we got to the current state rather than a way to help users resolve conflicts and update state, but it is certainly far more convenient for the user to work with. Follow suit with directory/file conflicts. Elijah Newren (1): merge-recursive: make file/directory conflicts easier to resolve merge-recursive.c | 38 ++++++++++++++++++++++------ t/t3030-merge-recursive.sh | 16 ++++++------ t/t6020-merge-df.sh | 4 +-- t/t6022-merge-rename.sh | 4 +-- t/t6036-recursive-corner-cases.sh | 5 ++-- t/t6042-merge-rename-corner-cases.sh | 4 +-- t/t6043-merge-rename-directories.sh | 4 +-- 7 files changed, 49 insertions(+), 26 deletions(-) -- 2.18.0.550.g44d6daf40a.dirty