【问题标题】:Why git blame does not follow renames?为什么 git blame 不跟随重命名?
【发布时间】:2015-06-10 16:10:00
【问题描述】:
$ 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:这是不可能的。阅读下文。

来自邮递员的答复,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?

"git blame" 跟随内容移动,从不处理"重命名" 任何特殊的方式,因为认为重命名是一件愚蠢的事情 有点特别;-)

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

【问题讨论】:

    标签: git blame git-blame


    【解决方案1】:

    git blame 是否跟随重命名(如果你给它--followgit log 也是如此)。问题在于它遵循重命名的方式,这是一种不太彻底的hack:当它一次退回一个提交(从每个孩子到每个父母)时,它会产生差异——您可以手动制作的相同类型的差异:

    git diff -M SHA1^ SHA1
    

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

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

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

    A <-- B <-- ... Q <-- R <-- S <-- T
    

    每个大写字母代表一个提交。进一步假设在提交R 中重命名了一个文件,因此在提交RT 中它的名称为newname,而在提交AQ 中它的名称为oldname

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

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

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

    git blame R^ -- oldname
    

    然后一切都会再次运行。


    1git diff documentation 中,您将看到有一个-M 选项控制如何 git diff 检测重命名。 blame 代码稍微修改了这一点(实际上做了两次传递,一次关闭-M,另一次打开-M)并使用自己的(不同的)-M 选项用于不同的目的,但最终它使用的是相同的代码。


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

    是否有任何工具可以显示文件重命名,例如:git renames SHA date oldname->newname

    不完全是,但git diff -M 很接近,并且可能足够接近。

    我不确定您在这里所说的“SHA 日期”是什么意思,但git diff -M 允许您提供两个 SHA-1 并比较左右。添加--name-status 以获取文件名和配置。因此git diff -M --name-status HEAD oldsha1 可能 报告要从HEAD 转换为oldsha1,git 认为您应该Rename 一个文件并将旧名称报告为“新”名称。例如,在 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
    [...]
    

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

    • 找到一个 SHA1:992cb206 是从哪里来的?如果您已经拥有 SHA-1,那很容易;如果不是,git rev-list 是 SHA1 查找工具;阅读其文档;
    • 事实上,通过每次提交进行一系列重命名,一次提交一个提交,就像git blame 所做的那样,与比较晚提交 (HEAD) 和早得多的提交相比,可能会产生完全不同的答案(992cb206 或其他)。在这种情况下,结果是一样的,但是这里的“相似度指数”是 100 分中的 97 分。如果在某些中间步骤中对其进行更多修改,那么相似度指数可能会低于 50% ......然而,如果我们要比较992cb206992cb206 之后的修订一点(就像git blame 那样),那么这两个文件之间的相似度指数可能会更高。

    git rev-list 本身需要(并且缺少)实现--follow,以便所有在内部使用git rev-list 的命令(即,在不止一个版本上工作的大多数命令)都可以解决问题。一路走来,如果它在另一个方向上工作会很好(目前--follow 只是新旧版本,即,与git blame 一起工作正常,与git log 一起工作正常,只要你不问对于最古老的历史,首先使用--reverse)。

    【讨论】:

    • 是否有任何工具可以显示文件重命名,例如:git renames &lt;filename&gt; SHA date oldname->newname
    • 查看添加的编辑;另请注意,git blame 本身会显示更新的文件名。
    • 使用该命令我试图回答当 重命名时什么是 DATE 和什么是 SHA-1。所以我可以把 oldname 和 sha1 作为责备参数。但是如果 --follow 将按照你的建议实施,我根本不需要这个命令
    【解决方案2】:

    **参见 UPD。现在你可以关注重命名的文件了

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

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

    现在你可以:

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

    您会看到每个提交都会更改&lt;filename&gt; 中的指定行。

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

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

    如果您对复制检测感兴趣,请使用-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\\\" $4 \";   \
        git log -w -b -p --ignore-blank-lines --topo-order -u -L $2,${3:-$2}:\"$FILE\" $4;\
    ' --"
    

    现在您可以跟踪指定的行范围是如何更改的:

    git follow file_name.c 30 35
    

    即使您可以继续关注以提交 (@arg4) 开头的不同文件

    git follow old_file_name.c 30 35 85ce061
    

    85ce061 - 是文件被重命名的地方

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

    【讨论】:

      猜你喜欢
      • 2014-12-05
      • 2021-05-20
      • 2019-06-10
      • 2014-06-04
      • 2020-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-22
      相关资源
      最近更新 更多