276

Is it possible to get git to produce a diff between a specific file as it exists now, and as it existed before the last commit that changed it?

That is, if we know:

$ git log --oneline myfile
123abc Fix some stuff
456def Frobble the foos
789dba Initial commit

Then git diff 456def myfile shows the last change to myfile. Is is possible to do the same without the knowledge produced by the git log; what changed in 123abc?

Martin G
  • 17,357
  • 9
  • 82
  • 98
Chowlett
  • 45,935
  • 20
  • 116
  • 150
  • 17
    I prefer using `git diff HEAD^ ` – asgs Sep 12 '17 at 12:50
  • 9
    @asgs - Doesn't do what I was asking (for two reasons - `HEAD^` is `123abc`, `HEAD^^` is `456def`; and if there were other commits _that didn't affect this file_ then `HEAD^` refers to them) – Chowlett Sep 12 '17 at 16:23
  • You're right, missed the "the last commit that changed it" part – asgs Sep 13 '17 at 07:36

3 Answers3

267

This does exist, but it's actually a feature of git log:

git log -p [-m] [--follow] [-1] <path>

Note that -p can also be used to show the inline diff from a single commit:

git log -p -1 <commit>

Options used:

  • -p (also -u or --patch) is hidden deeeeeeeep in the git-log man page, and is actually a display option for git-diff. When used with log, it shows the patch that would be generated for each commit, along with the commit information—and hides commits that do not touch the specified <path>. (This behavior is described in the paragraph on --full-diff, which causes the full diff of each commit to be shown.)
  • -m causes merge commits to include the diff content (otherwise these just show the commit message, as if -p were not specified).
  • -1 shows just the most recent change to the specified file (-n 1 can be used instead of -1); otherwise, all non-zero diffs of that file are shown.
  • --follow is required to see changes that occurred prior to a rename.

As far as I can tell, this is the only way to immediately see the last set of changes made to a file without using git log (or similar) to either count the number of intervening revisions or determine the hash of the commit.

To see older revisions changes, just scroll through the log, or specify a commit or tag from which to start the log. (Of course, specifying a commit or tag returns you to the original problem of figuring out what the correct commit or tag is.)

Credit where credit is due:

  • I discovered log -p thanks to this answer.
  • Credit to FranciscoPuga and this answer for showing me the --follow option.
  • Credit to ChrisBetti for mentioning the -n 1 option and atatko for mentioning the -1 variant.
  • Credit to sweaver2112 for getting me to actually read the documentation and figure out what -p "means" semantically.
  • Credit to Oscar Scholten for pointing out that by default, -p does not show diff-contents for merge commits.
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
  • 11
    This was a great solution for me. Showed each commit and its differences to the current file when I ran `git log -p filename` – Ian Jamieson Oct 06 '14 at 18:58
  • 4
    Perfect. To see just the last change, it's as simple as adding the `-n 1` parameter. `git log -p -n 1 filename` – Chris Betti Mar 11 '15 at 13:22
  • @ChrisBetti Thanks; I've incorporated that into my answer! – Kyle Strand Oct 01 '15 at 22:17
  • 1
    `-n 1` can also be replaced by `-1`, it doesn't change the result I just prefer the syntax: `git log -p -1 filename` – atatko Apr 27 '16 at 20:47
  • i really wish people would explain flags (-p) in their answers. (it means "show patch" which is like a diff with only the changed lines) – Scott Weaver Jul 22 '16 at 14:32
  • @sweaver2112 As you can tell from the answer itself, I'm happy to incorporate new information into my answer from helpful comments. Instead of complaining, why not suggest an improvement? You can even just edit the answer. – Kyle Strand Jul 22 '16 at 15:07
  • @kylestrand I'm with sweaver2112 on wishing folks would explain flags. I'm trying to understand the `[--follow]` option, and I went to the link provided and nowhere can I find any input from FranciscoPuga as mentioned. So, Kyle, what is the `--follow` option, what does it do, why was it recommended? – zipzit Jul 31 '16 at 04:07
  • @zipzit I did say what it does: it shows changes prior to a rename. If you don't understand what I mean by this, then make a couple of commits that change a file, then rename it, then run the command (without `-1`) with and without `--follow`. – Kyle Strand Jul 31 '16 at 06:42
  • It does look like I linked to the wrong answer, though. I've fixed it; the answer in the corrected link explains the option. – Kyle Strand Jul 31 '16 at 06:44
  • 1
    there is a useful option "--skip=[n]". You can type `git log -p -1 --skip=1 ` to display second commit. – Maciej Łoziński Jun 11 '17 at 12:48
  • If the last commit is a merge commit, `git log -p -1 ` prints the commit message, not the diff. To get the diff, run `git log --first-parent -p -1 ` – Oscar Scholten Sep 02 '21 at 07:56
  • @OscarScholten That's a good catch, but it's not `--first-parent` you need, it's `-m`. I'll update the answer; thanks! – Kyle Strand Sep 02 '21 at 16:11
236

One of the ways to use git diff is:

git diff <commit> <path>

And a common way to refer one commit of the last commit is as a relative path to the actual HEAD. You can reference previous commits as HEAD^ (in your example this will be 123abc) or HEAD^^ (456def in your example), etc ...

So the answer to your question is:

git diff HEAD^^ myfile
Francisco Puga
  • 23,869
  • 5
  • 48
  • 64
  • 7
    Oh, of course. I tried `HEAD^`, but of course that produced nothing. Didn't think to try `HEAD^^`. – Chowlett Apr 16 '12 at 15:11
  • 20
    Maybe easier syntax for long-ago commits : `HEAD~2` – ibizaman Sep 04 '13 at 17:19
  • 22
    It is not true (at least for Git 1.9.0) that `HEAD^^ myfile` will actually refer to the second-to-last commit that changed `myfile`; it will refer to the second-to-last commit overall. Is there any way to specify "I want to see the last change made to this file" without either specifying (part of) the commit hash or counting the number of commits between the last change made to *that file* and the current revision? – Kyle Strand Mar 14 '14 at 17:39
  • 4
    Hm, looks like `git log -p` is pretty close. – Kyle Strand Mar 14 '14 at 17:44
  • 1
    @KyleStrand as you wrote `git log` is the way to go. But see [this answer also](http://stackoverflow.com/a/5493663/930271) – Francisco Puga Mar 15 '14 at 10:34
  • 1
    @ibizaman cool! I put this at `git gui` tool add `git difftool -d HEAD~$ARGS`, thx! – Aquarius Power Jul 27 '14 at 00:19
  • Any idea why I'm getting fatal: bad revision '^^HEAD' ?? I'm running from the root directory that I cloned and using the fully qualified path to the file name. git status works fine and showed me all my changed files. and then I ran git add *.java and then git commit but I want to diff all my files so I can create a meaningful commit message. – fIwJlxSzApHEZIl Dec 03 '14 at 03:44
  • Use HEAD^^ not ^^HEAD or try using HEAD~2 – Francisco Puga Dec 03 '14 at 09:30
  • 37
    downvote reason: the question asks "between a specific file as it exists now, and as it existed before _the last commit that changed it_", but if the file was not changed in the last overall commit, this answer does not work. – Chris Betti Mar 11 '15 at 13:15
  • 5
    Why is this upvoted? It it no way answers the question asked >:( – gman Jan 09 '16 at 04:32
  • 1
    @gman For two years it was the only answer, and it was presumably "close enough" to what a lot of people were looking for. – Kyle Strand Jan 09 '16 at 06:17
  • 2
    @KyleStrand kind of strange/amazing that a question like this took two whole years to answer. also strange that the "close enough" but... IMO wrong answer got soo many upvotes. – Trevor Boyd Smith Oct 07 '16 at 12:59
  • And if you are using meld as difftool: `git difftool HEAD^ file` – ForeverLearning May 29 '17 at 18:03
  • As noted in previous comment under this answer it is not a correct answer to the posted question. – BobHy Aug 12 '20 at 17:54
9

If you are fine using a graphical tool this works very well:

gitk <file>

gitk now shows all commits where the file has been updated. Marking a commit will show you the diff against the previous commit in the list. This also works for directories, but then you also get to select the file to diff for the selected commit. Super useful!

Martin G
  • 17,357
  • 9
  • 82
  • 98