gpt4 book ai didi

git - 如何在移动的文件中 merge Git中的更改?

转载 作者:行者123 更新时间:2023-12-03 02:35:44 27 4
gpt4 key购买 nike

我移动了一些目录。

合并时,由于其他开发人员已提交更改,因此存在许多冲突文件。 egit合并工具和git mergetool均表示该文件已在本地或远程删除。见图片。

如何合并这些更改?

enter image description here

最佳答案

文件历史记录和重命名检测

您根本不需要担心Git中的“保存历史”。 Git根本没有文件历史记录,只有提交历史记录。也就是说,每个提交都“指向”其父级(或者包含其两个父级)(包含其哈希ID),这就是历史记录:提交E之前是提交D,而提交< cc>之前是提交D,依此类推。只要有提交,就可以拥有历史记录。

也就是说,Git可以尝试使用C合成一个特定文件的历史记录。您指定一个起始提交和一个路径名,然后Git逐个提交进行检查,以在将当前提交的父项与当前提交进行比较时查看文件是否被重命名。这使用Git的重命名检测来识别提交L(左)中的文件git log --follow与提交R(右)中的文件“ cc”是“同一文件”。

重命名检测有很多技巧,但是在基本级别上,基本上是这样的:


Git查看提交L中的所有文件名。
Git查看提交R中的所有文件名。
如果有一个文件名从L消失并出现在R中,例如a/b.txt不见了,而c/d.txt是全新的,为什么呢,这是检测到的重命名的候选者。
现在有了候选对象(未配对的L文件和未配对的R文件),Git会比较这些未配对文件的内容。


未配对的文件进入配对队列(一个用于L,一个用于R),Git哈希所有文件的内容。它已经具有内部Git哈希,因此首先要直接比较所有哈希。如果文件完全不变,则它在L和R中具有相同的Git哈希ID(但名称不同),并且可以立即进行配对并从配对队列中删除。

现在,完全匹配已被删除,Git尝试进行长时间的缓慢测试。它需要一个未配对的L文件,并为每个R文件计算一个“相似性索引”。如果某个R文件足够相似(或多个非常相似),它将采用“最相似”的R文件并将其与L文件配对。如果没有足够相似的文件,则L文件保持未配对状态(从队列中取出),并被视为“从L中删除”。最终,在未配对的L队列中没有文件,并且无论在未配对的R队列中保留什么文件,这些文件都会被“添加”(R中的新功能)。同时,所有配对的文件都已重命名。

这意味着什么:当比较(a/b.txt)提交L与R时,如果两个文件足够相似,则它们将作为重命名配对。默认的相似度索引为50%,因此文件需要匹配度为50%(无论如何,相似度索引的计算有些不透明),但是对于Git而言,精确匹配要容易得多且更快。

请注意,c/d.txt启用了重命名检测(在一个目标R文件中,因为我们正在向后浏览日志,将父提交与仅在子文件中知道名称的一个文件进行比较)。从Git 2.9版开始,git diffgit log --follow现在都自动启用了重命名检测。在旧版本中,必须使用git diff选项设置相似性阈值,或者将git log -p配置为-M,以获取diff.renamestrue进行重命名检测。

配对队列也有最大长度。它已经翻倍了两次,一次是在Git 1.5.6中,一次是在Git 1.7.5中。您可以自己控制它:它可以配置为git diffgit log -p。当前限制为400和1000。(如果将其设置为零,Git将使用其自己的内部最大值,这会消耗大量CPU时间-这就是为什么这两个限制首先存在的原因。如果设置diff.renameLimit但不是merge.renameLimitdiff.renameLimit使用您的差异设置。)

这导致适用于merge.renameLimit的经验法则:如果可能,当您打算重命名某些文件或一组文件时,请自行提交重命名步骤,而不更改任何文件内容。如果可能,将重命名的文件的数量保持较小:例如,等于或小于400。您可以分多个步骤一次提交400个以上的重命名。但是请记住,您要权衡git merge能力和速度,以免因无意义的提交而使历史混乱:如果您需要重命名50000个文件,也许您应该这样做。

但这如何影响合并?好吧,git log --followgit log --follow一样,始终会启用重命名检测。但是哪个提交是L,哪个提交是R?

合并和重命名检测

每当您运行时:

git merge <commit-specifier>


Git必须找到当前(HEAD)提交和指定的其他提交之间的合并基础。 (通常这只是 git merge。它通过将分支名称解析为其指向的提交来选择该另一个分支的尖端提交。根据Git中“分支名称”的定义,这就是该分支的尖端提交,因此,您可以通过哈希ID指定任何提交。)我们称此合并基础提交B(用于基础)。我们已经知道我们自己的提交是 git log --follow,尽管有些事情将其称为“本地”。让我们将其他提交称为O(对于其他),尽管有些事情将其称为“远程”(这很愚蠢:Git中没有远程对象!)。

然后,Git实际上执行两个 git merge <branchname>。一个将B与HEAD进行比较,因此对于此特定差异,L为B,R为H​​EAD。 Git将根据我们上面看到的规则来检测或无法检测到重命名。然后,Git执行另一个 HEAD,它将B与O进行比较。Git将根据相同的规则再次检测或无法检测到重命名。

如果在B-vs-HEAD中重命名了某个文件,Git会像往常一样将其内容进行差异化。如果在B-vs-O中重命名了某个文件,Git会像往常一样将其内容进行比较。如果将单个B文件F重命名为HEAD和O中的两个不同名称,则Git会在该文件上声明重命名/重命名冲突,并将这两个名称保留在工作树中供您清理。如果仅在一个差异中对其进行了重命名(在HEAD或O中仍称为F),那么Git会使用新名称将文件存储在工作树中,无论从哪一侧将其重命名。在任何情况下,Git都会像往常一样尝试将两组更改(从B-vs-HEAD和B-vs-O合并)在一起。1

当然,为了让Git能够检测到重命名,文件的内容必须一如既往地足够相似。这对于Java文件(有时甚至是Python)尤其成问题,在Java文件中文件名被嵌入到import语句中。如果模块主要由导入语句组成,并且仅包含几行代码,则重命名引起的更改将使其余文件内容不堪重负,并且文件匹配度甚至不会达到50%。

有一个解决方案,尽管它有点难看。与 git diff的经验法则一样,我们可以只提交重命名,然后提交内容更改的“修复所有导入”作为单独的提交。然后,当我们进行合并时,我们可以进行两个甚至三个合并:

git checkout ...  # whatever branch we plan to merge into
git merge <hash> # merge with everything just before the Great Renaming


由于没有文件被重命名,因此这种合并将像往常一样好或差。这是图形形式的结果。请注意,我们提供给 git diff命令的哈希是提交 git log --follow的哈希,恰好在 git merge进行所有重命名之前:

...--*--o--...--o--M    <-- mainline
\ /
o--o--...-A--R--...--o <-- develop, with renames at R


然后:

git merge <hash of R>


由于每个文件的内容在名称上完全相同,而在其他 A提交中(合并基础是提交 R),所以这里的作用仅仅是选择所有重命名。我们保留来自HEAD commit R的文件内容,但保留来自 A的文件名。合并将自动成功:

...--*--o--...--o--M--N    <-- mainline
\ / /
o--o--...-A--R--...--o <-- develop, with renames at R


现在我们可以 M继续合并开发分支了。

在许多情况下,我们不需要进行合并 R,但是如果只需要对所有重命名都进行合并 git merge develop,那么做它可能不是一个坏主意。原因是commit M无法正常工作:导入名称错误。在平分期间必须跳过Commit N。这意味着合并 R类似地不起作用,必须在二等分过程中跳过。出现 R可能会很好,因为 N实际上可以工作。

请注意,如果您执行上述任何操作,则只会扭曲/扭曲您的源代码,只是为了使您的版本控制系统满意。这不是一个好情况。它可能比您其他的选择还不错,但是不要告诉自己它很好。



1我仍然需要查看重命名/重命名冲突时文件的两个副本会发生什么。由于Git将两个名称都留在工作树中,因此两个名称是否都包含相同的合并内容,以及是否需要添加任何冲突标记?也就是说,如果文件名为 M且现在分别命名为 Mbase.txt,则 head.txtother.txt的工作树版本是否始终匹配?

关于git - 如何在移动的文件中 merge Git中的更改?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43716649/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com