Git日志(--follow)无法显示重命名之外的历史记录
Posted
技术标签:
【中文标题】Git日志(--follow)无法显示重命名之外的历史记录【英文标题】:Git log (--follow) not working to show histrory beyond renames 【发布时间】:2021-02-03 20:10:57 【问题描述】:我尝试通过 gitlog 在我的 git 中显示文件的完整历史记录。 问题是这个文件的父文件夹在历史记录中被重命名了,我想查看完整的历史记录。
git-log documentation 表示参数 --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
但无论我尝试什么,历史总是在文件的父文件夹被重命名的提交处结束。
我已经可以确认:
rename commit 中只有文件夹被重命名,文件内容100%不变,所以git应该简单地发现旧路径上的文件和新路径上的文件是相同的,然后重命名了。
git shot name-status
用于重命名提交显示 R100 oldpath/my-file.php newpath/my-file.php
(确认文件内容 100% 相同)
历史的“旧一半”和“新一半”似乎是正确的,都包括rename-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
选项,重命名提交的历史仍然会中断?
【问题讨论】:
如果:git log -M --oneline --all --follow -- newpath/my-file.php
怎么办? --
标记选项结束。
我测试了我移动文件夹中一些文件的更改命令,将--follow
移动到--
后面似乎是解决方案
即使它下面的重命名现在似乎工作了,当我添加 --grep="rename" --invert-grep
以删除“重命名”提交时,我得到 0 个结果
嗯,这是另一个问题。但也许解决方案是一样的:您不能在--
部分之后放置任何选项。
【参考方案1】:
作为noted in comments,--follow
选项必须在指示选项列表结束的独立--
之前。
即使跟随重命名现在似乎工作了,当我添加
--grep="rename" --invert-grep
以删除“重命名”提交时,我得到 0 个结果
这是有道理的(但这是一个错误),1 因为 方式 --follow
有效。这里的问题是 Git 根本没有任何类型的文件历史。 Git 所拥有的只是存储库中的一组提交。 提交是历史记录:
每个提交都通过其大而丑陋的哈希 ID 进行编号,该 ID 对于该特定提交是唯一的。 any Git 存储库2 中没有其他提交具有该哈希 ID。
每次提交都有每个文件的完整快照。
每个提交还存储先前提交的哈希 ID,或者,对于合并提交,两个或多个先前提交。
所以这些数字串在一起,向后提交:
... <-F <-G <-H
这里的大写字母代表实际的提交哈希 ID,Git 通过它找到提交。每个提交都有一个“向后指向的箭头”——存储的 previous 提交的哈希 ID——所以如果我们能记住 last 的哈希 ID > 在链中提交,我们可以让 Git 在链中向后工作。
branch name 只是告诉 Git 哪个提交是该分支中的 last 提交:
I--J <-- feature1
/
...--F--G--H
\
K--L <-- feature2
这里,commit J
是 last 提交功能分支之一,commit L
是 last 另一个提交。请注意,通过H
的提交都在两个分支上(很可能也在主分支或主分支上)。
git log
命令简单地处理提交,一次一个,从您选择的任何“最后一次提交”开始。默认的“最后一次提交”是您现在签出的任何分支的尖端。这个过程是逆向工作的:Git 从最后一次提交开始,然后逆向工作,一次提交一个。
git diff
的-M
选项是--find-renames
的缩写,可在git diff
中启用重命名检测。 --follow
选项对git log
的作用与git log
相同,但也采用单个文件的名称 来查找。 (将-M
选项赋予git log
使其在每个差异中使用重命名检测器,但由于它不是在寻找一个特定的 文件,这只会影响-p
或--name-status
样式输出。--follow
,git log
正在寻找那个特定的文件,我们稍后会看到。)
重命名检测器是这样工作的:
你给 Git 两次提交,before 和 after 或 old 和 new 或者说,@987654346 @ 和 G
。 (您可以将新提交放在左侧,将旧提交放在右侧,但git log
本身总是将旧提交放在左侧,新提交放在右侧。)
您让 Git 比较这两个提交中的快照。
这些提交中的某些文件是 100% 相同的:它们具有相同的名称和相同的内容。 Git 的内部存储系统已经对这些文件进行了重复数据删除,这使得git diff
或git log
很容易确定这些文件相同,因此它可以在适当的时候直接跳过它们。
其他文件具有相同的名称但不同的内容。 Git 假设,默认情况下,如果两个文件具有相同的名称——例如path/to/file.ext
:注意嵌入的斜杠只是文件名的一部分——它们代表“相同的文件”,即使内容发生了变化。所以该文件被修改,从旧的/左侧提交到新的/右侧提交。如果您要求--name-status
,您将获得M
,已修改,作为该文件名的状态。
有时,左侧提交有一个名为fileL
的文件,而右侧提交根本没有该文件。显然,在从旧(左)到新(右)的变化中,该文件被删除。使用--name-status
,您将获得D
的状态。
有时,右侧提交有一个名为 fileR
的文件,而左侧提交则没有。显然,该文件是新添加的,使用--name-status
,您将获得A
的状态。
但是如果左边的fileL
和右边的fileR
应该被认为是“同一个文件”呢?也就是说,如果我们重命名 fileL
到 fileR
会怎样?这就是 Git 的重命名检测器的用武之地。鉴于这样的删除/添加对,可能 fileL
的 content 足够接近或完全相同内容来自fileR
。如果:
然后仅然后-Git 将声明fileL
被重命名 成为fileR
。 --name-status
输出将包括 R
、相似性索引 值和 两个 文件名,而不是在左侧和右侧都匹配的单个文件名提交。
现在您知道重命名检测器的工作原理——并且必须打开——你可以看到--follow
的工作原理。请记住,使用git log
,您可以给它一个文件名,并告诉它不显示提交不要修改该特定文件。3 结果是您只看到确实修改该文件的提交:git log
访问的所有提交集合的子集。所以假设你运行git log --follow -- newpath/my-file.php
:
git log
遍历历史,一次提交一次,像往常一样倒退。
在每次提交时,它会将 this 提交(较新,在右侧)与其父提交(较旧,在左侧)进行比较。如果没有--follow
,它仍然会这样做,但只需查看您命名的文件是否更改(M
状态,来自git diff --name-status
)或已添加或已删除(A
、D
)。4但对于--follow
,它还会查找R
状态。
如果文件被改变了——有M
或A
或D
状态——git log
打印出这个提交,但如果没有,它只是抑制打印输出。使用--follow
,我们添加R
状态,如果发生这种情况,添加两个文件名。如果状态是R
,那么git log
之前一直在寻找newpath/my-file.php
。但现在它知道,在提交 parent 时,该文件被称为 oldpath/my-file.php
。 (再次注意,这里没有 文件夹。文件名是整个字符串,包括所有斜杠。)
因此,使用--follow
(打开重命名检测器)git log
可以获得重命名状态,因此可以看到文件被重命名。它还在寻找一个特定的文件名,在本例中为newpath/my-file.php
。如果它检测到重命名,git log
不仅会打印提交,还会更改它正在寻找的名称。现在,它从父提交向后查找 oldpath/my-file.php
,而不是 newpath/my-file.php
。
1--follow
代码本身......不是很好;整个实现需要重新设计,这可能会比我想的更简单的 hack 更好地解决这个问题。
2从技术上讲,只要您从未引入两人互相承诺。但实际上,您不会找到。
3--follow
选项只能跟随一个文件名。如果没有--follow
,您可以给git log
提供多个名称,或者一个“目录”的名称,即使Git 根本不存储目录。如果没有--follow
,git log
代码将在通用路径规范上运行。 --follow
,它只处理一个文件名。这是 Git 在这里使用的算法所施加的限制。
4它也可以有T
,类型改变了,我认为这很重要。完整的状态字母集是ABCDMRTUX
,但X
表示Git 中的一个错误,U
只能在未完成的合并期间出现,B
只能与git diff
和-B
选项一起出现,@ 987654412@ 和 R
只能在启用 --find-copies
和 --find-renames
(-C
和 -M
)选项的情况下发生。请注意,git diff
可能会根据您的diff.renames
设置自动启用--find-renames
,但git log
不会。
--follow
中的错误
从git log
的输出显示中删除一些提交的过程称为历史简化。 the documentation 中有一个很长的部分对此进行了描述,并以这个相当奇怪的声明开头:
有时您只对部分历史感兴趣,例如 修改特定
的提交。但是有两个部分 历史简化,一部分是选择提交,另一部分是 是如何做到这一点,因为有各种策略来简化 历史。
这个奇怪的措辞——“一部分是选择提交,另一部分是如何去做”——试图理解的是,启用历史简化后,git log
有时甚至不会步行 一些提交。特别是,考虑一个合并提交,其中两个提交字符串组合在一起:
C--...--K
/ \
...--A--B M--N--O <-- branch
\ /
D--...--L
要显示 所有 提交,git log
将必须遍历提交 O
,然后是 N
,然后是 M
,然后是 K
和 L
(以某种顺序),然后K
之前的所有提交和L
之前的所有提交都返回到C
和D
,然后在提交B
处重新加入单个线程,并从那里继续向后。
如果我们不打算显示所有次提交,也许——只是也许——在提交M
,我们可以回到仅提交 K
或仅提交 L
并完全忽略合并的另一“面”。这将节省大量的工作和时间,并避免向您展示不相关的内容。这通常是一件非常好的事情。
但是,对于--follow
,这通常是一件非常糟糕的事情。这是--follow
的问题之一:有时Git 在进行这种简化时会走“错误的腿”。添加--full-history
可以避免这种情况,但我们会立即遇到另一个问题。 --follow
选项只有一个文件名。如果我们在提交的两个分支中的一个中进行了重命名,但在另一个中没有,并且git log
首先进入重命名分支,它可能会在它失败时查找错误名称另一条腿。
如果文件在两个分支中重命名,则它从M
重命名回K
并且从M
重命名回L
,或者如果 Git 碰巧一开始就走在正确的腿上并且你不关心另一条腿,一切正常。但这是需要注意的。 (这不是--grep
给您带来的问题,或者如果没有--grep
就会发生。)
我认为您看到的错误是--grep
正在“过早”启动,实际上。 --grep
选项的工作原理是从 git log
的输出中消除在其提交中具有 (--invert-grep
) 或缺少 (--grep
而没有 --invert-grep
) 某些特定文本的提交 message。然后,假设重命名提交(导致git log --follow
知道使用名称oldpath/my-file.php
的提交)被您的--grep
选项跳过。 Git 不会看到 R
状态,也不知道将名称从 newpath/my-file.php
更改为 oldpath/my-file.php
。所以git log --follow
将继续寻找 new 路径,并且您只会得到那些同时满足 grep 条件的提交并且使用新名称修改文件。
这个错误可以通过让git log --follow
运行差异引擎来修复,即使它会因为其他原因跳过提交。但更一般地说,--follow
需要完全重写:它有一堆奇怪的特殊情况代码通过差异引擎线程化,只是为了使这个案例工作。它需要处理多个路径名和/或路径规范,并使用--reverse
和其他选项。它需要一种将新旧名称堆叠到提交路径上的方法,以便使用--full-history
,沿着合并的两条腿,它知道要寻找哪条路径。请注意,这还有其他含义:如果在合并的两条腿上都有不同的重命名怎么办?如果有人在合并中手动修复了重命名/重命名冲突,我们该如何处理用那个?
【讨论】:
哇,一大堆信息。我现在明白什么问题了。现在最大的问题是:有没有办法从 git 日志中隐藏“重命名”提交,但让--follow
选项仍然检查此提交中的重命名操作?
没有。但是请注意,您可以单独获取您关心的提交哈希 ID 列表(使用 git log --follow
,格式为 %H
),删除一个您不想访问的特定提交,然后查看那些带有git log --no-walk --stdin
的提交重定向到包含剩余哈希ID的文件。
我尝试使用 git ref-list
命令来执行此操作,但 git ref 没有 --follow
选项。我认为使用 git log 两次和 grep 之间听起来像是一个很好的工作方法。以上是关于Git日志(--follow)无法显示重命名之外的历史记录的主要内容,如果未能解决你的问题,请参考以下文章
带有重命名检测的 git show 文件(相当于 git log --follow)