git - git:将滞后分支合并到当前分支
问题描述
通常,当我们必须在当前分支(mybranch)上合并一些其他分支(branch-to-merge)时,我们会这样做:
git checkout mybranch
git pull origin branch-to-merge
我理解它的工作方式是将 mybranch 指针移动到与分支合并的负责人相同的提交。如果它的线性场景,一切顺利,否则我们需要解决合并冲突。
但是如果分支合并在 mybranch 之后,它会如何发挥作用呢?我基本上想将分支合并中的更改(它是滞后的)集成到我当前的功能分支中,以利用分支合并中所做的代码更改。
解决方案
TL;DR:什么都没有发生(Git 说Already up to date.
并停止并成功退出)。
长
您的理解并没有完全错误,但它缺少一两个关键细节,这将在一瞬间引起问题:
git checkout mybranch git pull origin branch-to-merge
... 会将 mybranch 指针移动到与分支合并的负责人相同的提交。
让我们在这里准确一点。git checkout mybranch
步骤:
- 从当前提交中删除所有文件(如 Git 的索引aka staging area中所记);
- 而是从名称指示的提交中提取文件
mybranch
;和 - 创建
mybranch
当前分支。
(或者,当然,它可能会因各种错误情况而失败,但我们可以假设这些不会发生。它也可以在保留未提交的工作的同时成功,如Checkout another branch when there are uncommitted changes on the current branch 中所述:this当它可以跳过特定文件的删除和替换时发生,Git 通常出于速度原因尝试这样做,无论这些文件中是否有未提交的工作)。
完成此结帐后,然后运行git pull
. 该pull
命令是其他两个 Git 命令的组合:
git pull
运行git fetch
。在旧版本的 Git 中,它确实是这样做的(它是一个 shell 脚本,它实际上使用各种参数运行)。git fetch
在当前的 Git 中,这是内置在pull
程序中的,但效果是一样的。此步骤获取您的存储库缺少的任何提交(origin
一个单独的 Git 存储库)。在正常情况下,此步骤还会更新origin/branch-to-merge
您自己的存储库中的名称。git pull
然后运行您编写的要运行的第二个命令。如果您没有设置任何内容,则默认为 usinggit merge
,但您可以将其设置为 rungit rebase
。与 fetch 步骤一样,旧脚本确实运行了这些;新的 C 代码内置了它们,但效果是一样的。传递给git merge
or的参数git rebase
有点复杂,但是如果我们假设你在git merge
这里使用,并且其他都正常,我们就得到了运行的效果git merge origin/branch-to-merge
(除了默认的提交消息)。但您也可以git merge --no-ff
在这里使用,或者,现在,git merge --ff-only
. 如果您使用git rebase
作为您的第二个命令,则图片会更加复杂。
如果它的线性场景,一切顺利,否则我们需要解决合并冲突。
这有许多棘手的细节和极端情况。该git merge
命令在多种场景下运行,并带有各种参数(--no-ff
, --ff-only
),并且每个参数都有不同的效果。但总的来说,我们可以将合并描述为以这种方式工作:
首先,Git 确保我们有一个“干净”的工作树(一些合并案例不检查,但如果您使用这些奇怪的合并变体之一,确保自己是这种情况是一个非常好的主意)。然后,Git使用来自的当前分支名称或来自的原始哈希 ID 来定位当前提交哈希 ID 。如果我们遇到合并冲突,这是“HEAD”或“我们的”提交。
HEAD
HEAD
Git 还会将参数指定的提交定位到
git merge
. 也就是说,origin/branch-to-merge
转换为原始提交哈希 ID。Git 会找到这个特定的提交。然后 Git 使用提交图计算两个提交的合并基础(请参阅有向无环图的最低公共祖先)。就确定合并结果而言,这是在某些方面最重要的提交。
我们可以通过各种方式绘制合并基础。例如,假设我们有这个:
o--L <-- mybranch (HEAD)
/
...--o--*
\
o--R <-- origin/branch-to-merge
在这里,名称mybranch
位于 commit L
,“左侧”或“本地”或 HEAD 或ours
提交。该名称origin/branch-to-merge
位于 commit R
:另一个,或“右侧”或“远程”或--theirs
提交。标记的提交*
是最好的共同祖先,所以提交*
是合并基础。
这种合并需要真正的合并。它可能会产生合并冲突,但如果没有,并且允许真正的合并,则此合并的结果是新的合并提交M
:
o--L
/ \
...--o--* M <-- mybranch (HEAD)
\ /
o--R <-- origin/branch-to-merge
因为这个提交是在“on”时进行的(mybranch
也就是说),所以分支名称现在指向 merge commit 。git status
on branch mybranch
mybranch
M
如果存在合并冲突,Git 在合并中间停止,所有三个提交都读入 Git 的索引,合并部分解决,部分未解决。作为人类操作 Git,你的工作是完成合并,清理 Git 留下的烂摊子;或者您可以选择运行git merge --abort
以丢弃部分结果,通过本质上重置为 commit 来清理混乱L
。(如果你开始一个“脏”合并然后使用git merge --abort
,这通常会很糟糕,这就是为什么你不应该首先开始一个“脏”合并。)
如果需要真正的合并并且您指定了--ff-only
,git merge
则甚至不会启动合并:它只是说快进是不可能的,并以错误终止。
此图说明了另一种情况:
...--o--L <-- mybranch (HEAD)
\
o--R <-- origin/branch-to-merge
L
在这里,“合并基础”——可以从提交和提交中获得的最佳共享提交——是R
commit L
。这是您的线性场景:Git可以简单地“向前移动分支名称”,同时还可以检查 commit R
,结果是:
...--o--L--o--R <-- mybranch (HEAD), origin/branch-to-merge
--ff-only
如果您指定或未使用,这是默认操作--no-ff
。但是,如果您指定--no-ff
了 ,Git 将继续执行完全合并:
...--o--L------M <-- mybranch (HEAD)
\ /
o--R <-- origin/branch-to-merge
Commit和以前一样M
是合并提交。commit的快照M
将与 commit 的快照匹配R
,因为合并提交快照是通过将合并基础 ( L
) 到每个提示提交 (L
和R
) 的更改组合而成的。从L
to的变化L
是空的,所以这会产生从L
to找到的变化R
。Git 将这些更改应用于合并库中的快照(即 in)L
,从而生成与 commit 匹配的快照R
。然后 Git 提交结果,因为没有合并冲突。
但是如果分支合并在 mybranch 之后,它会如何发挥作用呢?
请注意,在一种情况下,我们已经绘制了:
o--L <-- mybranch (HEAD)
/
...--o--*
\
o--R <-- origin/branch-to-merge
origin/branch-to-merge
后面就是这种情况mybranch
。只是,尽管落后了两次提交,但origin/branch-to-merge
也领先了两次提交。也就是说,有两个提交,沿着底线提交,它们是“on”(可访问)提交R
,而不是“on”(不可访问)提交L
。
让我们画出另一种情况:
...--o--R <-- origin/branch-to-merge
\
o--L <-- mybranch (HEAD)
L
在这里,和的合并基R
是R
。合并是不可能的:从R
to的差异R
是空的,并且应用差异从R
toL
产生快照 in L
。当 R 严格领先于 L 时, Git仍然可以执行相同的“强制合并”,但 Git 不会这样做。相反,它只是说它已经是最新的。
底线
合并命令:
- 定位要合并的提交(我们称它们
L
为R
两个提交的情况); - 定位合并基础(我们称它为
B
);和 - 使用它来驱动其余的动作。
对于具有递归或解析的标准双头合并,有三种可能性,按此顺序考虑:
B
=R
:发出“最新”消息并成功终止。B
=L
:考虑进行快进而不是合并。如果允许,检查 commitR
,更新当前分支中存储的哈希 ID,并成功终止。- 否则,如果允许(否
--ff-only
),则进行完全合并。如果这是成功的(并且既--no-commit
没有指定也没有--squash
指定),则进行新的合并提交,否则停止。对于--squash
,不R
记录in的哈希 IDMERGE_HEAD
;对于其他合并,请R
记录in的哈希 IDMERGE_HEAD
。
在B
= L
=的特殊情况下R
,我们首先进行B
=R
测试并说“最新”。该--no-ff
选项只是强制中间测试失败,以便我们继续第三种情况。
该--squash
选项确保合并提交M
只有一个父级。(它通过提前终止合并来实现这一点,就好像-n
已经指定了一样,这有点愚蠢:git merge -n --squash
如果你真的想要,你可以运行,并且它可以在不使用时将最终提交作为非合并提交-n
,然后声明合并完成。)该-n
选项确保 Git 不会立即进行最终提交,这允许您根据需要创建一个邪恶的合并。
如何查看合并基础
如果您擅长观察 Git 图(请参阅Pretty Git 分支图),您也许可以通过查看来发现合并基础,但是很多图都非常纠结。您可以运行git merge-base --all
:
git fetch
git merge-base --all mybranch origin/branch-to-merge
第二个命令吐出哈希 ID。理想情况下,它只输出一个哈希 ID:一个哈希 ID 是合并基础,一切都从那里开始。
如果这会打印两个或更多哈希 ID,则说明您有一个复杂的合并。默认(-s recursive
或 now, -s ort
)策略将合并合并基础,从结果中创建一个新的但临时的提交,然后将结果用作合并基础。这在理论上很简单,但很难理解,更难描述好。它也非常罕见,所以你可能不会遇到它。
推荐阅读
- javascript - 从使用 getElementsByClassName 或其他方法显示的所有 url 中删除 http:// 和 www
- python - 字典到字符串对列表
- scala - 错误:值 min 不是 (Int, Int) 的成员
- c# - 如何在 WPF 中添加用户名?
- c - 当我在 C 中运行 for 循环时程序停止响应
- esp32 - 无法为 ESP32 构建项目
- flutter - 如何在颤动表中显示用户输入
- javascript - 从控制器传递纬度和经度以使用 Googlemaps Codeigniter 进行查看
- javascript - 我在使用 React 制作的项目中遇到了一个未定义的错误
- coq - Coq 定理证明:peano 算术中的简单分数律