bigywater.tech
Git · 56

blame:-M、-C 怎么追踪搬运的代码

git blame 总说这行是最后搬动它的人写的,怎么找到真正的作者?-M 和 -C 各追什么、门槛是多少?格式化提交把整个文件 blame 到自己名下怎么办?这篇构造一个有交换、挪动、跨文件搬运的历史逐项实测。

20
-M / -C 默认只认每行至少 20 个字母数字字符的移动;-M5 降低到 5
-C -C -C
三层 -C:同一提交修改的文件 → 也找新建文件 → 任何提交里的任何文件
.git-blame-ignore-revs
列出格式化提交,blame 就不会再把整个文件归给它

实验历史

实测 · 本机执行输出
  $ git log --format='%an: %s' --reverse
  Alice: Alice 写了三个函数
  Bob: Bob 交换 P 与 Q
  Bob: Bob 把 short 挪到开头
  Carol: Carol 把 P 搬到 b.py
  现在 a.py 与 b.py 的内容:
  $ cat a.py; echo ------; cat b.py
  def short(a):
      return a
  
  def collect_user_names(users):
      names = [user.name for user in users]
      return sorted(names)
  ------
  def calculate_total_price(items):
      total = sum(item.price for item in items)
      return total
  
  def format_report_header(title):
      return "== " + title.upper() + " =="

Alice 写了三个函数 P、Q、short;Bob 交换 P 与 Q 并把 short 挪到开头;Carol 把 P 搬到 b.py。真正的作者是 Alice,但三次改动都让别人的名字出现在 blame 里。

默认

实测 · 本机执行输出
  b.py(Carol 搬来的函数 P):
    Carol | def calculate_total_price(items):
    Carol |     total = sum(item.price for item in items)
    Carol |     return total
  a.py(Bob 交换并挪动过的):
    Bob   | def short(a):
    Bob   |     return a
    Alice | def collect_user_names(users):
    Alice |     names = [user.name for user in users]
    Alice |     return sorted(names)

blame 以行为单位,问“这行是哪个提交加进来的”。被挪动的行在 diff 里是新增,于是算给挪动的人。

-M

实测 · 本机执行输出
  [默认] Bob 的两个提交之后的 a.py(P 被 Bob 挪到了最后):
    Bob   | def short(a):
    Bob   |     return a
    Alice | def collect_user_names(users):
    Alice |     names = [user.name for user in users]
    Alice |     return sorted(names)
    Bob   | def calculate_total_price(items):
    Bob   |     total = sum(item.price for item in items)
    Bob   |     return total
  [-M]:
    Bob   | def short(a):
    Bob   |     return a
    Alice | def collect_user_names(users):
    Alice |     names = [user.name for user in users]
    Alice |     return sorted(names)
    Alice | def calculate_total_price(items):
    Alice |     total = sum(item.price for item in items)
    Alice |     return total
  → 长函数 P、Q 的各行(≥ 20 个字母数字)被追到 Alice;短函数 short 的两行(不到 20 个字母数字)仍算 Bob
  降低阈值:-M5 让短的行也能追上:
    Alice | def short(a):
    Alice |     return a

在同一个文件里检测移动的行,追回原作者。门槛是每行至少 20 个字母数字字符(-M<n> 调整),太短的行(例如 def short(a):)不会被追。

-C 跨文件

实测 · 本机执行输出
  [默认] b.py:
    Carol | def calculate_total_price(items):
    Carol |     total = sum(item.price for item in items)
    Carol |     return total
  [-C]  :
    Alice | def calculate_total_price(items):
    Alice |     total = sum(item.price for item in items)
    Alice |     return total
  [-M]  (-M 只在同一文件里找,对跨文件没用):
    Carol | def calculate_total_price(items):
    Carol |     total = sum(item.price for item in items)
    Carol |     return total
  b.py 里 Carol 自己写的 format_report_header 还是 Carol:
    Carol | def format_report_header(title):
    Carol |     return "== " + title.upper() + " =="

在其它文件里找来源。把函数从 a.py 搬到 b.py 的提交同时修改了 a.py,所以 -C 能追回 Alice;-M 只在本文件里找,对跨文件搬运没用。

-C 的三个层级

实测 · 本机执行输出
  git blame           b.py → Carol
  git blame -C        b.py → Carol
  git blame -C -C     b.py → Alice
  git blame -C -C -C  b.py → Alice
  (这个提交只新建了 b.py,没有修改 a.py:-C 只在“同一提交里被修改的文件”里找,找不到;-C -C 才会去别的文件里找)

-C 只看同一提交里被修改的文件;-C -C 再加上这个提交新建的文件;-C -C -C 找任何提交里的任何文件。代价是越来越慢。

格式化提交

实测 · 本机执行输出
  默认: Alice, Bob, Alice, Alice, Bob
  --ignore-rev: Alice, Alice, Alice, Alice, Alice
  配置 blame.ignoreRevsFile 之后(不用每次传参数): Alice, Alice, Alice, Alice, Alice

批量改缩进或格式的提交会把大量行的 blame 改到自己名下。--ignore-rev 让 blame 跳过某些提交,把行归给更早的作者;把这些提交写进 .git-blame-ignore-revs 并设置 blame.ignoreRevsFile 可以让整个团队生效。

分步演示

blame 的搬运检测
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。blame 的作者来自 --line-porcelain 的 author 字段;所有提交的作者与时间固定;每个场景单独建仓库
    • 没有验证:-w(忽略空白)与 --ignore-rev 的组合;git log -L :函数名:文件 追踪函数历史;超过 20 个字母数字门槛时的具体计算方式(只验证了两个长度的行);大型仓库上 -C -C -C 的耗时;git blame --ignore-revs-file 对合并提交的处理
    京ICP备2026067502号-1京公网安备11010502063487号