【问题标题】:How do Git merges handle simultaneous commits?Git 合并如何处理同时提交?
【发布时间】:2014-04-29 19:07:48
【问题描述】:

给定一个包含两个分支的 repo,每个分支都有独立的提交:

Branch  Commits
------  -----------

final:        e-g-i
             /     \
master: a-b-c-d-f-h-?

上图中的字母很重要:即“master”和“final”同时在开发中,必须保留两个分支中的提交。

将“master”合并到“final”,然后将 that 合并回“master”会更好(更安全)吗?还是从“final”直接合并到“master”会保留之前的提交?

我了解可能会出现合并冲突。我更担心在将“final”合并到“master”之后,我们不会丢失任何提交给“master”的代码。

【问题讨论】:

    标签: git github merge commit


    【解决方案1】:

    将“master”合并到“final”,然后将其合并回“master”会更好(更安全)吗?还是从“final”直接合并到“master”会保留之前的提交?

    后者:从“final”直接合并到“master”,保留之前的提交。

    git 与 SVN 等其他版本控制系统不同。对于不熟悉的人,SVN 将分支视为“桶”——分支/桶是固定的(稳定的),您必须小心在桶之间移动提交。在 SVN 中,在将“最终”分支“重新集成”(伪合并)回“主”之前,您必须将任何最近的“主”提交合并(复制)到“最终”中。 'Reintegration' 有效地将 'final' 的尖端复制到 'master' 的尖端,有效地将 'master' 替换为 'final' 的尖端的精确副本。您可能会丢失“master”中未首先合并(复制)到“final”的任何提交。

    就像我说的,git 是不同的。在 git 中,我喜欢将存储库树(提交)视为稳定的,而不是将其视为移动的“分支”——将分支视为挂在提交上的小标签/装饰。

    我要画你的树有点不同 - 事实上,我会在创建提交'i'之前绘制它:

                  final
                    |
                e - g
              /
    a - b - c 
              \
                d - f - h
                        |
                      master
    

    现在让我们提交'i':

                      final
                        |
                e - g - i
              /
    a - b - c 
              \
                d - f - h
                        |
                      master
    

    只有两件事发生了变化。 (1)暂时忽略分支“装饰”,我们看到新提交“i”是从其父提交“g”创建的,(2)最终“装饰”从提交“g”移动到“i”。

    让我们将 final 合并到 master 中——也就是说,让我们更新 master 以便它包含来自 final 的更改:

                      final
                        |
                e - g - i
              /           \
    a - b - c              j
              \           /|
                d - f - h  |
                           |
                         master
    

    现在'master'分支装饰移动了。 “最终”的分支装饰保持不变。 “j”提交是从 2 个父项创建的合并提交:“h”和“i”。

    但是,如果我们将 master 合并到 final 中会怎样——也就是说,更新 final 以便它包含来自 master 的更改:

                         final
                           |
                e - g - i  |
              /           \|
    a - b - c              j
              \           /
                d - f - h
                        |
                      master
    

    现在“final”移动了,“master”留在了原地。请注意,合并是真正的合并 - 两个分支的更改都在提交“j”中。无论哪个分支合并到哪个分支,'j' 都是相同的 - 唯一的区别是哪个分支“装饰”被移动了。 (而且那些分支“装饰”很容易移动。)

    为了完整起见,让我们将另一个分支合并回第一个分支(谁先合并到谁并不重要——只要我们这次向另一个方向合并)。请注意,只有装饰移动 - 不需要新的提交(在 git 行话中,它是“快进”),因为“j”已经包含来自两个分支的两组更改:

                         final
                           |
                e - g - i  |
              /           \|
    a - b - c              j
              \           /|
                d - f - h  |
                           |
                         master
    

    事实上,让我们在几天后看看树(我删除了'final'分支 - 我已经完成了它):

                e - g - i
              /           \
    a - b - c              j - k - l - m
              \           /            |
                d - f - h              |
                                       |
                                     master
    

    好的,你能从树上分辨出哪个分支最初是“主”,哪个分支最初是“最终”:e-g-i 还是 d-f-h?有关系吗?

    在 git 中,合并是真正的合并。没有“桶”,您不必像 SVN 那样在“桶”/分支之间移动提交。只有您正在进化的“树”和分支(“装饰”)正在为您在其中的位置添加书签。

    【讨论】:

    • 通过“看到分支'装饰'移动了吗?”我假设你的意思是分支'final'然后指向i,而不是g。
    • 对于它的价值,你可以告诉哪个分支最初是master(嗯,有点),因为合并提交j的第一个父分支是在运行 git merge 时是最新的。因此,在这种情况下,如果j^ 是h 并且j^2 是i,则表明提交j 是通过将i 合并到h 中创建的。 (这就是 git rev-list 中的 --first-parent 的用途。)不过在这里不会有任何显着差异,所以这只是一个旁注。
    【解决方案2】:

    没有区别。您可以看到 git 将通过执行合并的更改

    git diff master...final
    

    和

    git diff final...master
    

    其中每一个都将显示来自其合并基础的每个分支的累积差异。 git 试图将这两组累积的更改放在一起,它没有“分支优先”或类似的概念。

    git 永远不会丢失提交。 Go back to the basics,从那里追逐链接,并特别注意文档指出与其他 vcs 的差异的地方。

    【讨论】:

      【解决方案3】:

      合并不是基于时间的,合并的结果1不依赖于哪个分支“合并”以及哪个分支“合并到”。2支持>

      “合并如何工作”的缩写形式是这样的。您在某个(当前/“我们的”/“merge-into”)分支上,您发出命令 git merge <commit-ID>,3 所以 git 会这样做:

      1. 找到当前分支的“合并基础”,以及命名的提交。

        在您的图表中,您要么在分支final,其提示提交为i,要么您在分支master,其提示提交为h。如果您在分支 final 上,则将其命名为 commit h,如果您在分支 master 上,则将其命名为 commit i,因此无论哪种方式,有问题的两个提交都是 h 和 @987654331 @,合并基础是 commit c:这就是你从这两个提交向后工作时找到共同点的地方。

      2. 运行 git diff(带有 -M 选项)以将合并基础与当前提示进行比较(例如,c 与 h)。

      3. 运行git diff(再次使用-M)以将合并基础与其他提交进行比较(例如,c vs i)。

      4. 使用第 3 步中的差异——“他们”所做的更改——从中删除在第 2 步中已经找到的任何更改,然后在当前提示中进行所有相同的更改(例如,进行所有未包含在h,将它们应用于h 中的树)。

      当尝试应用第 4 步中的“剩余更改”时,如果周围的上下文不匹配(或者,对于文件创建、删除或重命名,没有明显的正确选择),就会发生冲突。

      如果没有冲突并且你没有使用--no-commit,那么 git 会提交生成的树,这当然会在当前(“我们的”)分支上进行。但是无论哪种方式,您都会得到相同的 tree。 (当然,如果有冲突,你得到的树取决于你如何解决它们。我只是说自动结果。)


      1更准确地说,是 tree 结果。所做的 commit does 取决于哪个分支是“来自”,哪个是“进入”:特别是,生成的合并提交的第一个父级是“into”或“ours”分支,第二个父分支是“from”或“theirs”分支。当然,提交本身放在“进入”/“我们的”当前分支上。

      2无论如何,禁止使用 --ours、-X ours 等选项。

      3使用git merge <em>branchname</em>,branchname 部分被解析为提交 ID。不过,Git 确实保留了合并提交消息的原始参数,因此分支名称通常比原始 SHA-1 更好。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-21
        • 2019-03-17
        • 2016-05-22
        • 2015-03-10
        • 1970-01-01
        • 2011-06-23
        • 2013-06-20
        相关资源
        最近更新 更多