【问题标题】:Can I configure git blame to always ignore certain commits? Want to fix git blame once and for all我可以将 git blame 配置为始终忽略某些提交吗?想要一劳永逸地修复 git blame
【发布时间】:2016-04-29 16:11:57
【问题描述】:

我在一个 git blame 已被有效破坏的存储库中。

我想在 git blame 中忽略两个提交。

  • 提交 1 破坏了 很多 个文件。
  • 提交 2 立即恢复提交 1。

现在每次我 git blame 一行时,我都会看到 [commit 2] 的作者,而不是真正的逻辑作者。

我最终不得不改用git log [file in question],或者this question 中列出的另一种解决方案。

每当我使用 Intellij 中的 Annotate 功能(基本上是 git blame)时,这两个提交让我感到难过。

以前有没有人在不重写历史的情况下解决了这个问题?

【问题讨论】:

  • 我正在考虑用 husky 制作一个 git-hook,如果该提交的消息以 (pure-fmt) 之类的特殊内容开头,它可以自动向 .git-blame-ignore-revs 添加提交。有人听说过这样的 git-hook 正在开发吗? @VonC?

标签: git intellij-idea version-control


【解决方案1】:

如果它真的被立即还原,您可以使用git replace --edit $comment2 将 commit1 的父级伪装成它的父级。

【讨论】:

  • freenode.net#git 上的某个人还建议了git graft,我可能最终会在这种情况下使用它。 Replace 完全删除 git 对象,而 git 嫁接指向另一个提交。
  • 无替换不会删除原始对象(这会破坏完整性),它只会创建替换。基本上他们做同样的事情。 Here 是关于它们的一些扩展意见。
【解决方案2】:

每当我使用 Intellij 中的 Annotate 功能(基本上是 git blame)时,这两个提交让我感到难过。
有没有人在不重写历史的情况下解决了这个问题?

2019 年第三季度之前,没有。
但是使用 Git 2.23,您将能够指示 git blame 来忽略这两个有问题的提交。 (IntelliJ“注释”功能可能需要一段时间才能赶上)

Michael Platingscomments 虽然:

git blame --ignore-rev 假设指定的提交进行了无意义的更改(例如重新格式化)。
不幸的是,删除和添加文件都是相当大的变化,所以--ignore-rev 在这里无济于事。

话虽如此,git blame 现在可以忽略提交(在这种特殊情况下甚至可能不会)。

一般来说,从 Git 2.23 开始:

git blame”学会了“忽略”历史中的提交,其影响(以及它们的存在)被忽略。

您可以在git config 中注册!您甚至不需要在每次 git blame 调用时在参数中传递这些提交。

请参阅commit 78fafbb(2019 年 6 月 30 日)和Michael Platings (``)commit 1d028dc(2019 年 6 月 20 日)。
commit 07a54dc(2019 年 6 月 28 日)Jeff King (peff)
请参阅commit f0cbe74commit a07a977(2019 年 6 月 20 日)和commit 1fc7338commit 8934ac8commit ae3f36dcommit 55f808fcommit f93895fcommit 24eb33e(2019 年 5 月 15 日)@9876543。
(2019 年 7 月 19 日在 commit 209f075 中由 Junio C Hamano -- gitster -- 合并)

blame:添加忽略提交及其更改的功能

在责备文件时,进行格式更改或函数重命名的提交通常不有趣。
用户可能认为这样的提交“不有趣”并且想要忽略并在分配责任时对其进行更改。

例如,假设一个文件有以下 git history / rev-list:

---O---A---X---B---C---D---Y---E---F

提交XY 都触及特定行,而其他提交则触及 不是:

X: "Take a third parameter"
-MyFunc(1, 2);
+MyFunc(1, 2, 3);

Y: "Remove camelcase"
-MyFunc(1, 2, 3);
+my_func(1, 2, 3);

git-blame 将责怪Y 进行更改。
我希望能够忽略Y:提交的存在以及它所做的任何更改。
这与-S rev-list 不同,-S rev-list 指定了要处理的责任的提交列表。
我们仍会处理Y,但不要让指责“坚持”。

此补丁增加了用户忽略带有--ignore-rev=rev 的修订的功能,这可能会重复
他们可以指定一组完整对象名称的文件 revs,例如SHA-1 哈希,每行一个。
可以使用 blame.ignoreRevFile 配置选项指定单个文件 或--ignore-rev-file=file.
config 选项和命令行选项都可以重复多次。

空文件名"" 将清除以前处理的文件中的转速列表。
配置选项在命令行选项之前处理。

对于典型的用例,项目将维护包含执行大规模重新格式化的提交的修订的文件,并且他们的用户可以选择忽略该文件中的所有提交。

此外,用户可以使用--ignore-rev 选项进行一次性调查。
回到上面的例子,X 是对功能的实质性更改,但不是用户感兴趣的更改。
用户检查了X,但想找到对该行的先前更改 - 可能是引入该函数调用的提交。

要完成这项工作,我们不能简单地从 rev-list 中删除所有被忽略的提交。
我们需要区分Y 引入的更改,以便我们可以忽略它们。
我们让责任传递给Y,就像正常处理时一样。
Y 是目标时,我们确保Y 不会保留任何责备。
Y 负责的任何更改都会传递给其父级。请注意,我们通过所有替罪羊(父母)尝试正常推卸责任;在我们检查了所有的父母之前,我们不知道我们是否需要忽略提交。

blame_entry 将向上传递,直到我们找到一个具有影响这些行的差异块的提交。

一个问题是被忽略的提交确实做了一些更改,并且没有通用的解决方案可以在父提交中找到对应于被忽略提交中给定行的行。
这使得很难在被忽略的提交的差异中归因特定行 正确。

例如,忽略提交的父级有这个,比如在第 11 行:

commit-a 11) #include "a.h"
commit-b 12) #include "b.h"

提交X,我们将忽略它,交换这些行:

commit-X 11) #include "b.h"
commit-X 12) #include "a.h"

我们可以将该责备条目传递给父项,但第 11 行将归因于提交 A,即使“include b.h”来自提交 B
责备机制将在第 11 行查看父文件的视图。

ignore_blame_entry() 设置为允许使用替代算法来猜测每行的责任。
任何不属于父级的行都将继续归咎于被忽略的提交,就好像该提交没有被忽略一样。
即将发布的补丁能够检测到这些行并将它们标记在责备输出中。

现有的算法很简单:将每一行归咎于父 diff 块中对应的行。
超出此范围的任何行都与目标保持一致。

例如,忽略提交的父级有这个,比如在第 11 行:

commit-a 11) void new_func_1(void *x, void *y);
commit-b 12) void new_func_2(void *x, void *y);
commit-c 13) some_line_c
commit-d 14) some_line_d

在提交“X”之后,我们有:

commit-X 11) void new_func_1(void *x,
commit-X 12)                 void *y);
commit-X 13) void new_func_2(void *x,
commit-X 14)                 void *y);
commit-c 15) some_line_c
commit-d 16) some_line_d

提交 X 额外添加两行:13 和 14。
当前的guess_line_blames() 算法不会将这些归因于父级, 其差异块只有两行 - 不是四行。

当我们用当前算法忽略时,我们得到:

commit-a 11) void new_func_1(void *x,
commit-b 12)                 void *y);
commit-X 13) void new_func_2(void *x,
commit-X 14)                 void *y);
commit-c 15) some_line_c
commit-d 16) some_line_d

请注意,第 12 行归咎于 B,尽管 Bnew_func_2() 的提交,而不是 new_func_1()
即使guess_line_blames() 在父级中找到了一行,它仍然可能不正确。


git blame new documentation:

--ignore-rev <rev>::
Ignore changes made by the revision when assigning blame, as if the
change never happened.  Lines that were changed or added by an ignored
commit will be blamed on the previous commit that changed that line or
nearby lines.  This option may be specified multiple times to ignore
more than one revision.

--ignore-revs-file <file>:

忽略file 中列出的修订,它必须在same format as an fsck.skipList 中。
此选项可能会重复,并且这些文件将在使用 blame.ignoreRevsFile 配置选项指定的任何文件之后处理。
空文件名"" 将清除先前处理的文件中的转速列表。

git config new documentation:

blame.ignoreRevsFile:

忽略文件中列出的修订,每行一个未缩写的对象名称,git blame
# 开头的空格和 cmets 将被忽略。
此选项可能会重复多次。
空文件名将重置忽略的修订列表。
这个选项会在命令行选项--ignore-revs-file之前处理。


由于线检测并不总是完美的:

blame:为忽略或不可指责的行的输出添加配置选项

当忽略提交时,由于我们的启发式方法不准确,被指责的提交可能不对更改负责。
用户可能想知道特定行何时可能存在不准确的指责。

此外,guess_line_blames() 可能无法找到任何父提交 被忽略的提交触及的给定行。
那些“不可指责”的行仍然归咎于被忽略的提交。
用户可能想知道一行是否无可指责,这样他们就不会花时间调查他们知道无趣的提交。

这个补丁增加了两个配置选项来标记这两种类型的行 责备的输出。

第一个选项可以通过指定blame.markIgnoredLines 来识别被忽略的行。
设置此选项后,被忽略的提交以外的每个归咎于提交的责备行都标记有'? '

例如:

278b6158d6fdb (Barret Rhoden  2016-04-11 13:57:54 -0400 26)

显示为:

?278b6158d6fd (Barret Rhoden  2016-04-11 13:57:54 -0400 26)

'?' 被放置在提交之前,并且哈希值少了一个字符。

有时我们甚至无法猜测祖先提交触及了什么 行。
这些行“无可指责”。
第二个选项blame.markUnblamableLines 将用“*”标记该行

例如,假设我们忽略了 e5e8d36d04cbe,但我们无法责怪 这一行在另一个提交上:

e5e8d36d04cbe (Barret Rhoden  2016-04-11 13:57:54 -0400 26)

显示为:

*e5e8d36d04cb (Barret Rhoden  2016-04-11 13:57:54 -0400 26)

当这些配置选项一起使用时,被忽略的提交触及的每一​​行都将被标记为“?”或“*”。

这意味着git config man page 现在有:

blame.markUnblamables: 

git blame 的输出中使用“*”标记被忽略的修订更改的行。

blame.markIgnoredLines:

git blame 的输出中使用“?”标记由我们归因于另一个提交的忽略修订更改的行。


最后,改进git blame线路检测:

blame:添加指纹启发式以匹配忽略的行

此算法将用在文件的父版本中找到可能的候选行的启发式算法替换用于从忽略的提交中识别行的启发式算法。
实际替换发生在即将到来的提交中。

旧的启发式方法只是将目标中的行分配给父项中的相同行号(加上偏移量)。新功能使用指纹算法来检测线条之间的相似性。

新的启发式算法旨在准确匹配通过格式化工具(例如 clang-format 和 clang-tidy)机械地进行的更改。
这些工具会进行更改,例如拆分行以适应字符限制或更改标识符以适应命名约定。
启发式并不旨在匹配更广泛的重构更改,并且在这种情况下可能会产生误导性结果。

在大多数情况下,格式化工具会保留行顺序,因此启发式算法针对这种情况进行了优化。 (某些类型的更改会重新排序行,例如排序保持行内容相同,git blame -M 选项已经可以用来解决这个问题)。
依赖排序是有利的原因是由于源代码经常重复相同的字符序列,例如在一行中声明一个标识符,并在随后的几行中使用该标识符。
这意味着线条看起来非常相似,这在进行模糊匹配时会出现问题。依靠排序给了我们额外的线索来指向 真正的匹配。

启发式每次只对单个 diff 块更改进行操作
它为更改每一侧的每一行创建一个“指纹”

详细描述了指纹in the comment for struct fingerprint,但本质上是一行中字符对的多重集。

  • 启发式首先识别目标条目中的行,其指纹与父条目中的行指纹最明显匹配。
    在指纹相同匹配的情况下,线条的位置被用作平局。 - 启发式锁定最佳匹配,并从父条目中的行指纹中减去目标条目中的行指纹,以防止在该行的相同部分匹配其他行。
  • 然后它在匹配之前的块部分上递归地重复该过程,然后在匹配之后的块部分上重复该过程。

以下是指纹识别所产生差异的示例。
考虑一个有两个提交的文件:

    commit-a 1) void func_1(void *x, void *y);
    commit-b 2) void func_2(void *x, void *y);

在提交“X”之后,我们有:

    commit-X 1) void func_1(void *x,
    commit-X 2)             void *y);
    commit-X 3) void func_2(void *x,
    commit-X 4)             void *y);

当我们用旧算法进行责备忽略时,我们得到:

    commit-a 1) void func_1(void *x,
    commit-b 2)             void *y);
    commit-X 3) void func_2(void *x,
    commit-X 4)             void *y);

commit-b 被归咎于 2 而不是 3。

通过指纹算法,我们得到:

    commit-a 1) void func_1(void *x,
    commit-a 2)             void *y);
    commit-b 3) void func_2(void *x,
    commit-b 4)             void *y);

注意第 2 行可以与 commit-acommit-b 匹配,因为它是 与两行同样相似,但与 commit-a 匹配,因为它的 作为新行范围的一部分的位置更类似于作为旧行范围的一部分的commit-a
第 4 行也与这两行同样相似,但由于它出现在第 3 行之后,它将首先匹配,因此它无法与更早的行匹配。

有关更多示例,请参阅t/t8014-blame-ignore-fuzzy.sh,其中包含 示例父文件和目标文件以及父文件中的行号 必须匹配。

【讨论】:

  • git blame --ignore-rev 假设指定的提交进行了无意义的更改(例如重新格式化)。不幸的是,删除和添加文件都是相当大的变化,所以 --ignore-rev 在这里没有帮助,抱歉。
  • @MichaelPlatings 谢谢你的这一点。我已将您的评论包含在答案中以提高知名度。
  • 如果有人也希望在 Bitbucket 中获得支持,这里是功能请求的链接:jira.atlassian.com/browse/BSERV-12730
猜你喜欢
  • 2019-12-20
  • 1970-01-01
  • 2017-06-23
  • 2021-01-22
  • 1970-01-01
  • 2013-03-05
  • 2016-03-20
  • 2021-10-27
相关资源
最近更新 更多