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

时间:2015-04-06 09:05:56

标签: 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^

正如您所注意到的,该文件位于该提交的不同目录中

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

尽管有文件

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

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

更新:简短回答

git blame关注重命名,但不关注git blame COMMIT^ -- <filename>

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

UPDATE2:这是不可能的。请阅读以下内容。

来自MAILLIST的回答作者:Junio C Hamano

  

git blame关注重命名,但不关注git blame COMMIT^ -- <filename>

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

在接下来的六个月里,代码被重构了很多,你做到了 不需要分别对这两个文件的内容。你有 删除了A和B以及它们现在的大部分内容现在都在文件C中 现状。

git blame -C HEAD -- C

可能会跟随两者中的内容,但是如果你 允许说

git blame v1.0 -- C

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

&#34; git blame&#34;遵循内容动作,从不对待&#34;重命名&#34;在 任何特殊的方式,因为认为重命名是一个愚蠢的事情 不知怎的特别; - )

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

2 个答案:

答案 0 :(得分:30)

git blame 会重命名(如果您git log提供--follow,也会如此git diff -M SHA1^ SHA1 。问题在于方式它是否重命名,这是一个非常彻底的黑客攻击:因为它一次退回一个提交(从每个子节点到每个父节点),它会产生差异 - 您可以手动使用相同类型的差异:

git blame

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

这一切都很好,但这意味着git diff -M要检测重命名,(a)A <-- B <-- ... Q <-- R <-- S <-- T 必须检测它(幸运的是这里就是这种情况)并且 - 这就是导致你出现问题的原因 - 它必须跨越重命名

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

R

其中每个大写字母代表一个提交。进一步假设在提交R中重命名了一个文件,因此在提交Tnewname时,它在提交A到{{1}时具有名称Q它的名称为oldname

如果您运行git blame -- newname,则序列从T开始,比较ST,比较RS,并进行比较QR比较QR时,git blame会发现名称更改,并开始在提交oldname中查找Q,之前,所以当它比较PQ时,它会比较这两个提交中的文件oldnameoldname

另一方面,如果您运行git blame R^ -- newname(或git blame Q -- newname)以使序列从提交Q开始,则该提交中没有文件newname ,比较PQ时没有重命名,而git blame只是放弃了。

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

git blame R^ -- oldname

然后它再次起作用。


1 git diff documentation中,您会看到有一个-M选项可控制 git diff如何检测重命名。 blame代码会稍微修改一下(实际上会执行两次传递,一次关闭-M,另一次开启-M)并使用自己的(不同){{1 }}选项用于某些不同的目的,但最终它使用相同的代码。


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

  

是否有任何可以显示文件的工具重命名为:git renames&lt; filename&gt; SHA date oldname-&gt; newname

不完全,但-M接近,可能已经足够接近。

我不确定你的意思&#34; SHA日期&#34;在这里,git diff -M允许您提供两个SHA-1并比较左右对比。添加git diff -M以获取文件名和处置。因此,--name-status 可能报告要从git diff -M --name-status HEAD oldsha1转换为HEAD,git认为您应该oldsha1对文件进行命名,并将旧名称报告为&#34;新&#34;名称。例如,在git存储库本身中,有一个当前名为R的文件,其名称略有不同:

Documentation/giteveryday.txt

如果那是您关心的文件,那么您就是好人。这里的两个问题是:

  • 找到SHA1:$ 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 [...] 来自哪里?如果您已经拥有SHA-1,那很简单;如果没有,992cb206是SHA1查找工具;阅读其文件;
  • 并且事实上,通过每次提交一次重命名,一次提交一次,如git rev-list所做的那样,可能会产生完全不同的答案,而不是比较后期提交(git blame)与更早提交(HEAD或其他)。在这种情况下,它出现相同,但相似性指数&#34;如果在一些中间步骤中修改得更多,那么相似性指数可能会降到50%以下......但是,如果我们要比较修订版只是 little < / em>在992cb206992cb206之后(如992cb206所示),这两个文件之间的相似性索引可能更高。

git blame本身需要(和缺少)实现git rev-list的所有内容,以便所有在内部使用--follow的命令 - 即大多数命令都可以使用git rev-list而不只是一个修订 - 可以做到这一点。一路上,如果它在另一个方向上工作会很好(目前--follow只是更新到更老,即,git blame工作正常,并且git log正常工作很久你不会先--reverse)询问最早的历史。

答案 1 :(得分:1)

最新的git有趣的命令。在您的配置旁边添加:

[alias]
    follow= "!sh -c 'git log --topo-order -u -L $2,${3:-$2}:"$1"'" -

现在你可以:

$git follow <filename> <linefrom> [<lineto>]

您将看到每个提交更改<filename>中的指定行。

您也可以对--follow命令的git log选项感兴趣:

  

继续列出重命名以外的文件历史记录(仅适用于单个文件)。

如果您对复制检测感兴趣,请使用-C

  

检测副本以及重命名。另请参阅--find-copies-harder。如果指定了n,则其含义与-M相同。

-C将在同一次提交中查看不同的文件。如果要检测代码是从此提交中未更改的不同文件中获取的。然后你应该提供--find-copies-harder选项。

  

出于性能原因,默认情况下,-C选项仅在相同变更集中修改了副本的原始文件时才查找副本。此标志使命令检查未修改的文件作为副本源的候选者。对于大型项目来说,这是一项非常昂贵的操作,因此请谨慎使用。给予多个-C选项具有相同的效果。

<强> UPD
我改进了这个别名:

[alias]
    follow = "!bash -c '                                                 \
        if [[ $1 == \"/\"* ]]; then                                      \
            FILE=$1;                                                     \
        else                                                             \
            FILE=${GIT_PREFIX}$1;                                        \
        fi;                                                              \
        echo \"git log --topo-order -u -L $2,${3:-$2}:\\\"$FILE\\\"\";   \
        git log --topo-order -u -L $2,${3:-$2}:\"$FILE\";                \
    ' --"

现在,您可以跟踪指定的行范围的更改方式:

git follow file_name.c 30 35

注意:不幸的是,git没有考虑工作目录的变化。因此,如果您对文件进行本地更改,则必须先将其存储,然后才能follow更改