git blame 总说这行是最后搬动它的人写的,怎么找到真正的作者?-M 和 -C 各追什么、门槛是多少?格式化提交把整个文件 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 里是新增,于是算给挪动的人。
[默认] 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):)不会被追。
[默认] 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 只在本文件里找,对跨文件搬运没用。
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 可以让整个团队生效。