Git log 的 -S 与 -G:查找次数变化,还是查找改过的那一行
变量还叫原来的名字,搜索却漏掉了修改
排查配额变更时,你记得变量叫 quota,于是搜索它的历史,却只看到最早加入和后来新增引用的提交,没有看到数值从十改成二十。这未必是历史丢失,更可能是查询问错了问题。同一个词在修改前后都出现一次,次数没有改变,但包含它的代码行已经变了。
Git 官方文档把两种条件分得很清楚:-S 检查字符串在文件前后出现次数是否变化;-G 检查补丁中新增或删除的行是否匹配正则表达式。前者适合寻找一段代码何时出现或消失,后者适合追查相关行怎样调整。搜索提交说明的选项回答的又是另一类问题,不能替代代码差异搜索。
在三个提交上直接比较
下面脚本需要 Bash 与 Git,会在当前目录创建临时仓库并在退出时删除,只写入虚构身份与演示代码。建议在空的练习目录运行。三个提交依次是加入变量、改变数值、增加使用次数。脚本隔离全局配置并关闭提交签名与钩子,让结果不依赖个人仓库习惯。
AI生成概念示意图,非真实界面
#!/usr/bin/env bash set -euo pipefail export GIT_CONFIG_NOSYSTEM=1 export GIT_CONFIG_GLOBAL=/dev/null demo=$(mktemp -d "$PWD/pickaxe.XXXXXX") trap 'rm -rf -- "$demo"' EXIT git -c init.defaultBranch=demo init -q "$demo" cd "$demo" git config user.name 'Tutorial Example' git config user.email 'tutorial@example.invalid' git config commit.gpgsign false git config core.hooksPath /dev/null printf '%s\n' 'quota = 10' > app.py git add app.py git commit -qm 'seed' printf '%s\n' 'quota = 20' > app.py git add app.py git commit -qm 'change-value' printf '%s\n' 'quota = 20' 'print(quota)' > app.py git add app.py git commit -qm 'add-use' by_count=$(git log --reverse --format=%s -S 'quota' -- app.py) by_lines=$(git log --reverse --format=%s -G 'quota' -- app.py) regex_count=$(git log --reverse --format=%s \ -S 'quota[[:space:]]*=' --pickaxe-regex -- app.py) regex_lines=$(git log --reverse --format=%s \ -G 'quota[[:space:]]*=' -- app.py) [[ "$by_count" == $'seed\nadd-use' ]] [[ "$by_lines" == $'seed\nchange-value\nadd-use' ]] [[ "$regex_count" == 'seed' ]] [[ "$regex_lines" == $'seed\nchange-value' ]] printf '%s\n' '-S quota:' "$by_count" '-G quota:' "$by_lines" printf '%s\n' '-S regex:' "$regex_count" '-G regex:' "$regex_lines" git log -p --format='%s' -G 'quota[[:space:]]*=' -- app.py
第一组结果中,-S 应选中 seed 与 add-use;-G 还会包含 change-value。第二组把范围缩到赋值表达式后,正则计数只留下 seed,而按修改行搜索仍能看到 change-value。最后展示真实补丁,应该能找到删除的十与新增的二十。先逐个数词,再看断言,比只记住选项解释更可靠。
开启正则不会改变“计数”的含义
-S 默认把参数当作字符串,增加 --pickaxe-regex 后才按正则匹配,但比较的仍然是匹配次数。例子里赋值语句始终一条,所以修改数值不会被它选中。-G 本身接收正则,并不要求总次数增加或减少。给参数加单引号则是让 Shell 原样传递表达式,避免展开过程先把搜索条件改掉。
这里的次数变化发生在文件对中,不是把整个仓库的所有文本合起来计数。把同一段代码从一个文件搬到另一个文件,可能产生各自的减少和增加;重命名检测等差异处理也会影响比较对象。因此看到命中提交后,还应读补丁确认是行为调整、移动代码,还是单纯新增注释,不能直接把命中当成故障原因。
缩小范围前先确认自己没有排除答案
示例末尾的双横线将文件路径与选项分开,能把问题限定到 app.py。但实际项目若改过文件名,只搜现名可能漏掉旧路径;需要沿单个文件追踪时,再检查 --follow 的适用范围。默认日志也只从当前提交的历史出发,其他分支上的改动不会因为关键字相同就自动出现。
调查记录最好同时保存命令、分支和命中提交。后来仓库又有新提交时,这三项能帮助同事复现当时的搜索范围,而不必猜测你看到的是哪一段历史。
先用较宽的词找候选,再缩到函数调用或赋值模式;过早限定行形状,容易漏掉重构前的写法。
检查匹配是否落在新增或删除行上。只是出现在补丁上下文中的文字,不满足 -G 的条件。
人工核对提交父版本、当前版本和业务复现。没有搜索结果只说明本次范围与条件未命中,不能据此证明某段逻辑从未修改。


