如何在我移动的文件中合并Git中的更改?

How do I merge changes in Git in files that I moved?

在搬一些目录。

我的意思是,MERGE,多conflicting档案,因为他们有其他开发商承诺的变化。两个egit MERGE工具和git mergetool说,文件被删除或remotely局部。看到的图像。

在这些MERGE怎么变化?

enter image description here


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

你不必担心在Git中"保存历史"。Git根本没有文件历史记录,它只有提交历史记录。也就是说,每个提交"指向"(包含其父级的哈希ID),或者对于合并,它的父级和这都是历史:commit E前面是commit D,commit D前面是commit C等等。只要你有承诺,你就有历史。好的。

这就是说,Git可以尝试使用git log --follow合成一个特定文件的历史。您可以指定一个开始提交和路径名,以及git检查(commit by commit),以查看在将当前提交的父级与当前提交进行比较时是否重命名了该文件。它使用git的重命名检测来标识commit l(左)中的文件a/b.txt与commit r(右)中的文件c/d.txt是"同一个文件"。好的。

重命名检测有很多精细的旋钮,但在基本级别上,基本上是这样的:好的。

  • Git查看commit l中的所有文件名。
  • Git查看commit 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文件,则它将获取"最相似"的r文件并将其与l文件配对。如果没有足够相似的文件,则L文件保持不成对(从队列中取出),并被视为"从L中删除"。最终,在不成对的L队列中没有文件,并且无论未成对的R队列中保留什么文件,这些文件都是"添加的"(R中的新文件)。同时,所有配对文件都已重命名。好的。

这意味着:当比较(git diffcommit l到r时,如果两个文件足够相似,它们将作为重命名进行配对。默认的相似性索引是50%,所以文件需要50%的匹配(这意味着相似性索引的计算有点不透明),但是对于Git来说,精确的匹配更加容易和快速。好的。

请注意,git log --follow启用了重命名检测(在一个目标R文件上,当我们在日志中向后工作时,将父提交与子文件中已知的一个文件进行比较)。自从Git 2.9版以来,git diffgit log -p现在都自动打开了重命名检测。在旧版本中,您必须使用-M选项来设置相似性阈值,或将diff.renames配置为true,以使git diffgit log -p进行重命名检测。好的。

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

这就引出了一条适用于git log --follow的经验法则:如果可能,当您打算重命名某个文件或一组文件时,请自行执行重命名步骤,而不更改任何文件内容。如果可能的话,将重命名文件的数量保持在相当小的水平:例如,在400或以下。您可以通过多个步骤提交更多的重命名,一次400个。但请记住,您在权衡git log --follow的能力和速度,以避免用无意义的提交来混乱您的历史:如果您需要重命名50000个文件,也许您应该这样做。好的。

但这对合并有何影响?好吧,git mergegit log --follow一样,总是打开重命名检测。但哪个承诺是L,哪个承诺是R?好的。合并和重命名检测

无论何时跑步:好的。

1
git merge <commit-specifier>

Git必须找到当前(head)提交和指定的其他提交之间的合并基。(通常这只是git merge 。通过将分支名称解析为它指向的提交来选择另一个分支的提示提交。根据git中"branch name"的定义,这是该分支的提示提交,因此这个"只起作用"。但您可以通过散列ID指定任何提交。)让我们调用这个合并基提交B(对于基)。我们已经知道我们自己的承诺是HEAD,尽管有些事情称之为"本地"。让我们称另一个commit为o(对于另一个),尽管有些东西称之为"远程"(这很愚蠢:Git中没有任何东西是远程的!).好的。

实际上,git有两个git diffs,一个比较b和head,因此对于这个特殊的diff,l是b,r是head。Git将根据我们在上面看到的规则检测或检测不到重命名。然后Git做另一个git diff,比较b和o。Git将根据相同的规则再次检测或检测不到重命名。好的。

如果在b-vs-head中重命名了某个文件,则git会像往常一样对其内容进行区分。如果在b-vs-o中重命名了某个文件,git会像往常一样对其内容进行区分。如果在head和o中将单个b文件f重命名为两个不同的名称,那么git会在该文件上声明重命名/重命名冲突,并将这两个名称保留在工作树中供您清理。如果只在一个diff中重命名,那么在head或o中仍然称为f,那么git将使用从哪一侧重命名的新名称将文件存储在工作树中。在任何情况下,git都会像往常一样尝试组合这两组更改(从b-vs-head和b-vs-o)。好的。

当然,要让git检测重命名,文件的内容必须足够相似,就像往常一样。这对于Java文件(有时Python)尤其有问题,其中文件名嵌入到导入语句中。如果一个模块主要由import语句组成,并且只有几行自己的代码,那么由重命名引起的更改将覆盖其余的文件内容,并且这些文件甚至不会达到50%的匹配。好的。

有一个解决办法,虽然有点难看。根据git log --follow的经验法则,我们可以先提交重命名,然后将更改内容的"修复所有导入"提交为单独的提交。然后,当我们进行合并时,我们可以进行两个甚至三个合并:好的。

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

由于没有重命名任何文件,所以这种合并也会像往常一样进行,或进行得很糟糕。这是结果,以图表的形式。注意,我们提供给git merge命令的散列是commit A的散列,就在R之前,它执行所有重命名:好的。

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

然后:好的。

1
git merge <hash of R>

由于每个文件的内容完全相同,名称也完全相同,在其他Rcommit之间,合并基是commit A,这里的效果只是获取所有重命名。我们将文件内容保留在head commit M中,但保留R中的名称。此合并应自动成功:好的。

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

现在我们可以开始合并开发部门了。好的。

在许多情况下,我们不需要合并M,但如果我们只需要合并N,那么这样做对所有重命名来说可能不是一个坏主意。原因是commit R不起作用:它的导入名称错误。在平分过程中必须跳过commit R。这意味着合并N同样是不起作用的,在对分时必须跳过。有M在场可能会很好,因为M实际上可以工作。好的。

请注意,如果您这样做,您就是为了取悦版本控制系统而扭曲/扭曲源代码。情况不太好。它可能比你的其他选择更糟,但不要告诉你自己它是好的。好的。

1I仍然需要了解当存在重命名/重命名冲突时,文件的两个副本会发生什么情况。由于Git将两个名称都保留在工作树中,这两个名称是否包含相同的合并内容,如果需要,还包含任何冲突标记?也就是说,如果文件名为base.txt,现在名为head.txtother.txt,那么head.txtother.txt的工作树版本是否始终匹配?好的。好啊。