135

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?

Craig M. Brandenburg
  • 3,354
  • 5
  • 25
  • 37
Ken Hirakawa
  • 7,831
  • 10
  • 38
  • 49
  • I suppose it would be the same if you did a 'git mv a.txt test'? IE you just renamed the file instead of moving it into a subdirectory. – Max Waterman Jul 10 '20 at 05:56
  • Related: an answer I just wrote: sometimes you need to leave off the file path to get it to track renamed files: [How to manually view the change history of a file graphically in `meld`](https://stackoverflow.com/a/74965393/4561887) – Gabriel Staples Dec 30 '22 at 20:16

4 Answers4

114

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.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 4
    Lets say the commit contained multiple file changes. How would I narrow it down to a diff of a single file? – Ken Hirakawa Oct 14 '11 at 15:12
  • 5
    @KenHirakawa use `-- ` ... see my answer. – Nolan Amy Mar 10 '13 at 20:33
  • In my case, I wanted to show the details of a commit where the file was renamed, but it was just showing me that a file was deleted and a file was added... I ran `git show -C [commit]` and it recognized the file rename and showed me the diff between the files. Perfect. – Jason May 12 '17 at 16:24
105

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%.

Nolan Amy
  • 10,785
  • 5
  • 34
  • 45
  • ...Of course, also works when the change and rename are in separate commits as in the original example: `git diff HEAD^^ HEAD -M -- a.txt test/a.txt` – Nolan Amy Mar 10 '13 at 20:34
  • 1
    Only when the contents of the files are close enough for diff to come up with a similarity index. Using both options (-M and -C) is showing the diff to /dev/null an from /dev/null in a file that was renamed and has changed entirely (including indent wise), it doesn't even catch it when ignoring whitespace. – DavidGamba Apr 12 '13 at 15:11
64

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.)

benkc
  • 3,292
  • 1
  • 28
  • 37
  • 2
    Oooh, this is nice! – Nolan Amy Feb 07 '17 at 21:12
  • 2
    For a rename with additional file changes brought in via a merge this is the only answer that worked for me. Thanks! – Lane Rettig Apr 06 '17 at 12:02
  • What @LaneRettig said. Odd, because `git diff dev` correctly shows the limited changes, but all the rename detection suggestions fail and show the whole file as new. So this answer works, just means you have to account for the rename manually. – Terry Brown Mar 15 '21 at 17:28
  • When comparing to files from the same revision, I use `--no-index`, and I was having trouble with comparing files from different revisions because I kept using `--no-index`. So don't be like me, don't use `--no-index` in this command when comparing files from different revisions. – ob-ivan May 22 '23 at 10:45
3

If your rename commit is staged but not committed yet, you can use:

git diff --cached -M -- file.txt renamed_file.txt
Mr-IDE
  • 7,051
  • 1
  • 53
  • 59