bigywater.tech
Git · 59

pickaxe:-S 与 -G 查代码的出处

想知道某段代码是什么时候加进来、什么时候被删掉的,git log -S 和 -G 是最直接的工具。它们的区别很微妙:一个数出现次数,一个看增删的行。这篇用六个精心设计的提交把差别演示清楚,再测大历史上的耗时。

4 vs 6
同样搜 foo,-S 命中 4 个(次数变化),-G 命中 6 个(diff 里出现过)
挪行
把一行代码挪个位置,-S 看不到,-G 能看到
54 / 72 / 86 ms
3000 个提交的历史上 -S、-G、log -p | grep 的耗时

-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 里被增加或删除的行匹配正则的提交,所以改参数和挪行也算,命中更多。

-p 看具体的行

实测 · 本机执行输出
  $ 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 回答的是“这个字符串是哪个提交引入、哪个提交去掉的”。

--pickaxe-regex

实测 · 本机执行输出
  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 后按正则处理,语义仍是“出现次数变化”。

--pickaxe-all

实测 · 本机执行输出
  $ 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。

分步演示

pickaxe
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。耗时 5 次中位数,含进程启动;3000 个提交由 git fast-import 生成
    • 没有验证:git log -L(按函数或行范围追踪);二进制文件上的 -S;--follow 与 -S 的配合;更大的仓库(几十万提交)上的耗时;-S 对重命名文件的处理;-i(忽略大小写)
    京ICP备2026067502号-1京公网安备11010502063487号