想知道某段代码是什么时候加进来、什么时候被删掉的,git log -S 和 -G 是最直接的工具。它们的区别很微妙:一个数出现次数,一个看增删的行。这篇用六个精心设计的提交把差别演示清楚,再测大历史上的耗时。
git log -S'foo' → C1 新增 foo 的调用 | C4 再加一处 foo 调用 | C5 删掉第一处 foo | C6 删掉全部 foo
git log -G'foo' → C1 新增 foo 的调用 | C2 改参数 foo(1) → foo(2) | C3 把 foo 这行挪到末尾 | C4 再加一处 foo 调用 | C5 删掉第一处 foo | C6 删掉全部 foo
git log -S'foo(2)' → C2 改参数 foo(1) → foo(2) | C5 删掉第一处 foo
git log -G'foo\(2\)' → C2 改参数 foo(1) → foo(2) | C3 把 foo 这行挪到末尾 | C5 删掉第一处 foo
逐个提交 foo 的出现次数(整个文件):
C1 新增 foo 的调用 → 1 次
C2 改参数 foo(1) → foo(2) → 1 次
C3 把 foo 这行挪到末尾 → 1 次
C4 再加一处 foo 调用 → 2 次
C5 删掉第一处 foo → 1 次
C6 删掉全部 foo → 0 次
C2(改参数)和 C3(挪行)的 foo 次数没变,-S 看不到,-G 能看到;C4 增加一次、C5/C6 减少,两者都能看到-S<字符串> 找使该字符串在文件中出现次数发生变化的提交——“引入”和“去掉”它的提交;改参数、把行挪个位置、出现次数没变的修改都找不到。-G<正则> 找 diff 里被增加或删除的行匹配正则的提交,所以改参数和挪行也算,命中更多。
$ git log -S'foo(3)' -p --format='%s' | grep -E '^(C[0-9]|[+-][^+-])' C6 删掉全部 foo -other = foo(3) C4 再加一处 foo 调用 +other = foo(3)
加 -p 能看到每个命中提交里增删的具体行:同一行代码的“出现”和“消失”两个提交都会命中,-S 回答的是“这个字符串是哪个提交引入、哪个提交去掉的”。
git log -S'foo\(.\)' --pickaxe-regex → C1 新增 foo 的调用 | C4 再加一处 foo 调用 | C5 删掉第一处 foo | C6 删掉全部 foo git log -G'foo\(.\)' → C1 新增 foo 的调用 | C2 改参数 foo(1) → foo(2) | C3 把 foo 这行挪到末尾 | C4 再加一处 foo 调用 | C5 删掉第一处 foo | C6 删掉全部 foo git log -S'foo\(.\)' → (无)
-G 的参数默认就是正则;-S 的参数默认是字面字符串(带正则元字符的参数找不到任何东西),加 --pickaxe-regex 后按正则处理,语义仍是“出现次数变化”。
$ git log -S'needle' --stat --format='%s'; echo ---; git log -S'needle' --pickaxe-all --stat --format='%s' 同时改 a.txt(加 needle)和 b.txt a.txt | 1 + 1 file changed, 1 insertion(+) --- 同时改 a.txt(加 needle)和 b.txt a.txt | 1 + b.txt | 1 + 2 files changed, 2 insertions(+)
-S 和 -G 默认只保留匹配的文件;--pickaxe-all 在命中的提交里保留全部文件的改动。
git log -S'UNIQUE_TOKEN' 54 ms 输出: 844a201 git log -G'UNIQUE_TOKEN' 72 ms 输出: 844a201 git log -p | grep(笨办法) 86 ms 输出: 2 git log --format=%h(不比较内容) 26 ms 输出: 5a477d0 -S 命中了 2 个提交,在历史里分别排第 2345、2445 个(从最早数起):第 2345 个引入了这个字符串;第 2445 个是同一个文件 f45.txt 下一次被改写,把它删掉了
两者都需要比较每个提交改动的文件内容,比纯 git log 慢,但比把所有补丁打出来再 grep 快。-S 因为只数次数,略快于 -G。