我在存储库的分支中有一个分支。通过重命名其中一个顶级目录,上游存储库结构已略有更改。我的分支创建一个新的子目录,其中包含受影响目录中的文件。当我尝试变基时,我遇到了许多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 没有对它采取任何行动。 可能会有计划让它在未来采取行动,尽管如果这成为现实,我希望信息本身("建议它也许应该")也会改变。
无论如何,以下是您需要了解的内容:
git rebase
主要是一系列自动化git cherry-pick
操作。- 每个
git cherry-pick
都是一种合并,很像git merge
(但让它做樱桃选择而不是合并很奇怪)。 - 当发生合并冲突并且 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)
因此,要复制的提交集只是I
、J
和K
。 这些字母代表实际的 Git 哈希 ID,这些 ID 又大又丑且看起来随机;找出它们的唯一方法是运行git log
或类似。
放置副本的地方是"提交后L
"。 这样,它们就会在更新master
之后出现。
复制三个提交后,分支名称必须移动 - 相当尖锐。 分支名称是我们和 Git查找(最后一个)提交的方式,通常我们通过添加一个新提交来一步一步地移动它们。 新提交会自动指向上次提交的内容;现在,新提交是最后一次提交。
不过,在这里,我们将把提交L
作为一种临时分支,添加三个新的提交,然后将名称feature
拉过来,以便它指向K'
而不是K
。 这放弃了最初的三个提交(所以重要的是没有其他人有这些提交,以免他们将它们用于某些事情并想要保留它们)。
Git 很容易找到当前的分支名称(Git 必须一直这样做,所以在内部它非常简单)。
为了找到要复制的提交,我们使用一个参数运行
git rebase
,该参数告诉 Git不要复制什么。 也就是说,我们运行git rebase master
,例如。 这告诉 Git不要复制提交L
、H
、G
或更早的任何内容。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
在这里,在这个临时分支上,我们通过樱桃挑选"复制"了提交I
。Git复制究竟是如何提交I
的? 让我们考虑一下什么是提交:
- 它是(每个源文件的)(只读)快照...
- 。元数据包括一些名称和电子邮件地址以及日志消息,以及对于 Git 的内部工作至关重要的父提交哈希 ID。
因此,提交I
拥有所有文件,但其父提交,提交H
也是如此。 因此,Git 可以运行:
git diff <hash-of-H> <hash-of-I>
以查看哪些文件在H
和I
之间发生了更改。
要"复制"这些更改以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 做同样的事情。 不过,这一次,"合并"比较了I
和J
中的快照,以查看我们更改了什么,并将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
提交)。