Git - find name of file at commit

Viewed 57

Say I create a new repository and add the file foo.txt with some text and commit (hash A), and then I rename the file bar.txt and commit (hash B). I then want to see what the name of the file was in A. I am trying the following command

git diff -M --name-status A..B -- bar.txt

where A and B are the two hashes. This is just returning "A bar.txt" - how can I get it to indicate that the file was renamed from foo.txt?

(Note: I understand that git doesn't track renames and uses a similarity index, but similarity in this case should be 100%. Also note that this is a simplified example - for my actual use case I only want to assume that A is an ancestor of B, (EDIT: and cannot assume that the files are 100% similar.))

2 Answers

If the file hasn't changed content, you can use its id to find out. This is not elegant, but it works:

$ git ls-tree HEAD -- build.sbt
100644 blob a154be0bf501840c2bbecf8a570cffb8541f6758    build.sbt
$ git ls-tree -r HEAD~ | grep a154be0bf501840c2bbecf8a570cffb8541f6758
100644 blob a154be0bf501840c2bbecf8a570cffb8541f6758    build.sbt

You can't get the file rename [Edit: from git diff that is; see eftshift0's answer for a method if you're sure of a 100% match] without having Git do a full comparison of the two commits. That is:

git diff -M --name-status A B -- bar.txt

won't work. In theory:

git diff -M --name-status A B -- foo.txt bar.txt

could work, but I don't think it does. (You can try it to see if it does work, but it won't solve your actual problem since you're trying to find the name foo.txt in the real problem case.)

The reason for this is that, as you note, Git doesn't actually track the renames. Instead, it just discovers the rename. To do that it has to enumerate all the left-side (A commit) files, and all the right-side (B commit) files. It then finds that, gosh, on the left, there's a removed foo.txt and on the right there's an added bar.txt. Maybe, just perhaps, those might be "the same" file, whatever that means. At this point Git compares the two files—along with other added and/or removed files—to find out how similar they are and decides on "sameness" by picking those that have the best matches, above whatever the similarity threshold is.1

When you limit the diff to just one file with -- bar.txt, Git doesn't look at the entire left and right sides. It just trims them down to the one file and then does the comparison. That comparison finds "missing from left, exists on right" and thus Git classifies the file as Added.

The easy(ish) way to find the rename you are looking for is:

git diff -M --name-status --diff-filter=R A B

after which you must scan through the output (if doing this programmatically from a capable language, consider adding -z to the diff options) to find one in which the rename is "to desired name".

Note: I've used A B exclusively here instead of A..B as they have the same meaning to git diff and A B is one character shorter. The Git folks are trying to encourage people to move away from the two-dot syntax.


1Git does a first pass to find 100% similar files and remove them from the possible-match-up matrix, because the similarity-index computation is expensive.

Related