Git 日志(--follow)无法显示重命名之外的历史记录

Rad*_*472 5 git version-control git-log

我尝试通过 gitlog 显示 git 中文件的完整历史记录。问题是该文件的父文件夹在历史记录中被重命名,我喜欢查看完整的历史记录。

git -log 文档说参数--follow-M显示使 git log 遵循重命名。

我尝试了 gitlog 参数的不同组合,例如

git log -M --oneline --all -- --follow newpath/my-file.php

git log -M --oneline --all -- newpath/my-file.php 乃至

git rev-list --all -- newpath/my-file.php --objects --in-commit-order | git log --no-walk --oneline --stdin

但无论我尝试什么,历史记录总是在文件的父文件夹被重命名的提交处结束。

我已经可以确认:

  • 在重命名提交中仅重命名了文件夹,文件的内容100%不变,因此git应该简单地发现旧路径上的文件和新路径上的文件是相同的并且已重命名。

  • git shot name-status对于重命名提交显示R100 oldpath/my-file.php newpath/my-file.php(确认文件内容 100% 相同)

  • 历史的“旧半部”和“新半部”似乎是正确的,都包括重命名commt

  • 当我运行git log -M --oneline --all -- --follow newpath/my-file.php最旧的提交时 0979744 renamed: oldpath/ -> newpath/

  • 当我运行git log -M --oneline --all -- --follow oldpath/my-file.php最新的提交时 0979744 renamed: oldpath/ -> newpath/

所以一切看起来就像我的 git 成功地理解了新路径中的文件和旧路径中的文件被重命名。谁能告诉我为什么即使我使用-M--follow选项,历史记录仍然会在重命名提交时中断?

tor*_*rek 5

注释中所述,该--follow选项必须位于指示选项列表末尾的独立选项之前。--

\n
\n

即使下面的重命名现在似乎可以工作,当我添加--grep="rename" --invert-grep删除“重命名”提交时,我得到 0 个结果

\n
\n

这是有道理的(但也是一个错误),1因为它的工作方式 --follow。这里的问题是 Git根本没有任何类型的文件历史记录。Git 拥有的只是存储库中的一组提交。 提交是历史:

\n
    \n
  • 每个提交都通过其丑陋的大哈希 ID 进行编号,该 ID 对于该特定提交来说是唯一的。任何Git 存储库2 \xe2\x80\x94 中没有其他提交\ xe2\x80\x94 具有该哈希 ID。

    \n
  • \n
  • 每次提交都有每个文件的完整快照。

    \n
  • \n
  • 每个提交还存储先前提交的哈希 ID\xe2\x80\x94,或者对于合并提交,存储两个或多个先前提交的哈希 ID。

    \n
  • \n
\n

所以这些数字将提交串在一起,向后:

\n
... <-F <-G <-H\n
Run Code Online (Sandbox Code Playgroud)\n

这里的大写字母代表实际的提交哈希 ID,Git 通过它来查找提交。每个提交都有一个“向后指向的箭头”,\xe2\x80\x94是前一个提交的存储哈希 ID\xe2\x80\x94,因此,如果我们只记住链中最后一次提交的哈希 ID ,我们可以让 Git 通过链向后工作。

\n

分支名称只是告诉 Git 哪个提交是该分支中的最后一个提交:

\n
             I--J   <-- feature1\n            /\n...--F--G--H\n            \\\n             K--L   <-- feature2\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,commitJ是功能分支之一的最后一次L提交,而 commit是另一个功能分支上的最后一次提交。请注意,向上的提交H都在两个分支上(并且很可能也在主分支或 master 分支上)。

\n

git log命令只是简单地处理一次提交,从您选择的“最后一次提交”开始。默认的“最后提交”是您现在签出的任何分支顶端的提交。这个过程是向后进行的:Git 从最后一次提交开始向后进行,一次提交一个。

\n

-Mto 选项是git diff的缩写--find-renames,可在 中启用重命名检测git diff。选项--followgit log的作用相同,但也采用单个文件git log的名称来查找。(提供选项使其在每个差异处使用重命名检测器,但由于它不是在寻找一个特定文件,因此只会影响输出的或样式。使用,正在寻找该一个特定文件,因为我们\一会儿就会看到。)-Mgit log-p--name-status--followgit log

\n

重命名检测器的工作方式如下:

\n
    \n
  • 你给 Git 两次提交,之前之后,或者旧的新的,或者,比如说,FG。(您可以将新提交放在左侧,将旧提交放在右侧,但git log它本身总是将旧提交放在左侧,将新提交放在右侧。)

    \n
  • \n
  • 您让 Git 比较这两个提交中的快照。

    \n
  • \n
  • 这些提交中的某些文件 100% 相同:它们具有相同的名称相同的内容。Git 的内部存储系统已经对这些文件进行了重复数据删除,这使得很容易git diff判断git log这些文件是否相同,因此可以在适当的情况下直接跳过它们。

    \n
  • \n
  • 其他文件具有相同的名称内容不同。默认情况下,Git 假定如果两个文件具有相同的名称\xe2\x80\x94,例如path/to/file.ext:请注意,嵌入的斜杠只是文件名的一部分\xe2\x80\x94,它们代表“同一文件” ,即使内容已更改。这样该文件就会被修改,从旧的/左侧的提交到新的/右侧的提交。如果您询问--name-status,您将得到M,已修改, 作为该文件名的状态。

    \n
  • \n
  • 有时,左侧提交有一个名为 的文件fileL,而右侧提交根本没有该文件。显然,该文件在从旧(左)到新(右)的更改中被删除。有了--name-status你就会获得D地位。

    \n
  • \n
  • 有时,右侧提交有一个名为 的文件fileR,而左侧提交则没有。显然,该文件是新添加的--name-status您将获得A该状态。

    \n
  • \n
  • 但是如果fileL左边和fileR右边应该被认为是“同一个文件”怎么办?也就是说,如果我们重命名 fileL为呢fileR?这就是 Git 的重命名检测器发挥作用的地方。给定这样的删除/添加对,也许的内容与的内容足够fileL接近或完全相同。如果:fileR

    \n
      \n
    • 您已经打开了重命名检测器,它实际上会执行此内容检查,并且
    • \n
    • 内容检查说“完全相同”(由于重复数据删除,很快就能知道)或“足够相似”(慢得多,但由相同的重命名检测器开关启用),
    • \n
    \n

    然后\xe2\x80\x94并且只有\xe2\x80\x94Git才会声明它fileL重命名fileR. 输出--name-status将包括R相似性索引值和两个文件名,而不是在左侧和右侧提交中匹配的单个文件名。

    \n
  • \n
\n

现在您已经了解了重命名检测器的工作原理\xe2\x80\x94并且必须将其打开\xe2\x80\x94,您可以看到它是如何--follow工作的。请记住,使用git log,您可以给它一个文件名,并告诉它不要显示修改该特定文件的提交。3 结果是您只能看到修改该文件的提交访问的所有提交集合的子集git log。假设您运行git log --follow -- newpath/my-file.php

\n
    \n
  • git log像往常一样,一次一次地回顾历史。

    \n
  • \n
  • 在每次提交时,它都会将此提交(较新的,在右侧)与其父项(较旧的,在左侧)进行比较。如果没有--follow它,仍然会执行此操作,但只需查看您命名的文件是否已更改M状态,来自git diff --name-status)或添加删除AD)。4 但对于--follow,它也会寻找R状态。

    \n
  • \n
  • 如果文件更改\xe2\x80\x94hasM或状态\xe2\x80\x94打印出A此提交,但如果没有,它只是抑制打印输出。使用,我们添加状态,如果发生这种情况,还添加两个文件名。如果状态,那么,之前一直在找。但现在它知道,从提交开始,该文件被称为. (再次注意,这里没有文件夹。文件的名称是整个字符串,包括所有斜杠。)Dgit log --followR Rgit lognewpath/my-file.phpoldpath/my-file.php

    \n
  • \n
\n

因此,使用--follow\xe2\x80\x94 打开重命名检测器 \xe2\x80\x94git log可以获得重命名状态,因此会看到文件被重命名。它还正在寻找一个特定的文件名,在本例中为newpath/my-file.php. 如果它检测到重命名,git log不仅会打印提交,还会更改它正在查找的名称。现在,它不是newpath/my-file.php从父提交向后查找 ,而是寻找oldpath/my-file.php

\n
\n

1代码--follow本身……不太好;整个实现需要重新设计,这可能比我正在考虑的更简单的黑客方法更好地解决这个问题。

\n

2从技术上讲,其他一些 Git 存储库可能有一个重复使用该哈希 ID 的不同提交,只要您从未将这两个提交相互引入即可。但实际上,你找不到这样的人。

\n

3--follow选项只能跟随一个文件名。如果没有--follow,您可以给出git log多个名称,或者一个“目录”的名称,即使 Git 根本不存储目录。如果没有--follow代码git log,则对通用路径规范进行操作。 使用 时 --follow,它仅处理一个文件名。这是 Git 在这里使用的算法所施加的限制。

\n

4它也可以有T, 类型改变,我认为这很重要。完整的状态字母集是ABCDMRTUXX表示 Git 中的错误,U只能在未完成的合并期间发生,B只能git diff-B选项一起发生,并且C只能R在启用--find-copiesand --find-renames(-C-M) 选项时发生。请注意,可能会根据您的设置git diff自动启用,但不会。--find-renamesdiff.renamesgit log

\n
\n

中的错误--follow

\n

从 的输出显示中删除一些提交的过程git log称为历史简化文档中有一个很长的部分描述了这一点,它以这个相当奇怪的声明开始:

\n
\n

有时您只对历史记录的一部分感兴趣,例如\n修改特定 <path> 的提交。但是历史简化有两个部分,一个部分是选择提交,另一个部分是如何做到这一点,因为有多种策略可以简化历史。

\n
\n

这个奇怪的措辞\xe2\x80\x94“一个部分是选择提交,另一个是如何做到这一点”\xe​​2\x80\x94 试图表达的是,启用历史简化后,git log有时甚至不会走路一些承诺。特别是,考虑合并提交,其中两个提交字符串组合在一起:

\n
          C--...--K\n         /         \\\n...--A--B           M--N--O   <-- branch\n         \\         /\n          D--...--L\n
Run Code Online (Sandbox Code Playgroud)\n

要显示所有提交,git log必须依次遍历 commit O、 then NMthen 、 andKL(按某种顺序),然​​后是之前的所有提交以及返回到andK之前的所有提交,然后在提交时重新加入单个线程并继续从那里,向后。LCDB

\n

但是,如果我们不打算显示所有提交,也许 \xe2\x80\x94只是也许\xe2\x80\x94at commit M,我们可以返回到仅提交K或仅提交L并忽略提交的另一“面”完全合并。这将节省大量工作和时间,并避免向您展示不相关的内容。这通常是一件非常好的事情。

\n

然而,对于 来说--follow,这通常是一件非常糟糕的事情。--follow这是问题之一:有时 Git 在进行这种简化时会走“错误的路”。添加--full-history可以避免这种情况,但我们立即遇到了另一个问题。该--follow选项只有一个文件名。如果我们在提交的两个分支之一中进行了重命名,但在另一个分支中没有重命名,并且git log首先沿着重命名分支进行重命名,则当它沿着另一分支进行时,它可能会查找错误的名称。

\n

如果文件在两条腿中都被重命名,因此它从Mback 重命名为K Mback 重命名为L,或者如果 Git 碰巧首先沿着正确的腿走,而您不关心另一条腿,一切正常。但这是需要注意的事情。(这不是使用 时遇到的问题--grep,否则也会在不使用时发生--grep。)

\n

认为你看到的错误是--grep“太早”触发了,可以说。该选项的工作原理是从\ 的输出中--grep消除其提交消息中具有 ( ) 或缺少 (没有) 某些特定文本的任何提交。然后,假设您的选项跳过了重命名 commit\xe2\x80\x94(导致知道使用名称\xe2\x80\x94)。Git 将看不到状态,并且不知道将名称从 更改为。因此,将继续寻找路径,并且您将仅获得那些既满足 grep 标准修改具有新名称的文件的提交。git log--invert-grep--grep--invert-grepgit log --followoldpath/my-file.php--grepRnewpath/my-file.phpoldpath/my-file.phpgit log --follow

\n

无论如何,这个错误可以通过git log --follow运行 diff 引擎来修复,即使它会因为其他原因跳过提交。但更普遍的--follow是需要完全重写:它有一堆奇怪的特殊情况代码通过 diff 引擎线程只是为了让这个案例工作。它需要处理多个路径名和/或路径规范,并使用--reverse和其他选项。它需要一种方法将旧名称和新名称堆叠到提交路径上,以便通过沿着--full-history合并的两条腿向下走,它知道要寻找哪条路径。请注意,这还有其他含义:如果在合并的两条腿上都有不同的重命名怎么办? 如果有人在合并中手动修复了重命名/重命名冲突,我们该如何处理?

\n