19

Here was my problem for the last 30 minutes: I had a couple of changes that disappeared in one of my files, and I don't know when that happened. And I want to know who did that!

I started looking for the revisions having my files:

git grep <searched_string> $(git rev-list --all) -- <file>

is the path to the file or a wildcard like *.gsp

I got a bunch of revisions, I look at the last one, and try to get it's children (thinking the first child should be the first revision where my changes disappeared)

git rev-list --children <revision_id>

is the 40 chars from the beginning of the last line of the previous command

Getting close! I am looking at the beginning of the output, and take the first child and then run

git log <revision_id_s_first_child> --stat

Then I look at the output and find my file and who did the change! (it turned out, I was to blame...)

Is there anyway to do that faster (git blame would not show what has been deleted) ?

standup75
  • 4,734
  • 6
  • 28
  • 49

4 Answers4

29

git blame has a --reverse option that takes a range of commits and shows you the last commit where a line existed before it was deleted. So, you find a commit you know the lines were there, let's say abcdef01 for example, and to show the last commit before the delete, do:

git blame --reverse abcdef01..HEAD -- <file>
Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • 1
    It's a shame that `git gui blame` does not support `--reverse`! – Mikko Rantalainen Aug 24 '12 at 11:02
  • What does this tell me here - `9597c8db (XYZ 2014-05-27 10:18:51 -0700 93) 8e4dbc16 (XYZ 2014-05-06 19:08:29 +0100 94) 1b4dbc16 (XYZ 2014-05-06 19:08:29 +0100 95)` -700 and +100 what does it mean ? – R11G May 28 '14 at 09:01
  • Those are time zone offsets. -0700 is probably the western U.S., 7 hours behind GMT. +0100 is probably Europe, 1 hour ahead of GMT. – Karl Bielefeldt May 28 '14 at 13:00
  • 7
    Indeed `--reverse` shows "the last commit where a line existed before it was deleted". That commit might have nothing to do with the file under consideration though. It is the next commit that actually deleted the line: see http://stackoverflow.com/a/9870218/2097 to find this next commit. Does anyone know whether it is possible to have `git blame --reverse` show the relevant commit for each line, instead of the irrelevant commit it shows by default? – BlackShift Nov 02 '16 at 11:23
10

If you know some substring that would be in the line that was removed, then you can use the -G option to git log to find commits that introduced a change that added or removed lines containing that substring. e.g. if you knew that the word "pandemic" was in the line that disappeared, you can do:

git log -Gpandemic -p

(The parameter to -G can be a regular expression.) This option was added rather recently to git - if it doesn't work, try -S instead, which has slightly different semantics, but should have a similar effect.

Mark Longair
  • 446,582
  • 72
  • 411
  • 327
  • -G does not seem to work for me. -S would show me when it has been added, not when it has been removed – standup75 Aug 31 '11 at 20:42
  • `-S` should show you when it has been added **or** removed (see the [man page](http://www.kernel.org/pub/software/scm/git/docs/git-log.html)). However, `-S` does this slightly strangely - it looks at the number of occurrences of the string in the the version before the commit and in the commit - so if you've just *moved* a line, for example, that won't be detected. This is why `-G` is generally preferable, but that was only introduced in git version 1.7.4. – Mark Longair Aug 31 '11 at 20:59
  • we got 1.7.3.4. Anyway thanks for the feedback, definitely interesting. Still can't see when my change has been deleted with -S, although the string I search is not present in the next revision at all. – standup75 Aug 31 '11 at 21:19
  • You might need `-c` or `-cc` to show removals during merge (conflicts). See [this answer](http://stackoverflow.com/a/12591569/923794) – cfi Jul 04 '13 at 12:19
3

Note that since Git 2.11 (Q4 2016), you won't have to specify ..HEAD if you want to look at commits between a specific one and the current one.

So Karl Bielefeldt's answer would then be:

 git blame --reverse abcdef01 -- <file>

See commit d993ce1 (14 Jun 2016) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit 1172e16, 10 Oct 2016)

blame: dwim "blame --reverse OLD" as "blame --reverse OLD.."

It is a common mistake to say "git blame --reverse OLD path", expecting that the command line is dwimmed as if asking how lines in path in an old revision OLD have survived up to the current commit.

Instead of always requiring both ends of a range, we could DWIM "OLD", which could be a misspelt "OLD..", to be a range that ends at the current commit.

git blame --reverse now include:

--reverse <rev>..<rev>:

Walk history forward instead of backward.
Instead of showing the revision in which a line appeared, this shows the last revision in which a line has existed.
This requires a range of revision like START..END where the path to blame exists in START.
git blame --reverse START is taken as git blame --reverse START..HEAD for convenience.

(Note of the note: "dwim" is an acronym for "Do What I Mean" (not what I say))

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
1

git blame --reverse can get you close to where the line is deleted. But it actually doesn't point to the revision where the line is deleted. It points to the last revision where the line was present. Then if the following revision is a plain commit, you are lucky and you got the deleting revision. OTOH, if the following revision is a merge commit, then things can get a little wild. As part of the effort to create difflame I tackled this very problem so if you already have python installed on your box and you are willing to give it a try, then don't wait any longer and let me know how it goes.

https://github.com/eantoranz/difflame

eftshift0
  • 26,375
  • 3
  • 36
  • 60