如何使用 git 真正显示重命名文件的日志

Posted

技术标签:

【中文标题】如何使用 git 真正显示重命名文件的日志【英文标题】:How to REALLY show logs of renamed files with git 【发布时间】:2011-08-10 06:24:37 【问题描述】:

我对 Git 比较陌生。我之前用过Subversion (SVN)。

我注意到,如果文件已被重命名,大多数图形化 Git 前端和 IDE 插件似乎都无法显示文件的历史记录。当我使用

git log --follow

在命令行上,我可以跨重命名查看整个日志。

According to Linus Torvalds (alternative link) --follow 开关是一个“SVN noob”取悦;严重的 Git 用户不要使用它:

--follow 完全是 hack,旨在满足从不 对诸如此类的事情一无所知 父母身份或漂亮的修订图 无论如何。

这并不完全是基本的,但 “--follow”的当前实现 真的是一个快速预处理的东西 固定在修订行走 逻辑,而不是任何东西 真的很重要。

它实际上被设计为“SVN noob”取悦者,而不是“真正的 git” 功能”的东西。这个想法是 你会远离(破碎的) 重命名的思维方式 事关大局。

铁杆 Git 用户如何在文件重命名时获取文件的历史记录?这样做的“真正”方法是什么?

【问题讨论】:

@David Hall:git mv oldfile newfile根本不会记录重命名 - 这与删除一个文件并添加另一个文件相同。 git 只会在事后每次提交时从树的状态中计算出重命名和副本。 @David Hall:如果你用 git 之外的其他工具重命名文件(例如/bin/mv oldfile newfile),然后再使用git add newfile; git rm oldfile,结果与git mv oldfile newfile 没有区别。 如果您将文件移动到新的存储库,这种思想就会崩溃,在这种情况下,无法移动其整个历史可能是一个主要问题。当然,在一个复杂的项目中,一个文件可以真正带来多少真实历史是有限度的。 注意:git log --follow 在 git 2.9(2016 年 6 月)中有所改进:参见 my answer below 从 v2.15 开始,您可能希望在 diff 时尝试使用 --color-moved 【参考方案1】:

我认为,Linus 观点背后的总体驱动力是——并且稍微加点盐——铁杆 Git 用户从不关心“文件”的历史。您将内容放入 Git 存储库是因为内容作为一个整体具有有意义的历史记录。

文件重命名是“内容”在路径之间移动的一个小特例。您可能有一个在文件之间移动的函数,Git 用户可能会使用“pickaxe”功能(例如,log -S)来追踪这些文件。

其他“路径”更改包括合并和拆分文件; Git 并不真正关心您认为哪个文件被重命名,以及您认为哪个文件被复制(或重命名和删除)。它只是跟踪你的树的完整内容。

Git 鼓励“整棵树”思维,而许多版本控制系统非常以文件为中心。这就是为什么 Git 更多地提到“路径”而不是“文件名”。

【讨论】:

Linus 的观点是,一个“适当的”gui 将能够跨文件跟踪代码块,他希望我们现在能够拥有这样的工具。不幸的是,我们仍然没有那么奢侈,而且 --follow 仍然有用。 除了--follow 之外,git 是否真的给了你一个解决方案? @Griwes 目前没有。我想 Linus 考虑的“正确”方式是 log <path>show 日志中最旧的提交,手动识别内容的来源,然后从 log <old-path> 重复。这将假设您的提交很小,因为它是实用的 - 但话说回来,微小的提交已经大大提高了生活质量。 我认为 --follow 是默认值,可以增强“整棵树”的思维。我的意思是当我想查看文件中代码的历史时,我通常并不关心文件是否被重命名,我只想查看代码的历史,而不管重命名。所以在我看来,--follow 作为默认值是有意义的,因为我不关心单个文件; --follow 帮助我忽略单个文件重命名,这通常是无关紧要的。 所以...如果我决定关心“内容”而不是文件,如何打印与当前文件中的内容相关的提交?如果 git 为我在不同的文件中跟踪它并报告所有更改的日志,我会很高兴——我只是不知道如何获取它。【参考方案2】:

我遇到了与您完全相同的问题。尽管我不能给你答案,但我相信你可以阅读this emailLinus 早在 2005 年写的,它非常中肯,可能会给你一些关于如何处理问题的提示:

...我声称任何试图跟踪重命名的 SCM 基本上都是 除非出于内部原因(即允许高效 deltas),正是因为重命名并不重要。他们不帮助你,而且 他们不是你感兴趣的反正

重要的是找到“这是从哪里来的”,以及 git 建筑确实做得很好——比其他任何东西都好 在那里。 …

我发现 this blog post, 引用了它,这对您找到可行的解决方案也很有用:

在消息中,Linus 概述了理想的内容跟踪系统如何让您了解代码块是如何形成当前形状的。您将从文件中的当前代码块开始,返回历史记录以查找更改文件的提交。然后您检查提交的更改以查看您感兴趣的代码块是否被它修改,因为更改文件的提交可能不会触及您感兴趣的代码块,而只会触及其他部分文件。

当您发现在提交之前文件中不存在代码块时,您可以更深入地检查提交。您可能会发现这是许多可能的情况之一,包括:

    提交真正引入了代码块。提交的作者是你正在寻找它的起源的那个很酷的特性的发明者(或者是引入这个 bug 的有罪的一方);或 文件中不存在该代码块,但在不同文件中存在五个相同的副本,提交后均消失。提交的作者通过引入一个辅助函数来重构重复的代码;或 (作为特例)在提交之前,当前包含您感兴趣的代码块的文件本身不存在,但确实存在另一个内容几乎相同的文件,并且您所在的代码块感兴趣的,以及当时存在的文件中的所有其他内容,确实存在于该其他文件中。它在提交后消失了。提交的作者重命名了文件,同时对其进行了少量修改。

在 git 中,Linus 的终极内容跟踪工具还没有以完全自动化的方式存在。但大多数重要的成分已经可用。

请随时向我们发布您在这方面的进展情况。

【讨论】:

感谢您发布这些文章。直到我读到它们,我才完全掌握了内容历史的概念!。我一直在想这个错误的方式! Linus 的那封电子邮件很棒,感谢您发布此内容。 有趣的事实,Git v2.15 adds --color-moved 朝着“理想的跟踪系统”迈进。我正在玩它以查看它在文件中跟踪移动的行,但意外地意识到它在整个差异中跟踪移动的行。 Linus 解释了一个复杂的情况。但是这里我们有一个简单的情况:一个文件刚刚被重命名(或移动到另一个目录)。因此应该有一个简单的解决方案。我认为这个问题来自一个事实,而不是与 Subversion 相反,用户不能在提交时指示 Git 文件来自哪里,并且 --follow 可能是错误的(例如,如果 2 个文件具有相同的内容,或者如果有是文件移动之外的修改)。【参考方案3】:

我注意到,如果文件被重命名,大多数图形化 git 前端和 IDE 插件似乎无法显示文件的历史记录

您会很高兴知道一些流行的 Git UI 工具现在支持这一点。可用的 Git UI 工具有几十种,我就不一一列举了,举个例子:

Sourcetree,查看文件日志时,左下角有一个“关注重命名的文件”复选框 TortoiseGit 在左下角的日志窗口中有一个“关注重命名”复选框。

有关 Git UI 工具的更多信息:

http://git-scm.com/downloads/guis https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools

【讨论】:

Source 在重命名一次时非常有用,在重命名两次时,更改详细信息不适用于重命名前的提交。我在这里报告了这个错误:jira.atlassian.com/browse/SRCTREE-5715 gitk 工作得很好,即使一个文件在历史上被重命名了两次。该命令看起来像这样“gitk --follow path/to/file” 很好!!!除非您回答,否则永远不会在 SourceTree 中发现“关注重命名的文件”。【参考方案4】:

在 Linux 上,我已经验证 SmartGit 和 GitEye 在跟踪特定文件的历史记录时能够跟踪重命名。然而, 与gitk 和 GitEye 不同,SmartGit 显示单独的文件视图和存储库视图(其中包含目录结构,但不包含其中包含的文件列表)。

【讨论】:

【参考方案5】:

注意:git 2.9(2016 年 6 月)将大大改善 git log --follow 的“错误”性质:

参见SZEDER Gábor (szeder) 的commit ca4e3ca(2016 年 3 月 30 日)。(由 Junio C Hamano -- gitster -- 合并到 commit 26effb8,2016 年 4 月 13 日)

diffcore:修复重命名检测期间相同文件的迭代顺序

如果两个路径“dir/A/file”和“dir/B/file”内容相同 并且父目录被重命名,例如'git mv dir other-dir',然后 diffcore 报告了以下确切的重命名:

renamed:    dir/B/file -> other-dir/A/file
renamed:    dir/A/file -> other-dir/B/file

(注意这里的倒置:B/file -> A/fileA/file -> B/file

虽然技术上没有错,但这不仅让用户感到困惑, 也适用于基于重命名做出决定的 git 命令 信息,例如'git log --follow other-dir/A/file' 跟随 'dir/B/file' 过去的重命名。

此行为是提交 v2.0.0-rc4~8^2~14 的副作用 (diffcore-rename.c:简化查找确切的重命名,2013-11-14): hashmap 存储源返回来自同一存储桶的条目,即 与当前目的地匹配的来源,按 LIFO 顺序排列。 因此,迭代首先检查 'other-dir/A/file' 和 'dir/B/file',并在找到相同的内容和基本名称后,报告一个准确的重命名。


在 Git 2.31(2021 年第一季度)中,diffcore 的文件级重命名检测得到了改进。

见commit 350410f(2020 年 12 月 29 日)和commit 9db2ac5、commit b970b4e、commit ac14de1、commit 5c72261、commit 81c4bf0、commit ad8a1be、commit 00b8ccc、commit 26a66a6(12 月 11 日) by Elijah Newren (newren).(由 Junio C Hamano -- gitster -- 在 commit a5ac31b 中合并,2021 年 1 月 25 日)

diffcore-rename:加速rename_dst设置

签字人:Elijah Newren

register_rename_src() 只是引用 rename_src 中传递的对。

相比之下,add_rename_dst()rename_dst 做了完全不同的事情。 它不是复制传递的对,而是从传递的对复制第二个diff_filespec,引用它,然后将diff_rename_dst.pair 字段设置为NULL。 后来,当找到配对时,record_rename_pair() 通过diff_queue() 分配了一个完整的diff_filepair,并将其srcdst 字段指向适当的diff_filespecs

register_rename_src() 用于rename_src 数据结构和add_rename_dst() 用于rename_dst 数据结构之间的这种对比奇怪地不一致,并且需要更多的内存和工作量。 [...] 该补丁将设置时间加快了约 65%,最终回写到输出队列的时间缩短了约 50%,从而使重新定位几十个补丁的执行时间总体下降了 3.5%。

【讨论】:

【参考方案6】:

你是这样做的:

git log -M --summary | grep rename | grep BASENAME

只需将基本名称放在那里。

如果结果太多那么你也可以串行grep每个中间目录名。

【讨论】:

以上是关于如何使用 git 真正显示重命名文件的日志的主要内容,如果未能解决你的问题,请参考以下文章

如何重命名git根文件夹?

在 Git 中重命名文件后没有预期的日志 [重复]

在git下提交文件时如何控制重命名阈值?

如何对移动/重命名的文件进行 git diff?

当git上只做文件大小写重命名的修改时,如何躲坑

如何找到跨文件重命名和重新排序添加特定 Gradle 构建依赖项的 Git 提交?