目录树更改时 Git 变基冲突



我在存储库的分支中有一个分支。通过重命名其中一个顶级目录,上游存储库结构已略有更改。我的分支创建一个新的子目录,其中包含受影响目录中的文件。当我尝试变基时,我遇到了许多CONFLICT (file location)问题。我以前遇到过这种情况,但通常 gits 建议是正确的,我已经能够git add .git rebase --continue.但是,在这种情况下,git 提供的建议是完全不正确的。澄清一下:

SomeName/SubfolderName/已在上游存储库中SomeOtherName/SubfolderName/

我在分叉中添加的文件在SomeName/SubfolderName/ANewSubfolder/.当我变基时,git 告诉我;CONFLICT (file location): SomeName/SubfolderName/ANewSubfolder/Filename.ext added in a1c2e3... SomeCommitName inside a directory that was renamed in HEAD, suggesting it should perhaps be moved to ANewSubfolder/Filename.ext

如您所见,该建议是完全不正确的(大概是因为我在该提交中添加的文件是新的,并且 git 没有任何可比较的内容来确定它们应该去哪里)

如何告诉 gitSomeOtherName/SubfolderName/ANewSubfolder/Filename.ext的正确位置?

当 Git 提出这个"建议"时,据我所知,Git 没有对它采取任何行动。 可能会有计划让它在未来采取行动,尽管如果这成为现实,我希望信息本身("建议它也许应该")也会改变。

无论如何,以下是您需要了解的内容:

  1. git rebase主要是一系列自动化git cherry-pick操作。
  2. 每个git cherry-pick都是一种合并,很像git merge(但让它做樱桃选择而不是合并很奇怪)。
  3. 当发生合并冲突并且 Git 像这样在中间停止时,您可以完全控制结果。 您有责任构建正确的最终提交。

在这种情况下,答案是:确保文件位于工作树中的正确位置,然后对该文件运行git add。 如果有必要——我认为不会——git rm跑或git rm --cached走错路。

同时,值得更详细地介绍上述三点中的每一点。 如果您已经非常熟悉变基,请随时直接转到合并冲突后暂存区域中的文件是什么?

变基是重复采摘樱桃,加上一点

变基是将提交复制到新的和改进版本的过程。 这意味着git rebase命令在最基本的级别需要三个输入:

  • 当前分支名称,无论它是什么。
  • 要复制的提交列表。 复制是必要的,因为任何现有提交的任何部分都无法更改。 这包括源快照父提交哈希 ID。 当我们使用变基时,我们这样做是为了"更改"快照和/或父哈希 ID。 我们真的做不到这一点;相反,我们进行新的和改进的提交,它们具有不同的哈希 ID,然后相互链接(然后是新的基础)。
  • 副本要去的地方。

我们可以从进行一组提交开始:

I--J--K   <-- feature (HEAD)
/
...--G--H   <-- master

(较新的提交显示在右侧;分支名称查找我们所说的"在分支上"的最后一个提交;并且每个提交都向后链接到其父提交。

同时,其他人做了一些新的提交L,并(以某种方式)把它放在我们的master上:

I--J--K   <-- feature (HEAD)
/
...--G--H--L   <-- master

我们现在必须采用我们的I-J-K提交,这是好的,并制作新的和改进的版本,将提交L考虑在内,如下所示:

I--J--K   [abandoned]
/
...--G--H--L   <-- master

I'-J'-K'  <-- feature (HEAD)

因此,要复制的提交集只是IJK。 这些字母代表实际的 Git 哈希 ID,这些 ID 又大又丑且看起来随机;找出它们的唯一方法是运行git log或类似。

放置副本的地方是"提交后L"。 这样,它们就会在更新master之后出现。

复制三个提交后,分支名称必须移动 - 相当尖锐。 分支名称是我们和 Git查找(最后一个)提交的方式,通常我们通过添加一个新提交来一步一步地移动它们。 新提交会自动指向上次提交的内容;现在,新提交最后一次提交。

不过,在这里,我们将把提交L作为一种临时分支,添加三个新的提交,然后将名称feature拉过来,以便它指向K'而不是K。 这放弃了最初的三个提交(所以重要的是没有其他人这些提交,以免他们将它们用于某些事情并想要保留它们)。

  • Git 很容易找到当前的分支名称(Git 必须一直这样做,所以在内部它非常简单)。

  • 为了找到要复制的提交,我们使用一个参数运行git rebase,该参数告诉 Git不要复制什么。 也就是说,我们运行git rebase master,例如。 这告诉 Git不要复制提交LHG或更早的任何内容。

    Git 一开始就永远不会复制L,因为要复制的提交集以K结尾。 但是最初的复制副本以相反的顺序进行 -K,然后是J,然后是I,然后是H,然后是G,然后是之前的所有内容:git log将显示的所有内容。 从这个列表中,我们必须减不应该复制的提交,该列表包括我们从master开始并向后工作找到的一组提交。

    (这个"从分支名称开始,向后工作"的主题在 Git 中一遍又一遍地出现。

  • 为了找到放置副本的位置,Git 使用了我们在命令行上给出的参数master

因此,在git rebase master中,master的单一参数既提供了不复制的内容,也提供了放置副本的位置。 有时,这个单一的论点是不够的;为此,我们有git rebase --onto. 我们可以跳过这里的细节,留待以后。

樱桃采摘是合并

让我们看看git cherry-pick实际上是如何工作的。 想象一下,我们已经开始变基了:

git rebase master

并且已经走到了这一步:

I--J--K   <-- feature
/
...--G--H--L   <-- master

I'  <-- HEAD

在这里,在这个临时分支上,我们通过樱桃挑选"复制"了提交IGit复制究竟是如何提交I的? 让我们考虑一下什么是提交:

  • 它是(每个源文件的)(只读)快照...
  • 。元数据包括一些名称和电子邮件地址以及日志消息,以及对于 Git 的内部工作至关重要的父提交哈希 ID

因此,提交I拥有所有文件,但其父提交,提交H也是如此。 因此,Git 可以运行:

git diff <hash-of-H> <hash-of-I>

以查看哪些文件在HI之间发生了更改

要"复制"这些更改以I',Git 现在应该签出提交L并应用相同的更改。 如果我们更改了README文件和另一个文件f1.c,并且他们只更改了Documentation/background.txt文件,则我们的更改很容易溜进:这里不会有冲突。

但。。。如果他们也更改了README文件怎么办? 如果他们在顶部添加一些行,我们的更改靠近底部怎么办? Git 需要知道更改移动的行,以解释他们在顶部添加的内容。

这 - 将他们的更改与我们的更改相结合 - 正是合并的作用。 Git 需要将H中的快照与I中的快照进行比较,以了解我们做了什么,还需要将H中的快照与L中的快照进行比较,看看他们做了什么。

鉴于这两个比较,Git 现在可以将我们的更改与其更改合并。 如果合并的更改不冲突,Git 可以将这两组更改从合并基础H应用于README,为我们提供新提交I'的新README。 然后 Git 应该继续进行新的提交,但这个新提交不是合并提交,它只是一个普通的提交。 此外,此普通提交的父级是 commitL。 所以这让我们承诺I',并让我们走到这一步。

为了复制提交J,Git 做同样的事情。 不过,这一次,"合并"比较了IJ中的快照,以查看我们更改了什么,并将I中的快照与I'中的快照进行比较,以查看"它们"更改了什么。

想一想。 这可能很明显有效,也可能无效。 (事实上,它确实有效,大多数时候都很好。

这就是挑选的基本机制:Git 执行标准的三向合并,"合并库"是要复制的提交的父级当前提交是我们现在签出并由HEAD找到的提交,而"其他"提交(git merge调用theirs的提交)是我们正在复制的提交

这意味着在挑选樱桃期间,包括由变基调用的挑选,--theirs选项意味着我们的提交--ours选项意味着...好吧,这很混乱:它是第一次樱桃选择的提交L,现在是提交I',我们通过复制I构建的提交。 所以--theirs是我们的,--ours是...混合:他们的加上我们的。

不过,这就是樱桃采摘正在合并的原因。 有三个提交作为三个输入;Git 使用通常的三向合并算法来组合两组更改。 最终,只是提交本身不是合并。

如果我们有合并冲突,我们有责任解决它们。

解决合并冲突

最后要知道的是——尽管这是你在学习git merge工作原理时应该真正学习的东西,你应该在学习变基之前这样做——是git merge使用 Git 的索引(又名暂存区域)工作。 不过,我不会再写一遍,而是链接到合并冲突后暂存区域中的文件是什么? 在解决合并冲突时,您的工作是处理索引中的非零阶段编号文件,并将其替换为阶段零文件。 在执行此操作时,您可以对 Git 的索引进行任何您喜欢的更改。 索引/暂存区域中的任何东西,最终,当您运行git rebase --continue时,都会进入最终提交(然后是下一个樱桃选择中的--ours提交)。

最新更新