如何对已删除的代码行进行"git blame"操作?

618

git blame 对于修改和添加的行非常有用,但是如何查找特定先前提交中存在的行最终被删除了呢?我考虑使用 bisect,但我希望有更方便的方法。

(在你问之前:在这种情况下,我只是运行了一个 git log -p 命令,并搜索了代码行,结果发现 (a) 一些白痴在上一个提交中刚刚删除了重要的行,(b) 那个白痴就是我。)


6
иҝҷйҮҢжңүдёҖдёӘеҗҺз»ӯй—®йўҳпјҢе…¶дёӯзҡ„зӯ”жЎҲжҫ„жё…дәҶgit log -S<string> /path/to/fileйңҖиҰҒдҪҝз”Ё-cжҲ–-ccеҸӮж•°жқҘжҳҫзӨәеҗҲ并пјҲеҶІзӘҒпјүжңҹй—ҙзҡ„еҲ йҷӨж“ҚдҪңгҖӮ - cfi
3
应该使用"-c"和"--cc"。@Steen:正确,感谢指出!这是一个愚蠢的疏忽。但愿我能编辑评论。现在我添加一个新的评论,然后删除我的评论,接着你再删除你的评论就太麻烦了:) - cfi
6
我希望 'git blame' 命令能够添加一个选项以显示已删除的行(使用删除线或红色文本),并标明它们被删除的修订版本。 - Craig McQueen
这个写起来难不难?我对 Git 内部不是很了解。 - Michael Lorton
6个回答

774
如果您知道该行的内容,这是一个理想的使用案例:
git log -S <string> path/to/file

这会显示引入或删除该字符串实例的提交。还有-G<regex>,可以使用正则表达式来实现同样的功能!请参阅man git-log并搜索-G-S选项,或者了解更多关于pickaxe(这些功能的友好名称)的信息。

-S选项也在git-blame的页眉中提到,在描述部分中给出了一个使用git log -S...的示例。


57
使用 Git 一年后,我仍然惊讶地发现 Git 总是 有一个命令/选项去解决我几乎所有的使用场景。谢谢分享这个,它正是我现在需要的! - Pascal Bourque
26
这种方法以前对我有用过,但刚刚我看到了一个案例,在这个案例中它没有找到删除该行的提交记录。结果发现该行是在合并提交中被删除的--这能解释这个失败吗?(然而,“git blame --reverse”方法却找到了它。) - antinome
11
要显示合并后的提交,请额外使用-c选项。 - yunzen
2
我在 manpage 上使用 ctrl+f 查找了 "-s",但没有发现任何内容。你在页面的哪个位置看到它了吗?我正在使用 git 1.8.5.2。 - temporary_user_name
2
只是一个快速的提示,您不必针对单个文件运行该命令。git log -S ... path/to/directory可以正常工作。 - Florian Brucker
显示剩余8条评论

156

我认为你真正想要的是

git blame --reverse START..END filename

manpage页面得知:

向前遍历历史,而非向后。不是显示一行出现在哪个修订版本中,而是显示该行存在的最后一个修订版本。这需要像START..END这样的一系列修订版本范围,其中路径在START中存在责任。

使用git blame reverse,你可以找到该行出现的最后一次提交。但您仍然需要获取其之后的提交。

您可以使用以下命令显示反向的git日志。第一个显示的提交将是该行最后一次出现的时间,下一个提交将是更改或删除它的时间。

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

14
如果在将该行添加到的分支和该行缺失所在的分支之间存在多个合并(或者任何其他情况下从开始到结束的衍生路径有多条),则git blame --reverse将显示最后一次按时间顺序排列的合并之前的修订版本,而不是最初做出决定放弃该行的初始合并之前的修订版本。是否有一种方法可以找到该行停止存在的最早修订版本,而不是最近的修订版本? - rakslice
3
@rakslice,你可以使用blame --reverse --first-parent命令,这样会稍微好一些。 - max630

32

仅仅是为了补充 Cascabel的回答:

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

我和这里提到的问题一样,但是事实证明,这行代码缺失是因为从一个分支合并提交被撤销,然后再次合并,有效地删除了这行代码。 --full-history标志可以防止跳过这些提交。


11
git blame --reverse 命令可以让你接近被删除行的位置,但它实际上并不指向被删除行所在的版本,而是指向该行最后一次出现时的版本。如果下一个版本是一个普通提交,那么你就很幸运能找到删除版本。但如果下一个版本是一个合并提交,那么情况可能会有些复杂。
为了创建difflame,我解决了这个问题。如果您已经在计算机上安装了 Python 并且愿意尝试,请不要再等待,让我知道它的使用效果如何。 https://github.com/eantoranz/difflame

你能解释一下,如果以下的修订版本是合并提交时的逻辑吗? - Nasif Imtiaz Ohi
1
现在我记不清具体细节了(我甚至认为我需要在difflame中纠正一些边角情况),但如果以下修订是合并提交,则需要跟踪合并修订的其他父级,以查看该行被删除的位置(还要考虑它可能已经在合并修订本身中被删除,而与父级无关)。 - eftshift0

7

对于隐藏在合并提交中的更改

合并提交自动从Git日志输出中隐藏其更改。无论是pickaxe还是reverse-blame都无法找到更改。因此,我想要的行已经被添加了,然后又被删除了,我想找到删除它的合并。文件git log -p -- path/file历史记录仅显示该行被添加。以下是我发现的最佳方法:

git log -p -U9999 -- path/file

搜索更改,然后向后搜索“^ commit”-第一个“^ commit”是文件上次拥有该行的提交。第二个“^ commit”在它消失之后。第二个提交可能是删除它的提交。 -U9999 旨在显示整个文件内容(在每次更改文件后),假设您的文件最多有9999行。

通过蛮力查找任何相关的合并(将每个可能的合并提交与其第一个父提交进行比较,运行大量提交)

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

(我试图更限制修订过滤器,但遇到问题,不建议这样做。我正在查找的添加/删除更改位于合并在不同时间和A ... B合并到主线路时未包括的不同分支上。)

显示具有这两个提交的Git树(以及大量复杂的Git历史记录已删除):

git log --graph --oneline A B ^$(git merge-base A B) (A是上面的第一个提交,B是第二个提交)

显示A的历史记录和B的历史记录,减去A和B的历史记录。

备用版本(似乎更线性地显示路径,而不是常规的Git历史记录树-但我更喜欢常规的git历史记录树):

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

三个点,不是两个点 - 三个点意味着“r1 r2 --not $(git merge-base --all r1 r2)”。它是一组从r1(左侧)或r2(右侧)之一可达的提交,但不能从两者都可达。- 来源:“man gitrevisions”


这最终帮助我追踪到了一个更改。需要注意的一点是:当文件名不同时,我要查找的那一行被删除了,因此我必须弄清楚那个文件名并在搜索时使用它。 - CTS_AE

2

如果你喜欢使用GUI界面,免费软件DeepGit是个不错的选择。在旧版本文件的Blame视图中,通过拖动左侧行号来选择感兴趣的行。文件顶部的日志将被筛选,只显示与这些行相关的提交记录。最上面的提交记录将是它们的删除记录。

或者,如果查看的是之后版本的文件,其中这些行已经不存在了,通过拖动左侧边距选择已删除行周围的行。上方的提交日志将被筛选为这些行,并且将包括删除所选行之间的提交记录。点击Diff按钮并逐个浏览筛选后的修订版本,找到删除操作记录。


不幸的是,当该行被合并提交修改与其第二个父提交相比时,它似乎不能正常工作。 - RyanCu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接