git log --follow --graph 命令跳过了某些提交记录

5

设置

git version 2.11.0.windows.1

以下是一个bash代码片段,可以用来复制我的测试仓库:
git init

# Create a file
echo Hello > a.txt
git add a.txt
git commit -m 'First commit'

# Change it on one branch
git checkout -b feature
echo Hi > a.txt
git commit -am 'Change'

# Rename it on the other
git checkout master
git mv a.txt b.txt
git commit -m 'Move'

# Merge both changes
git merge --no-edit feature

在最后,git log --graph --pretty=oneline --abbrev-commit 命令会输出以下内容:
*   06b5bb7 Merge branch 'feature'
|\
| * 07ccfb6 Change
* | 448ad99 Move
|/
* 31eae74 First commit

问题

现在,我想要获取b.txt文件的完整日志(例如-b.txt)。
执行命令:git log --graph --pretty=oneline --abbrev-commit --follow -- b.txt,输出结果如下:

...
* | 1a07e48 Move
|/
* 5ff73f6 First commit

正如您所看到的,尽管Change提交确实修改了文件,但该提交未列出。
我认为这是由于--graph隐式使用--topo-order导致的,因为添加--date-order会将提交恢复,但这可能只是偶然。
此外,添加-m显示合并提交(很好),以及Change提交,但然后合并提交会重复。
*   36c80a8 (from 1a07e48) Merge branch 'feature'
|\
| | 36c80a8 (from 05116f1) Merge branch 'feature'
| * 05116f1 Change
* | 1a07e48 Move
|/
* 5ff73f6 First commit

问题

我遇到了奇怪的行为,需要解释一下我错过了什么?
如何清晰地显示更改文件的所有提交记录,并跟踪重命名过程?

1个回答

10

您正在遭受git log的廉价和低劣实现的影响,加上git log通常甚至不会查看合并内部。

从根本上讲,--follow在内部工作是通过更改它正在查找的文件名来完成的。它不记住两个名称,因此当线性化算法(通过优先级队列的广度优先搜索)沿着合并的另一条腿向下走时,它就有了错误的名称。您正确地指出了提交访问顺序很重要,因为当Git推断出重命名时,Git会更改它正在搜索的文件名。

在这个图中(看起来您运行了脚本几次,因为哈希值发生了变化-这里的哈希值来自第一个样本):

*   06b5bb7 Merge branch 'feature'
|\
| * 07ccfb6 Change
* | 448ad99 Move
|/
* 31eae74 First commit

git log 命令会访问提交记录 06b5bb7,并将 448ad9907ccfb6 加入队列。使用默认的拓扑排序,下一个要访问的是 448ad99,检查差异,并看到了重命名。现在它正在寻找 a.txt 而不是 b.txt。选择了提交记录 448ad99,因此 git log 会将其打印到输出中,并将 31eae74 添加到访问队列中。接下来,Git 访问 07ccfb6,但是现在它正在寻找 a.txt,因此该提交记录未被选中。Git 将 31eae74 添加到访问队列中(但已经存在,因此这是一个无操作)。最后,Git 访问 31eae74;将该提交记录的树与空树进行比较,Git 发现添加了 a.txt,因此选择了该提交记录。

请注意,如果 Git 在访问 448ad99 之前先访问了 07ccfb6,它会同时选择两者,因为一开始它正在寻找 b.txt

-m标志通过将合并拆分为两个单独的内部“虚拟提交”(具有相同的树,但在其“名称”中添加了(from ...)以便能够区分哪个虚拟提交是由哪个父提交生成的)来工作。这样会保留拆分合并的两个版本,并查看它们的差异(因为拆分此合并的结果是两个普通的非合并提交)。现在 - 请注意,这将使用具有第二个示例中的新哈希的新存储库 - Git 访问提交 36c80a8 (from 1a07e48),将1a07e4836c80a8进行比较,看到对b.txt的更改并选择该提交,并将1a07e48放入访问队列。接下来,它访问提交36c80a8 (from 05116f1),将05116f136c80a8进行比较,并将05116f1放入访问队列。从这里开始,其余的操作就很明显了。

如何干净地显示更改文件的所有提交,跟踪重命名?

对于 Git 来说,答案是你不能,至少不能使用 Git 内置的功能。

您可以通过在git log命令中添加--cc-c来使其更加详细。这将使git log查看合并提交的内部内容,执行Git所称的combined diff。但是这并不一定有效,因为在文档的不同部分中隐藏了关键句子

请注意,combined diff仅列出从所有父级修改的文件。

这是我使用--cc时得到的结果(请注意,...实际上存在于git log的输出中):

$ git log --graph --oneline --follow --cc -- b.txt
*   e5a17d7 (HEAD -> master) Merge branch 'feature'
|\  
| | 
... 
* | 52e75c9 Move
|/  
|   diff --git a/a.txt b/b.txt
|   similarity index 100%
|   rename from a.txt
|   rename to b.txt
* 7590cfd First commit
  diff --git a/a.txt b/a.txt
  new file mode 100644
  index 0000000..e965047
  --- /dev/null
  +++ b/a.txt
  @@ -0,0 +1 @@
  +Hello

然而,从根本上讲,您需要使git log在合并提交时更加了解文件重命名,并使其在任何使用旧文件名的分支中查找旧名称,在任何使用新名称的分支中查找新名称。这将要求git log在每个合并中内部使用(大多数)-m选项,即将每个合并拆分为N个单独的差异,每个父级一个,以查找哪些分支有哪些重命名,然后保持列表,说明在哪些合并的哪些分支下使用哪个名称。但是当分支再次汇聚,即当合并的多个分支(在我们的反向方向上成为分叉)重新合并时,不清楚哪个名称是正确的名称!


好的,无论如何感谢您对该问题进行全面的分析 :) - Quentin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接