强制 git status 确认副本和类型更改

Mik*_*lor 4 git logging

git 中状态码“C”和“T”是如何触发的?有人可以给我一组非常简单的运行命令来模拟所需的步骤,以便在运行时git log --name-status看到上面的状态代码吗?我尝试过复制文件并更改已被跟踪的文件的扩展名类型,但它只是标记为已修改或已添加。

\n\n

https://git-scm.com/docs/git-diff

\n\n
\n

仅选择已添加 (A)、已复制 (C)、已删除 (D)、\n 已修改 (M)、已重命名 (R) 且具有其类型的文件(即常规文件、\n 符号链接、子模块、\xe2\x80 \xa6\xe2\x80\x8b) 已更改 (T)、未合并 (U)、未知\n (X),或者其配对已损坏 (B)。可以使用过滤字符的任意组合(包括无)。当 * (All-or-none)\n 添加到组合中时,如果有任何\n 文件与比较中的其他条件匹配,则选择所有路径;如果没有\n 与其他条件匹配的文件,则不会选择任何内容。

\n
\n\n

谢谢

\n

Von*_*onC 5

对于C状态(已复制),您需要添加--find-copies-harder

 git log --find-copies-harder --name-status -3
Run Code Online (Sandbox Code Playgroud)

如果您在新提交中添加文件的完整未修改副本,您将看到:

C100    b.bat   b1.bat
Run Code Online (Sandbox Code Playgroud)

如果文件较大,-C -M则足以检测副本。


对于(T),一个简单的chmod 755orchmod 644就足够了。
或者用文件替换符号链接。
或者文件的gitlink(父存储库索引中记录的特殊条目,模式 16000)。

--diff-filter在 Git 中很早就引入了:commit f2ce9fd, Git v0.99, Jun 2005

你可以看到diff.h. 它用于diff.c#diff_resolve_rename_copy()

因此,如果您以不同的类型复制文件,它就是(T).
DIFF_PAIR_TYPE_CHANGED定义为:

#define DIFF_PAIR_TYPE_CHANGED(p) \
    ((S_IFMT & (p)->one->mode) != (S_IFMT & (p)->two->mode))
Run Code Online (Sandbox Code Playgroud)

S_IFMT 0xF000 /* File type mask */,用于“如何读取git-ls-tree输出的模式字段”)

DIFF_PAIR_TYPE_CHANGED很早就在2005 年 5 月的 Git v0.99中引入了。并在Git v1.4.0-rc1, Apr. 2006中进行了重构。


tor*_*rek 5

为了详细说明VonC\'s 答案,请注意文档git diff提到了这-C一点--find-copies-harder说明:

\n\n
\n

出于性能原因,默认情况下,-C仅当副本的原始文件在同一变更集中被修改时,该选项才会查找副本。\n 此标志使命令检查未修改的文件作为副本源的候选文件。对于大型项目来说,这是一个非常昂贵的操作,因此请谨慎使用。给出多个-C\n 选项具有相同的效果。

\n
\n\n

这是“非常昂贵”的原因是近似匹配代码。在进一步阅读之前,请考虑一下您在典型差异中看到的内容:

\n\n
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh\nindex 96d208d..80b2387 100755\n--- a/t/t5000-tar-tree.sh\n+++ b/t/t5000-tar-tree.sh\n@@ -347,7 +347,7 @@ test_lazy_prereq TAR_HUGE \'\n        test_cmp expect actual\n \'\n\n-test_expect_success \'set up repository with huge blob\' \'\n+test_expect_success LONG_IS_64BIT \'set up repository with huge blob\' \'\n        obj_d=19 &&\n        obj_f=f9c8273ec45a8938e6999cb59b3ff66739902a &&\n        obj=${obj_d}${obj_f} &&\n
Run Code Online (Sandbox Code Playgroud)\n\n

这里我们有\xe2\x80\x94,Junio Hamano 已\xe2\x80\x94 删除了原来的text_expect_success行并插入了一个新的、略有不同的test_expect_success行。

\n\n

嗯,真的,他刚刚插入LONG_IS_64BIT 队列。我们刚刚掩盖了一个非常大的主题,即:我们如何选择“块”来进行比较和显示?我们假设正确的答案是“一次一行”。在内部,Git 需要做出各种不同的选择,并且它并不总是做出相同的选择,或者做出一致的选择;它做得更多是临时性的,无论出于什么目的,只要效果好就可以。

\n\n

给定一个 diff\xe2\x80\x94,找到一个本身就有点困难,有点计算密集型;朴素算法是 O(MN),迈尔斯算法大致是 O(ND)\xe2\x80\x94 然后我们可以定义一个相似性索引t/t5000-tar-tree.sh:新的中有多少原始的t/t5000-tar-tree.sh,有多少不同?Git 将其定义为:

\n\n
\n

与文件\xe2\x80\x99s 大小相比的添加/删除量

\n
\n\n

(再次,来自git diff文档)。另请参阅Edward Thomson 的回答,其中详细介绍了相似性指数的计算。出于性能原因,它更多地作为一种精简的差异,而不是完整的差异。

\n\n

还有另一个重要的性能技巧:只需通过比较 blob 哈希值即可预先处理精确匹配(100% 相似的文件)。这消除了运行任何差异的需要,甚至是精简的差异。由于文件在更改刚刚发生时具有完全匹配命名时文件具有精确匹配,因此我们可以非常快地完成此操作。

\n\n

差异树

\n\n

git diff考虑一下这里必须做什么。我们正在比较两个目录树,我们可以将其称为“左”(通常是旧版本)和“右”(新版本)。左边的树有一些文件:a.txtb.txt等(可能在子树中,例如dir/e.txt)。右边的树也有一些文件。

\n\n

假设左右树都有相同的文件(按名称)。在这种情况下,我们可能只想停在这里,并声明没有文件被重命名,也没有文件被复制;我们也许可以继续比较每个文件的内容,一次一对。这将是 Git 的默认操作。

\n\n
    \n
  • 同样,如果我们没有右侧文件也没有左侧对应文件,则这是默认设置。

  • \n
  • 然而,如果我们添加-B(“打破”大的变化),我们需要添加另一遍。我们逐对比较每个已经配对的文件对(例如“左a.txt,右a.txt”)。如果这些文件足够相似,我们就声明它们是同一个文件。如果它们差异太大,我们就会中断配对,然后返回寻找重命名和/或副本。当然,“差异太大”的“差异量”是 的论点-B

    \n\n

    (这个有点简化了\xe2\x80\x94-B实际上有两个参数,一个用于“重写”检测,一个用于“重命名”检测。这第一遍的主要功能是记录相似度索引。前面提到,出于性能原因,这种相似性索引计算diff与我们将看到的补丁不同。)

  • \n
\n\n

现在我们回到了这样的情况:我们可能有右侧文件,但没有左侧“原始”版本。无论我们是否有任何此类右侧文件,此时我们都可能拥有没有右侧版本的左侧文件。这就是-C进来的地方-M

\n\n

请注意,-M也称为--find-renames,并-C暗示-M。也就是说,我们可能只启用了重命名检测,或者我们可能启用了重命名和复制检测。

\n\n

重命名检测

\n\n

我们先来看看重命名检测。

\n\n
    \n
  • 如果它被禁用,则将所有未配对的右侧文件声明为新文件。我们完成了!那很简单!

  • \n
  • 否则,对于每个未配对的右侧文件,尝试找到一个足够相似的左侧文件。这意味着我们必须将每个未配对的左侧文件与该右侧文件进行比较。

    \n\n

    假设我们有R 个未配对的右侧文件和L 个未配对的左侧文件。假设我们没有想出一些聪明的办法,我们会进行多少次比较?对于每个R,我们必须查看L中的每个文件,因此答案是R * L比较。但每次比较都是相似性索引 diff,这本身就很昂贵。

  • \n
\n\n

如果双方都没有多少未配对的文件,那么这没什么大不了的。但假设左侧有 10,000 个文件。好吧,只要这些文件中的 9,995 个已经配对,这仍然不是什么大问题:现在我们只有 5 个左侧未配对的候选文件,无论右侧有多少未配对的文件。假设有 10 个这样的文件,即 10 个R文件,以及 5 个(共 10,000 个)未配对的L文件。

\n\n

这就是-M工作原理。它只查看未配对的左侧文件。所以它的成本并不算太离谱:10 个R文件乘以 5 个L候选 = 50 个相似性差异。当然,如果您的计算机每秒可以进行大约 100 次相似性比较,那么计算时间仍然约为半秒。

\n\n

计算机现在的速度相当快,因此它们可能可以做更多的事情。并且假设改变只是命名,那么相似度指数为100%。Git 可以非常快地检测到这一点,根本不需要做任何 diff。Git 首先执行这些操作,一旦将它们配对,它们就会从“硬” R集中退出,从而变得-M更便宜。

\n\n

复制检测

\n\n

-C或多或少,这也是默认的工作方式。我说“或多或少”是因为它不是查看未配对的左侧文件,而是查看已修改或未配对的左侧文件。也就是说,文件a.txt同时存在于左树和右树中,但如果a.txt左侧的哈希值a.txt与右侧的哈希值不匹配,则 Git 将添加a.txt到左侧集合中。假设除了5个未配对的L文件外,还有7个修改过的文件。因此,Git 现在必须执行 120 ((5 + 7) * 10) 相似性测试,而不是 50 (5 * 10) 相似性测试。

\n\n

然而,添加--find-copies-harder告诉 Git:不要只查看未配对或修改过的文件,还要查看每个文件左侧文件。现在我们有 10 个新文件乘以 10,000 个旧文件:需要计算 10 万个相似性索引值。即使我们每秒可以计算 1000 个,找到 100,000 个相似性索引值仍然需要 100 秒。

\n\n

结论

\n\n

这意味着配置中的一个-C选项或设置diff.renamescopies而不仅仅是true)并不那么昂贵。不过,使用起来--find-copies-harder仍然相当昂贵。

\n