How do I "git blame" a deleted line?
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 开发者_StackOverflow中文版was that idiot.)
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...
.
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
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.
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
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"
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.
精彩评论