TL;DR
To extract the "ours" or "theirs" version, use a merge tool independent of Git. To add the final resolution, use git add
.
Long
Each merge tool operates independently of Git, so for this specific aspect of the question, it's necessary to refer to the merge tool documentation.
When using git checkout --ours
or git checkout --theirs
, the complexity of the Git index becomes apparent. The Git index, also called the staging area or cache, is where you and Git construct the next commit to make. To add the final resolution, use the git add
command.
For more detailed instructions and explanations, refer to the full text.
git merge <commit-or-branch-specifier>
Git找到了三个提交:
- 其中一个是您的当前提交,这是您随时在使用的提交,因此除了您可以通过名称
HEAD
或单字符@
(例如,git rev-parse HEAD
或git rev-parse @
)引用它的外,它并没有什么特殊之处。 - 另一个是您刚刚命名的提交。如果您运行了
git merge otherbranch
,则可以运行git rev-parse otherbranch
查看其当前的提交哈希ID。(分支名称具有移动属性:即现在由分支名称标识的提交不一定与昨天或明天由该名称标识的提交相同。分支名称的这种移动是分支增长的方式。)当然,如果您运行了git merge a123456
,那么另一个提交,即--theirs
所用的提交,就是哈希IDa123456
。 - 最后一个提交是合并基础,Git会自动为您找到。它通过使用从您的提交和其他提交的父链接来向后工作,直到找到两个分支首次重新汇合的适当点为止,从而找到此提交。
在找到这三个提交后,Git会实际运行:
git diff --find-renames <merge-base> <ours> # see what we changed
git diff --find-renames <merge-base> <theirs> # see what they changed
合并进程——以“合并”作为动词,可以说是找到这三个提交,执行差异并组合更改的过程。当两组更改影响相同行时,会发生合并冲突。
对于没有合并冲突的文件,Git 将结果放入您的工作树(作为普通文件)和索引(作为文件的特殊 Git 形式,准备提交)。因此,对于未发生冲突的文件,通常不需要进行其他操作。
然而,在出现合并冲突时,Git 会执行两个异常操作:首先,它将合并冲突版本写入工作树,使您可以将其作为普通文件进行编辑。其次,它写入到索引中,不是文件的一个版本,而是所有三个版本:合并基础版本、我们的版本和他们的版本。
Git 称这些额外版本为“高级阶段”。第一阶段是合并基础,没有“--base”选项访问它,但您可以使用“git show:1:
实际上,当您运行“git mergetool”时,它会在索引中找到三个版本,将它们提取为常规(非 Git 化)文件,并在这三个文件上运行实际的合并工具。合并工具假定要做正确的事情(无论结果如何),将这三个文件组合成一个合并的文件,之后,“git mergetool”可以在结果上运行“git add”。
不过,从命令行——这是我进行合并的方式——您只需编辑带有冲突标记的工作树文件,并找出正确的结果。将其写出,使用“git add”添加生成的文件,就可以了,因为“git add”注意到文件以三个阶段版本的形式存在并擦除这三个版本,改为写入零阶段。
一旦有了零阶段(不再有 1-3 阶段),则认为该文件已解决。
现在,git checkout --ours -- path
告诉 Git:将阶段2的版本从索引中取出并放到工作树中。带有 --theirs
的版本告诉 Git 选择使用阶段3的版本。在两种情况下,索引和它的三个版本都保持不变。这个命令 只能 从索引中提取到工作树。如果文件名不像一个选项,那么这里的 --
只是为了防止文件名与选项混淆。使用 --
是一个好习惯,但大多数人都不这样做。
由于索引仍然有所有的三个版本,因此该文件尚未解决。运行 git add
命令将工作树文件放入 slot zero,擦除1-3号条目,此时该文件已经解决。
奇妙的是,运行git checkout HEAD -- 路径
或 git checkout otherbranch -- 路径
会导致该文件被解决。这是 Git 的一种实现方式: 在内部,当您使用git checkout name -- path
时,Git 首先要定位给定name
(提交哈希或像HEAD
或otherbranch
这样的名称)中文件的 Git 形式。然后,它必须将该 Git 形式的文件 复制 到索引中…这个复制操作会擦除1-3号条目,并写入普通的0号条目。最后,Git 将从索引条目零提取文件(Git 格式)到工作树。
此“先写入索引,然后从索引提取到工作树”的副作用是,如果该文件处于冲突状态-有1-3号版本处于活动状态-那么它就不再处于冲突状态了!因此:
git checkout --ours -- file
不会解析该文件(因为它从索引槽2中提取),但是:
git checkout HEAD -- file
does指的是通过从当前提交中提取文件,进入索引槽位0,清除1-3;然后提取刚刚写入的槽位0条目。
编辑,2022年6月:自我撰写以上内容以来,Git增加了一对新命令git switch
和git restore
,将git checkout
曾经结合的多个不同作用分开。 如果您的Git版本为2.23或更高版本,则可能希望使用git restore
而不是git checkout
来提取单个文件。(使用旧的git checkout
,很容易以错误的方式调用它:即使有经验的Git用户有时也会在这里出错。)不过,git restore
的--ours
和--theirs
标志与git checkout
的工作方式相同:它们始终选择槽位2和3,即使在重新设置基准期间也是如此(请参见下面的附言)。
附言
以上描述了由git merge
命令调用的合并操作流程。 Git的内部合并引擎——“合并作为动词”——也被git cherry-pick
、git revert
和git rebase
(等等)使用。在使用这些命令时,通过不同的过程选择填充槽位1的“合并基础”提交,而“我们的”和“他们的”提交——例如通过--ours
与git checkout
或git restore
配合使用的提交——并不总是简单明了。我将把详细信息留给其他StackOverflow问题和答案,因为这已经足够消化了。
--theirs
总是“我们的”,但--ours
就...嗯,它相当令人困惑:在要被复制的 第一个 提交上,--ours
明显是他们的。之后,--ours
也是我们的,因为已经修改以适应他们的!我在关于变基的答案中涵盖了这一点。这就是我更喜欢在这里提到“索引槽”的原因。 - torek