为什么git blame不遵循重命名?

Eug*_*kov 41 git blame git-blame

$ pwd
/data/mdi2/classes

$ git blame -L22,+1 -- utils.js
99b7a802 mdi2/utils.js (user 2015-03-26 21:54:57 +0200 22)  #comment

$ git blame -L22,+1 99b7a802^ -- utils.js
fatal: no such path mdi2/classes/utils.js in 99b7a802^
Run Code Online (Sandbox Code Playgroud)

您已经注意到,该文件位于该提交的不同目录中

$ git blame -L22,+1 99b7a802^ -- ../utils.js
c5105267 (user 2007-04-10 08:00:20 +0000 22)    #comment 2
Run Code Online (Sandbox Code Playgroud)

尽管如此

The origin of lines is automatically followed across whole-file renames (currently there is no option to turn
       the rename-following off)
Run Code Online (Sandbox Code Playgroud)

责备不遵循重命名.为什么?

更新:简答

git blame 按照重命名但不是 git blame COMMIT^ -- <filename>

但是,通过批量重命名和大量历史记录手动跟踪文件重命名太难了.我认为,必须修复此行为以静默跟随重命名git blame COMMIT^ -- <filename>.或者,至少--follow必须实施,所以我可以:git blame --follow COMMIT^ -- <filename>

更新2:这是不可能的.参见下文.

来自 Junio C Hamano的MAILLIST回答

git blame 按照重命名但不是 git blame COMMIT^ -- <filename>

假设您的v1.0版本中有文件A和文件B.

未来六个月,代码进行了大量重构,您不需要分别对这两个文件的内容进行重构.你已经删除了A和B以及它们现在在文件C中的大部分内容.这是当前的状态.

git blame -C HEAD -- C
Run Code Online (Sandbox Code Playgroud)

可以按照两者的内容就好了,但是如果你 允许说的话

git blame v1.0 -- C
Run Code Online (Sandbox Code Playgroud)

它甚至意味着什么?C根本不存在v1.0.您是否要求遵循A的内容,或者B?当你在这个命令中告诉它C时,你是怎么告诉你的意思是A而不是B?

"git blame"遵循内容动作,从不以任何特殊的方式对待"重命名",因为认为重命名在某种程度上是特殊的,这是一个愚蠢的事情;-)

你从命令行告诉从命令开始挖掘什么内容的方法是给出起始点提交(默认为HEAD,但你可以给出COMMIT ^作为你的例子)和起始点的路径.因为将C告诉Git然后神奇地让它猜测你在某些情况下意味着A而在其他情况下意味着B是没有任何意义的.如果v1.0没有C,那么唯一明智的做法就是退出而不是猜测(并且不告诉用户它是如何猜测的).

tor*_*rek 33

git blame 确实遵循重命名(git log如果你给它的话--follow).问题在于它重命名的方式,这是一个不太彻底的黑客攻击:因为它一次退回一个提交(从每个子节点到每个父节点),它会产生差异 - 你可以做同样的差异手动制作:

git diff -M SHA1^ SHA1
Run Code Online (Sandbox Code Playgroud)

- 并检查此差异是否检测到重命名.1

这一切都很好,但它意味着git blame要检测重命名,(a)git diff -M必须能够检测到它(幸运的是这里就是这种情况)并且 - 这就是导致你出现问题的原因 - 它必须跨过重命名.

例如,假设提交图看起来像这样:

A <-- B <-- ... Q <-- R <-- S <-- T
Run Code Online (Sandbox Code Playgroud)

其中每个大写字母代表一个提交.进一步假设在commit中重命名了一个文件R,以便在提交时R通过T它具有名称,newname而在提交AQ它具有名称oldname.

如果你运行git blame -- newname的程序开始于T,比较ST,比较RS,并比较QR. 它进行比较QR,git blame发现名称变化,并开始寻找oldname在提交Q和更早,所以当它进行比较PQ它比较文件oldname,并oldname在这两个提交.

如果,另一方面,你运行git blame R^ -- newname(或者git blame Q -- newname),使该序列开始于提交Q,没有任何文件newname在提交时,并没有进行比较时,没有重命名PQ,和git blame简单地放弃.

诀窍在于,如果您从文件具有先前名称的提交开始,则必须为git指定旧名称:

git blame R^ -- oldname
Run Code Online (Sandbox Code Playgroud)

然后它再次起作用.


1git diff文档中,您将看到有一个-M选项可控制如何 git diff检测重命名.该blame代码修改这一点(事实上的确两遍,一个与-M关闭,第二与-M开启),并使用它自己的(不同的)-M选项有所不同的目的,但最终它使用相同的代码.


[ 编辑以添加对评论的回复(不适合作为评论本身)]:

是否可以向我显示文件的任何工具重命名为:git renames <filename> SHA date oldname-> newname

不完全,但git diff -M接近,可能足够接近.

我不确定你的"SHA日期"是什么意思,但git diff -M允许你提供两个SHA-1并比较左右对比.添加--name-status以获取文件名和处置.因此git diff -M --name-status HEAD oldsha1 可能会报告要转换HEADoldsha1,git认为您应该R枚举文件并将旧名称报告为"新"名称.例如,在git存储库本身中,有一个当前名为的文件Documentation/giteveryday.txt,其名称略有不同:

$ git diff -M --name-status HEAD 992cb206
M       .gitignore
M       .mailmap
[...snip...]
M       Documentation/diff-options.txt
R097    Documentation/giteveryday.txt   Documentation/everyday.txt
D       Documentation/everyday.txto
[...]
Run Code Online (Sandbox Code Playgroud)

如果这是你关心的文件,那你很好.这里的两个问题是:

  • 找到SHA1:992cb206来自哪里?如果您已经拥有SHA-1,那很容易; 如果没有,git rev-list是SHA1查找工具; 阅读其文件;
  • 并且事实上,通过每次提交一系列重命名,一次提交一个提交git blame,可能会产生完全不同的答案,而不是将一个更晚的commit(HEAD)与更早的提交(992cb206或其他)进行比较.在这种情况下,它是相同的,但这里的"相似性指数"是97中的97.如果在一些中间步骤中被修改得更多,那么相似性指数可能会低于50%......然而,如果我们只是比较修订一点992cb206,以992cb206(如git blame会),也许这两个文件之间的相似性指数可能会更高.

需要(和缺少)的是git rev-list它自己实现--follow,所以所有在git rev-list内部使用的命令- 即大多数命令不仅仅在一个修订版上工作 - 都可以解决问题.一路上,如果它在另一个方向上工作将是很好的(目前--follow只是较新到较旧,即工作正常git blame并且git log只要您不要求最早的历史记录就可以正常工作--reverse).