显示 Git 存储库的移动/重命名文件

问题描述

我正在处理一个最近经过重组的大型代码库,许多文件不再出现在我期望的位置。

如何查看在两次提交之间移动/重命名文件列表?我对这些或其他文件的更改不感兴趣,只是以某种方式将旧名称映射到新名称的视图。

解决方法

TL;DR:使用 git diff --find-renames,可能与 --diff-filter=R--name-status 一起使用。

Git 实际上并不存储重命名。它只存储快照。然而,Git 可以比较两个快照,并且——在你的控制和指导下,在某种程度上——推断以前名为 path/to/file 的某个文件现在被命名为 new/name/of/file:即,尽管这些是不同的文件,但它们在某种程度上也是相同文件,就像Ship of Theseus的复制品可能成为忒修斯之船,即使它不是'

Git 的重命名检测是通过将 -M--find-renames 添加到 git diff 来启用的,或者如果您使用的是现代版本的 {{ 1}}。如果您正在使用底层差异程序之一(git diffgit diff-tree 等),您需要显式选项。然后,您让 Git 比较任意两个快照或其他树(例如由 Git 的索引或您的工作树表示的快照),它会尽力找到具有不同名称的“足够相似”的文件>,然后将声称此类文件必须已重命名。 (请注意,即使我只是删除一个旧文件,然后使用足够相似的新名称创建一个新文件,它也会这样做。)

这里的“足够相似”很棘手。 Git 将其所谓的相似性指数计算为一个百分比,但是这个百分比没有很好地定义。1仍然是一个文件,其内容与之前某个文件的内容完全匹配获得 100% 的相似度指数;完全不共享字节的文件将获得 0% 的相似度索引;和共享一些但不是所有字节的文件将获得一个介于两者之间的索引,至少在某种模糊的意义上,它表示文件的相似程度。

Git 的默认设置是在发现文件 git diff-index 被重命名为 path/to/file 时声明:

  1. new/name/of/file 在右侧提交中根本不存在,而 path/to/file 在左侧提交中根本不存在。 (两个“边”来自左提交和右提交哈希或您给 new/name/of/file 的其他说明符,例如,git diff 将左侧的提交 git diff a123456 b789abc 与提交 a123456 进行比较在右边。)

  2. 内容相似。

  3. 没有其他配对的其他未配对的左右文件更多相似。2

  4. (至关重要)相似度指数达到或超过您在命令行中指定的阈值

如果启用重命名检测而不设置特定阈值,则默认阈值为 50% 相似。 b789abc-M 选项采用可选阈值;如果给定,则设置最小阈值。

如果没有配对达到所需的阈值,Git 声明删除左侧文件并新添加右侧文件。否则,从潜在配对列表中删除配对的相似文件,并考虑剩余文件。放置在这些左右配对池中的文件名队列的长度也有限制,但在大多数情况下,您不必担心这一点。

通过 diff 引擎运行所有文件以找到正确的对,如果/根据需要计算相似性,等等,Git 然后也会对配对的文件进行比较,除非你用 {{1} } 或 --find-renames。与重命名检测一样,此差异仅找到一种 方法来转换左侧文件以匹配右侧文件。这不一定是任何人所做或将要做的:这只是一种从左侧到右侧的方式,最好使用最少的编辑命令。

如果您确实使用了 --name-only,Git 将找到每个重命名并在输出中显示它们,状态码为 --name-status,后跟导致配对的相似性索引,以及两个名称(左侧和右侧文件名以斜杠完成)。 (这也抑制了内容差异,如上所述。)如果您将 --name-status 添加到您的 R 命令,您可以告诉 Git 打印重命名的文件。有更多过滤选项可用;详情请参阅 the documentation


1该算法使用增量压缩代码来查找在一个文件对另一个文件进行增量压缩时将保留的字节序列,以及将简单地作为“新字节”引入的字节序列.如果我没记错的话,这也会删除回车符,以便文本文件中的 CRLF 更改不计入相似性索引,但我可能记错了。无论如何,然后将这些除以整体文件大小,得出相似性指数值。

2为了打破平局,如果新文件名以相同的最终路径component结尾,则树代码用于将计算的相似性指数加1。也就是说,假设我们发现 --diff-filter=Rgit diffold/path/name 的相似度为 75%。这给了我们一个平局:文件是从 new/name 重命名为 new/xyz 还是 old/path/name?字符串 new/name 与字符串 new/xyz 不匹配,但两个 xyz 部分确实匹配,因此这部分获得 1% 的奖励。

自从我看到这段代码以来,Git 对内部目录重命名变得更加聪明:也就是说,如果 name 变成了 name 和 {{1 }} 变成了 path/to/file,它开始看起来像 path/four/file——一个目录名——必须变成 path/to/X。如果模式对每个 path/four/X 重复,那么确实,目录重命名是表示这一点的方式。这种重命名检测要好得多,而且不需要 1% 的奖金,前提是 Git 实际上提前将所有新旧文件配对。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...