当文件报告为新文件/删除文件时,如何区分更改

时间:2019-01-31 14:42:15

标签: git diff

我重命名了两个文件,并进行了一些更改(在Visual Studio中)。 git status显示以下内容:

    On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    deleted:    Core/Models/Metadata/MetadataModel.cs
    deleted:    Core/Models/Metadata/MetadataModelCollection.cs
    new file:   Core/Models/Metadata/MetadataValueModel.cs
    new file:   Core/Models/Metadata/MetadataValueModelCollection.cs

如果我尝试git diff --staged,它不会显示已删除文件和新文件之间的差异。而是将每个文件中的所有行都列为已删除或已添加。不足为奇,因为git无法将更改识别为重命名。

如何区分MetadataModel.cs和MetadataValueModel.cs? 还是MetadataModelCollection.cs和MetadataValueModelCollection.cs?

如果有问题,我将使用Windows 10 Pro和git版本2.20.1.windows.1

1 个答案:

答案 0 :(得分:1)

TL; DR

您在这里有两个选择:进行多次提交,并且每个步骤的更改都较小。或者,使用--find-renames=percentage参数(对于-X find-renames=...使用git merge拼写,对于--find-renames=...使用-M...git diff)来降低相似性阈值从默认的50%。请注意,git status没有旋钮可以执行此操作:git status始终使用50%。

从根本上讲,这是关于身份的问题。从哲学上讲,这是The Ship of Theseus,即祖父的斧头悖论。 (“这是祖父的斧头。父亲代替了手柄,我换了头,但这是同一根斧头。是吗?”)

如何知道文件“ old.name”在时间点A和时间点B之间被(1)重命名为“ new.ext”,并且(2)发生了巨大变化。即使整个名称不同并且大多数内容不同,我们应该将其称为“相同”文件吗?好吧,您可能自己做了重命名,所以当然知道。 :-)但是鲍勃或卡罗尔会知道吗?怎么样?吉特会知道吗?

最后一个答案是,Git不会知道。 Git只是不记录该信息。 Git只是制作和使用快照。快照 有一个名为Core/Models/Metadata/MetadataModel.cs的文件,或者没有有一个具有该名称的文件。如果两个要比较的快照都有一个同名文件,则Git假定 1 这两个文件都是“相同”文件,只是内容有所更改。如果一个快照包含文件而另一个快照没有文件,则更为复杂。

Git所做的是在事实发生后(尝试) detect 重命名。假设左侧快照没有Core/Models/Metadata/MetadataModel.cs,右侧快照没有Core/Models/Metadata/MetadataValueModel.cs,而左侧快照 没有git merge,右侧快照 。例如,这里就是这种情况。

在这种情况下,文件很有可能被重命名(也可能被修改)。如果您要求Git这样做,则Git会将位于左侧而非右侧的所有文件的内容 all 进行比较。在右边而不是左边的文件。对于内容足够相似的任何两个文件,Git会为这对文件分配一个“相似性得分”,Git用百分比表示-一个介于0%(完全不相似)和100%(完全相同)之间的数字。

100%相同的大小写特别有用,因为它可以保证正常工作并且非常快捷。 2 因此,如果重命名文件而没有更改 ,然后立即提交结果,“之前”和“之后”提交几乎完全相同。它们具有相同的文件,具有相同的内容,除了一对文件-或两对或N对(如果重命名两个或N个文件)之外。 Git可以将左侧提交与右侧提交进行比较,查看除重命名的文件以外的所有文件均已配对,然后使用快速100%精确匹配的情况进行内容比较并检测到重命名。

进行了中间提交之后,您可以对重命名的文件进行更改(甚至是大规模更改),然后进行另一次提交。当Git比较父提交和子提交时,所有文件都具有相同的 names ,即使其中一些内容发生了很大的变化,Git也会为您提供配对文件的逐个文件差异没有 not 更改名称的文件。 (再次参见脚注1。)

当您比较第一个快照(重命名)和最后一个快照(重命名后大规模变更后)时,此不会提供帮助。仅当您将其重命名为post-rename,然后将其重新命名为post-mass-change时,它才有用。或等效地,一次完成一次提交,就像Git通常所做的那样。因此,以后使用git merge并没有太大帮助。

对于这不合适的情况(包括在git merge时),当git diff --find-renames在基础上运行git diff --find-renames=30并提示提交而没有查看它们之间的任何提交时,您可以降低最小相似度。上面我们所做的是,通过两次提交,利用了快速简便的情况:给定两个文件的名称不同,但内容100%相同,Git可以轻松地将它们配对。但是给定两个文件的名称不同,并且仅提供90%相似的内容,Git仍然可以将它们配对。只是需要更多的工作。

更改重命名文件的内容越多,则说这两个文件相似就越难。但是Git还是会尝试-它将尝试所有可能的配对。 3 最佳匹配,无论是什么,只要 达到或超过< em> minimum 与您指定的匹配。该最小值默认为50%。

要选择默认值以外的其他值,例如,使用git merge -X find-renames=30设置30%,使用git diff --find-renames=25 --name-status --diff-filter=R在合并过程中使用相同的减少限制。您如何知道要使用的百分比?答案实际上只是试用-相似性指数计算有点奇怪,因此您只需要尝试看看哪种方法适合您的情况。如果您有两个提交哈希ID,则可以运行git status以查看配对为25%的内容,如果配对太多或太少,请重复75或其他您喜欢的数字。

当您运行git diff时,将运行 HEAD s,两棵树中的每棵:

  • git diff与索引
  • 索引与工作树

两个比较均已重命名检测并设置为50%。无法更改此选项。

索引和工作树都不是实际的提交,因此您不能完全将它们交给git diff,但是git diff --cached --name-status --find-renames=... # for HEAD vs index git diff --name-status --find-renames=... # for index vs work-tree 本身可以进行相同的比较,在这里您可以使用选项:

--diff-filter=R

添加--find-renames仅显示检测到的重命名(如果您很在意)。

请注意,--find-renames从Git 2.9开始默认为打开,而在较早的Git版本中默认为关闭。使用diff.renames可以50%或您提供的数字打开检测。可以将配置设置true设置为falsecopiescopygit diff。只有瓷器差异命令(例如git loggit showdiff.renames)使用已配置的git diff-管道命令不受用户设置的影响。 (这是使它们成为“管道命令”的重要组成部分。)


1 使用git merge时,您可以告诉Git配对 break 。也就是说,如果您有两个名称相同但内容截然不同的文件,则可以告诉Git:在进行重命名检测之前,请分解成对的文件,它们的内容差别太大。将分解的配对放入重命名检测池中。此选项在git diff中不可用,仅在renameLimit中可用。

2 Git通过哈希ID存储每个内容,因此检测到提交A中名称为X的文件与提交B中名称为Y的文件100%相同,只需查看哈希即可ID。如果哈希ID匹配,则文件也匹配。找到100%相同的内容匹配后,Git现在将A:X与B:Y配对,并且两个名称不再位于“要配对的文件”池中。

请注意,尽管这是快速,容易并且可以保证的,但是如果还有一个B:Z与A:X完全相同,那么就无法确定A:X是否将与B:Y匹配或B:Z。在这里,除了重命名检测以外(或者除了重命名检测之外),您可能希望启用Git的 copy 检测,以便Git可以说A:X都复制了 B :Y和B:Z。交互的细节在这里变得有些复杂。

3 实际上,Git尝试配对的数量是有限制的。重命名检测代码具有两个文件名队列:左侧不匹配右侧不匹配。相似度计算必须比较每个左右条目,即len(left)* len(right)文件比较。如果两个长度为N,则为N 2 -计算上非常昂贵。因此,Git有一个名为0的设置,该设置限制了队列的长度。此限制最初是100,然后在Git 1.5.6中增加到200,然后在Git 1.7.4.2 / 1.7.5中增加到400,但是您可以通过将限制配置为diff.renameLimit来将其设置为“ unlimited”(如果愿意) (尽管Git仍会将其内部限制为32767)。

有一个可单独配置的合并重命名队列长度限制,当前默认值为1000。如果设置merge.renameLimit但未设置diff.renameLimit,则两者都使用{{1}}值。