Subject: Directory renames (was Re: blame follows renames, but log doesn't)

From: Steven Grimm <[email-masked]>
Date: Tue, 19 Jun 2007 11:28:28 -0700

Directory renames can break in more cases than just adding new files. 
Here's a demonstration based on a situation I ran into a few minutes ago 
on a real-world project.

$ git init
$ mkdir a
$ echo some contents > a/file1
$ echo other contents > a/file2
$ git add a
$ git commit -m "initial commit"

So far so good. Now there's a revision where the files happen to be 
identical. (On a new branch for later convenience.)

$ git checkout -b modifybranch
$ echo other contents > a/file1
$ git commit -a -m "commit that makes both files identical"

Now we rename the directory. (Again, the new branch will be used later.)

$ git checkout -b renamebranch
$ git mv a b
$ git commit -m "rename directory"

Make a change to file2.

$ echo more contents from renamebranch >> b/file2
$ git commit -a -m "add contents to file2"

Where did file2's contents come from?

$ git blame b/file2
a7dbcfdc a/file1 (Steven Grimm 2007-06-19 10:36:20 -0700 1) other contents
69a87194 b/file2 (Steven Grimm 2007-06-19 10:43:29 -0700 2) more contents

Which is wrong. The history of file2 has nothing to do with the history 
of file1, but git blame thinks it does. However, that's not what blew up 
on me in my real-world test; it gets better. Let's say one of the files 
changed in a different branch.

$ git checkout master
$ echo a change to file2 in master >> a/file2
$ git commit -a -m "file2 changed"

$ git merge renamebranch
Removed a/file1
Merge made by recursive.
 a/file1            |    1 -
 a/file2 => b/file1 |    0
 b/file2            |    2 ++
 3 files changed, 2 insertions(+), 1 deletions(-)
 delete mode 100644 a/file1
 rename a/file2 => b/file1 (100%)
 create mode 100644 b/file2

And this is just completely broken. What it *should* do is give me 
b/file1 with "other contents" from renamebranch, and b/file2 with a 
merge conflict since I added a different line to it in each branch. 
Instead, the merge succeeds with no conflict and applies the change in 
my current branch to the wrong file:

$ cat b/file1
other contents
a change to file2 in master
$ cat b/file2
other contents
more contents from renamebranch

There's one more variation on this theme that's broken in a similar but 
not identical way (this didn't happen in my real-world scenario but I 
ran into it while coming up with the above test case):

$ git checkout modifybranch
$ echo a change to file2 in modifybranch >> a/file2
$ git commit -a -m "a change in modifybranch"
$ git merge renamebranch
CONFLICT (delete/modify): a/file2 deleted in renamebranch and modified 
in HEAD. Version HEAD of a/file2 left in tree.
Automatic merge failed; fix conflicts and then commit the result.

This should definitely be a merge conflict, but it shouldn't be *this* 
merge conflict. What I'd expect here would be b/file1 == "other 
contents" and b/file2 with conflict markers.

$ ls a b
a:
file2

b:
file1   file2
$ cat a/file2
other contents
a change to file2 in modifybranch
$ cat b/file1
other contents
$ cat b/file2
other contents
more contents from renamebranch

In other words, no conflict markers at all, and I still have the old 
directory "a" with one of its two files in original form, but not the 
other file.

Hope that's illuminating or at least interesting to someone.

-Steve