When I am moving or renaming a file with git mv
, git shows the move/rename action in the global diff output:
me@myhost:~/test$ git mv foo.txt bar.txt
me@myhost:~/test$ git diff --staged
diff --git a/foo.txt b/bar.txt
similarity index 100%
rename from foo.txt
rename to bar.txt
When I call git diff
on the file, though, it show up as new:
me@myhost:~/test$ git diff --staged bar.txt
diff --git a/bar.txt b/bar.txt
new file mode 100644
index 0000000..fad95f3
--- /dev/null
b/bar.txt
@@ -0,0 1 @@
Hey there!
Why is the output of git diff
for this file different depending on whether I call it with or without the filename? Is there a flag or another way to see the rename/move status when I pass the filename?
Most of the similar questions here on Stack Overflow suggest adding the --find-renames
argument to the call. This seems to be the default behaviour in my case, though, since git diff
without arguments already shows the rename correctly. Passing --find-renames
to the second call with the filename does not make a difference, however.
CodePudding user response:
Note, for simplicity, let's assume you committed the change and are comparing commits; the result will be the same whether you diff beforehand with --staged
or afterward using the commits.
Why is the output of git diff for this file different depending on whether I call it with or without the filename?
Think of the file specification as a lens in which to view the diff through.
When viewing the two commits in their entirety, Git sees:
- Commit 1 contains filename F1 with contents of blob B1 with hash H1, and does not contain F2.
- Commit 2 contains filename F2 with contents of blob B1 with hash H1, and does not contain F1.
Git sees that F1 and F2 are pointing to the same blob and since F1 is gone, and F2 appears with the same blob, Git can infer that is a rename. If the file was also edited, Git can do heuristics (which is configurable, btw), to determine if the differences between the blobs are close enough to still call it a rename.
When viewing the two commits through the filename lens, Git sees:
- Commit 1 does not contain F2.
- Commit 2 contains filename F2 with contents of blob B1 with hash H1.
Git sees this as an add.
What can you do?
You could make the lens larger to include both filenames. Using your example syntax for staging the move, consider these statements:
git diff --staged -- foo.txt # specify only the old file shows a delete
git diff --staged -- bar.txt # specify only the new file shows an add
git diff --staged -- foo.txt bar.txt # specify both shows a rename
Or after you commit it, here's the similar syntax for comparing the current and previous commits:
git diff @~1 @ -- foo.txt # specify only the old file shows a delete
git diff @~1 @ -- bar.txt # specify only the new file shows an add
git diff @~1 @ -- foo.txt bar.txt # specify both shows a rename
The obvious downside to this is you must know the previous name as well. Depending on what you're ultimately trying to achieve, perhaps you could chain multiple commands together to parse out the before and after filenames, and use both of them as the diff file specification. Here's an example starting point that shows you the renames only for the global lens:
git diff --staged --name-status -R
# or
git diff @~1 @ --name-status -R
Which could lead you to something like this (using Bash):
git diff --staged -- $(echo $(git diff --staged --name-status -R | grep bar.txt) | cut -d' ' -f 2-)
# or
git diff @~1 @ -- $(echo $(git diff @~1 @ --name-status -R | grep bar.txt) | cut -d' ' -f 2-)
Here's a great question and answer with details on the various diff filter options.