git 与 rebase 相比,合并 diffs 应用程序流程
git merge diffs application process compared to rebase
例如,我有以下内容:
A--B (master)
\
C--D (feature)
如果我这样做 git rebase master feature
git 在提交 A
和 C
之间采用 diff
并将其应用到提交 B
之上,然后它在提交 C
和 D
之间占用 diff
,并将其应用于新提交 C'
之上。 Git 称之为 replaying changes
。现在,如果我将 feature
合并到 master
中,实际上会发生什么过程?
根据我读过的内容,我的假设是 git 找到共同祖先,在这种情况下为 A
,然后:
- 在分支
feature
上的最后一次提交之间花费 diff
,此处在 A
和 D
之间
- 在分支
master
上的最后一次提交之间需要 diff
,这里是在 A
和 B
之间
- 将第 1 步和第 2 步中的
diff
应用于共同祖先
这是正确的吗?我决定确认这一点,因为在 git 文档中提到 merge
操作时也提到了 replaying changes
。
Git 最常用的算法是 recursive three-way merge。合并的三路部分是指两个分支头(B和D)及其祖先(A)。
首先,它确定了共同的祖先。在您的示例中,这很容易,但是分支之间有很多合并,这可能很重要。如果有多个候选祖先,它将在它们和他们的 候选祖先之间执行虚拟合并,并将该合并用作虚拟祖先。如果候选人的祖先无法解决,它将对他们的祖先进行另一次合并……依此类推,直到找到一个祖先。这是 "recursive" 部分。使用 rebase 更新功能分支的部分原因是为了保持分支祖先简单。
三向合并查找祖先和两个分支(A、B 和 D)中相同或不同的部分。
- 如果三者都同意,则使用 A(或 B 或 D)。
- 如果只有B和D一致,两个分支做同样的改变,B或D输出。
- 如果只有祖先(A)和一个分支同意,另一个分支进行了更改。
- 如果只有B和A一致,D(特征)做了改变,输出D。
- 如果只有D和A同意,B(master)做了修改,B就输出了。
- 如果都不同,则存在冲突。
Git 具有启发式识别文件何时被重命名或复制的额外能力。因此,如果功能将 foo 重命名为 bar 并进行小的更改,Git 通常可以识别功能的 foo 是 master 的 bar 并正确合并。
How diff works is by solving the longest common subsequence problem. If you want a lot of detail, here is a formal study of how diff3 works.
但是 Git 有多种合并策略,并且会选择它认为最好的一个。如果你认为它猜错了,通常是因为有很多冲突,你可以告诉它使用哪一个并用 -s
和 -X
配置它。您可以在 the git-merge man page.
中阅读有关这些策略的更多信息
这里有一些关于策略是什么以及何时使用它们的资源。
- When to use different strategies
- Pro Git - Advanced Merging
正确:git 从合并基础 (A
) 获取每个提示的差异并合并它们。
值得注意的是,发出 git merge
命令时所在的分支始终是结果合并提交的 first 父级,而分支-你要求它合并的提示是第二个;如果合并因冲突而停止,git checkout
的 --ours
和 --theirs
标志分别引用当前和待合并的提交。
在 git rebase
操作期间 git 进入 rebase 目标上的 "detached HEAD",然后(本质上是普通 rebase,但对于交互式 rebase 确实如此)cherry - 将每个提交都选择到它形成的新分支中。如果 cherry-pick 导致合并冲突,git 将 --ours
分配给您正在进行的提交——分离的 HEAD——并将 --theirs
分配给您正在 cherry-picking 的提交,这是你要重新定位的那个;所以在这种情况下 "ours" 和 "theirs" 的感觉是相反的。
由于 rebase 所做的选择与您提交的 re-base 一样多,您可以获得合并冲突,解决它们,继续,并获得另一组不同的合并冲突 - 或者在某些情况下,相同的合并冲突(在这种情况下设置 rerere.enabled
可能是个好主意)。
一旦 rebase 完成,git 调整分支引用以指向新建(不再是 "detached")HEAD 的尖端。
例如,我有以下内容:
A--B (master)
\
C--D (feature)
如果我这样做 git rebase master feature
git 在提交 A
和 C
之间采用 diff
并将其应用到提交 B
之上,然后它在提交 C
和 D
之间占用 diff
,并将其应用于新提交 C'
之上。 Git 称之为 replaying changes
。现在,如果我将 feature
合并到 master
中,实际上会发生什么过程?
根据我读过的内容,我的假设是 git 找到共同祖先,在这种情况下为 A
,然后:
- 在分支
feature
上的最后一次提交之间花费diff
,此处在A
和D
之间
- 在分支
master
上的最后一次提交之间需要diff
,这里是在A
和B
之间
- 将第 1 步和第 2 步中的
diff
应用于共同祖先
这是正确的吗?我决定确认这一点,因为在 git 文档中提到 merge
操作时也提到了 replaying changes
。
Git 最常用的算法是 recursive three-way merge。合并的三路部分是指两个分支头(B和D)及其祖先(A)。
首先,它确定了共同的祖先。在您的示例中,这很容易,但是分支之间有很多合并,这可能很重要。如果有多个候选祖先,它将在它们和他们的 候选祖先之间执行虚拟合并,并将该合并用作虚拟祖先。如果候选人的祖先无法解决,它将对他们的祖先进行另一次合并……依此类推,直到找到一个祖先。这是 "recursive" 部分。使用 rebase 更新功能分支的部分原因是为了保持分支祖先简单。
三向合并查找祖先和两个分支(A、B 和 D)中相同或不同的部分。
- 如果三者都同意,则使用 A(或 B 或 D)。
- 如果只有B和D一致,两个分支做同样的改变,B或D输出。
- 如果只有祖先(A)和一个分支同意,另一个分支进行了更改。
- 如果只有B和A一致,D(特征)做了改变,输出D。
- 如果只有D和A同意,B(master)做了修改,B就输出了。
- 如果都不同,则存在冲突。
Git 具有启发式识别文件何时被重命名或复制的额外能力。因此,如果功能将 foo 重命名为 bar 并进行小的更改,Git 通常可以识别功能的 foo 是 master 的 bar 并正确合并。
How diff works is by solving the longest common subsequence problem. If you want a lot of detail, here is a formal study of how diff3 works.
但是 Git 有多种合并策略,并且会选择它认为最好的一个。如果你认为它猜错了,通常是因为有很多冲突,你可以告诉它使用哪一个并用 -s
和 -X
配置它。您可以在 the git-merge man page.
这里有一些关于策略是什么以及何时使用它们的资源。
- When to use different strategies
- Pro Git - Advanced Merging
正确:git 从合并基础 (A
) 获取每个提示的差异并合并它们。
值得注意的是,发出 git merge
命令时所在的分支始终是结果合并提交的 first 父级,而分支-你要求它合并的提示是第二个;如果合并因冲突而停止,git checkout
的 --ours
和 --theirs
标志分别引用当前和待合并的提交。
在 git rebase
操作期间 git 进入 rebase 目标上的 "detached HEAD",然后(本质上是普通 rebase,但对于交互式 rebase 确实如此)cherry - 将每个提交都选择到它形成的新分支中。如果 cherry-pick 导致合并冲突,git 将 --ours
分配给您正在进行的提交——分离的 HEAD——并将 --theirs
分配给您正在 cherry-picking 的提交,这是你要重新定位的那个;所以在这种情况下 "ours" 和 "theirs" 的感觉是相反的。
由于 rebase 所做的选择与您提交的 re-base 一样多,您可以获得合并冲突,解决它们,继续,并获得另一组不同的合并冲突 - 或者在某些情况下,相同的合并冲突(在这种情况下设置 rerere.enabled
可能是个好主意)。
一旦 rebase 完成,git 调整分支引用以指向新建(不再是 "detached")HEAD 的尖端。