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.