How do I "git blame" a deleted line?

Git

Git Problem Overview


git blame is great for modified and added lines, but how can I find when a line that existed in a specific previous commit was eventually deleted. I'm thinking bisect, but I was hoping for something handier.

(Before you ask: in this case, I just did a git log -p and searched through for the code line and (a) some idiot had just deleted the vital line in the previous commit and (b) I was that idiot.)

Git Solutions


Solution 1 - Git

If you know the contents of the line, this is an ideal use case for:

git log -S <string> path/to/file

which shows you commits which introduce or remove an instance of that string. There's also the -G<regex> which does the same thing with regular expressions! See man git-log and search for the -G and -S options, or pickaxe (the friendly name for these features) for more information.

The -S option is actually mentioned in the header of the git-blame manpage too, in the description section, where it gives an example using git log -S....

Solution 2 - Git

I think what you really want is

git blame --reverse START..END filename

From the manpage:

> 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 revisions like START..END where the path to blame exists in START.

With git blame reverse, you can find the last commit the line appeared in. You still need to get the commit that comes after.

You can use the following command to show a reversed git log. The first commit shown will be the last time that line appears, and the next commit will be when it is changed or removed.

git log --reverse --ancestry-path COMMIT^..master

Solution 3 - Git

Just to complete Cascabel's answer:

git log --full-history -S <string> path/to/file

I had the same problem as mentioned here, but it turned out that the line was missing, because a merge commit from a branch got reverted and then merged back into it, effectively removing the line in question. The --full-history flag prevents skipping those commits.

Solution 4 - Git

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

Solution 5 - Git

For changes hidden in merge commits

Merge commits automatically have their changes hidden from the Git log output. Both pickaxe and reverse-blame did not find the change. So the line I wanted had been added and later removed and I wanted to find the merge which removed it. The file git log -p -- path/file history only showed it being added. Here is the best way I found to find it:

git log -p -U9999 -- path/file

Search for the change, then search backwards for "^commit" - the first "^commit" is the commit where the file last had that line. The second "^commit" is after it disappeared. The second commit might be the one that removed it. The -U9999 is meant to show the entire file contents (after each time the file was changed), assuming your files are all max 9999 lines.

Finds any related merges via brute force (diff each possible merge commit with its first parent, run against tons of commits)

git log --merges --pretty=format:"git diff %h^...%h | grep target_text" HEAD ^$(git merge-base A B) | sh -v 2>&1 | less

(I tried restricting the revision filter more, but I ran into problems and don't recommend this. The add/removal changes I was looking for were on different branches which were merged in at different times and A...B did not include when the changes actually got merged into the mainline.)

Show a Git tree with these two commits (and a lot of the complex Git history removed):

git log --graph --oneline A B ^$(git merge-base A B) (A is the first commit above, B is the second commit above)

Show history of A and history of B minus history of both A and B.

Alternate version (seems to show the path more linearly rather than the regular Git history tree - however I prefer the regular git history tree):

git log --graph --oneline A...B

Three, not two dots - three dots means "r1 r2 --not $(git merge-base --all r1 r2). It is the set of commits that are reachable from either one of r1 (left side) or r2 (right side), but not from both." - source: "man gitrevisions"

Solution 6 - Git

If you prefer a GUI, the freeware DeepGit seems pretty good for this. While in the Blame view for the old revision of the file, select the lines of interest by dragging over the left margin. The file's log at the top is filtered to only show commits relevant to those lines. The top commit would be their deletion.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichael LortonView Question on Stackoverflow
Solution 1 - GitCascabelView Answer on Stackoverflow
Solution 2 - GitChronialView Answer on Stackoverflow
Solution 3 - GitestaniView Answer on Stackoverflow
Solution 4 - Giteftshift0View Answer on Stackoverflow
Solution 5 - GitCurtis YallopView Answer on Stackoverflow
Solution 6 - GitRyanCuView Answer on Stackoverflow