Reputation: 8241
I have a file a.txt
.
cat a.txt
> hello
The contents of a.txt
is "hello".
I make a commit.
git add a.txt
git commit -m "first commit"
I then move a.txt
into a test
dir.
mkdir test
mv a.txt test
I then make my second commit.
git add -A
git commit -m "second commit"
Finally, I edit a.txt
to say "goodbye" instead.
cat a.txt
> goodbye
I make my last commit.
git add a.txt
git commit -m "final commit"
Now here is my question:
How do I diff the contents of a.txt
between my last commit and my first commit?
I've tried:
git diff HEAD^^..HEAD -M a.txt
, but that didn't work. git log --follow a.txt
properly detects the rename, but I can't find an equivalent for git diff
. Is there one?
Upvotes: 140
Views: 44459
Reputation: 767
To see the changes in a.txt between your first and last commit, even though it was moved, use this command:
git diff --follow <first-commit>..<last-commit> -- a.txt
For example, if your first commit is ghi9111 and the last commit is xyzab, run:
git diff --follow ghi9111..xyzab -- a.txt
This will show the differences between "hello" and "goodbye" in a.txt across the commits, even with the rename.
Upvotes: 0
Reputation: 7641
If your rename commit is staged but not committed yet, you can use:
git diff --cached -M -- file.txt renamed_file.txt
Git version 1.6 and above can use the "staged"
flag:
git diff --staged -M -- path/to/file.txt path/to/renamed_file.txt
The same commands also work with git difftool
, to launch your third-party visual diff tool (which you have to configure first):
git difftool --tool=p4merge --staged -M -- file.txt file_renamed.txt
If you want to do a diff of a renamed file, which is in a historical Git commit in the past, follow these steps: First of all, retrieve the paths of the renamed file more easily like this:
# To view the renamed path in 1 Git commit, use this:
git diff SHA_ID^ SHA_ID | grep "rename"
git diff 90e404f3^ 90e404f3 | grep "rename"
# To view the renamed path over several Git commits, use this:
git diff NEW_SHA_ID OLD_SHA_ID | grep "rename"
git diff 90e404f3 268a64e8 | grep "rename"
Result:
diff --git a/file.txt b/file_renamed.txt
...
rename from project/file.txt
rename to project/file_renamed.txt
Then use this command to view the diff, using the paths retrieved from the previous command:
# For 1 Git commit:
git diff SHA_ID^ SHA_ID -- project/file.txt project/file_renamed.txt
git diff 90e404f3ab332e696e36a3630206bd837925c0f4^ 90e404f3ab332e696e36a3630206bd837925c0f4 -- project/file.txt project/file_renamed.txt
# To track it over multiple Git commits:
git diff NEW_SHA_ID OLD_SHA_ID -- project/file.txt project/file_renamed.txt
git diff 90e404f3 268a64e8 -- project/file.txt project/file_renamed.txt
Or use difftool:
git difftool 90e404f3^ 90e404f3 -- project/file.txt project/file_renamed.txt
git difftool --tool="meld" 90e404f3^ 90e404f3 -- project/file.txt project/file_renamed.txt
git difftool --tool="p4merge" 90e404f3 268a64e8 -- project/file.txt project/file_renamed.txt
Upvotes: 2
Reputation: 3382
You can also do:
git diff rev1:file1 rev2:file2
which, for your example, would be
git diff HEAD^^:./a.txt HEAD:./test/a.txt
Note the explicit ./
-- this format otherwise assumes the paths to be relative to the root of the repo. (If you're in the root of the repo, you can of course omit that.)
This doesn't depend on the rename detection at all, as the user is explicitly stating exactly what to compare. (Therefore, it also comes in handy in some other circumstances, such as comparing files between different svn branches in a git-svn environment.)
Upvotes: 72
Reputation: 11135
To diff across a rename of a specific file, use -M -- <old-path> <new-path>
(-C
also works).
So if you both renamed and changed a file in the last commit, you can see the changes with:
git diff HEAD^ HEAD -M -- a.txt test/a.txt
This produces:
diff --git a/a.txt b/test/a.txt
similarity index 55%
rename from a.txt
rename to test/a.txt
index 3f855b5..949dd15 100644
--- a/a.txt
+++ b/test/a.txt
@@ -1,3 +1,3 @@
// a.txt
-hello
+goodbye
(// a.txt
lines added to help git detect the rename)
If git isn't detecting the rename, you can specify a low similarity threshold with -M[=n]
, say 1%:
git diff HEAD^ HEAD -M01 -- a.txt test/a.txt
From the git diff docs:
-M[<n>] --find-renames[=<n>]
Detect renames. If
n
is specified, it is a threshold on the similarity index (i.e. amount of addition/deletions compared to the file's size). For example,-M90%
means Git should consider a delete/add pair to be a rename if more than 90% of the file hasn't changed. Without a%
sign, the number is to be read as a fraction, with a decimal point before it. I.e.,-M5
becomes 0.5, and is thus the same as-M50%
. Similarly,-M05
is the same as-M5%
. To limit detection to exact renames, use-M100%
. The default similarity index is 50%.
Upvotes: 113
Reputation: 791421
The issue with the difference between HEAD^^
and HEAD
is that you have an a.txt
in both commits, so just considering those two commits (which is what diff does), there is no rename, there is a copy and a change.
To detect copies, you can use -C
:
git diff -C HEAD^^ HEAD
Result:
index ce01362..dd7e1c6 100644
--- a/a.txt
+++ b/a.txt
@@ -1 +1 @@
-hello
+goodbye
diff --git a/a.txt b/test/a.txt
similarity index 100%
copy from a.txt
copy to test/a.txt
Incidentally, if you restrict your diff to just one path (as you do in git diff HEAD^^ HEAD a.txt
you aren't ever going to see the renames or copies because you've excluded the everything apart from a single path and renames or copies - by definition - involve two paths.
Upvotes: 114