【问题标题】:git: merge two branches: what direction?git:合并两个分支:什么方向?
【发布时间】:2011-01-22 00:10:59
【问题描述】:

我们有以下情况:

             A --- B --- C --- ... --- iphone
           /
  ... --- last-working --- ... --- master

在 last-working 和 iPhone 之间,进行了 32 次提交。在 last-working 和 master 之间,进行了很多次提交。

我现在想要的是一个新的分支,我将 iphone 和当前的 master 合并在一起。并且在稍后的某个时间,这应该被合并到 master 中。

首先,我打算这样做:

git checkout iphone -b iphone31
git merge master

但后来我想,如果这样做会更好:

git checkout master -b iphone31
git merge iphone

现在我想知道。 结果会有什么不同?合并的行为会有所不同吗?

我已经尝试了这两种方法,正如我所料,我遇到了很多冲突,因为与 master 相比,iphone 真的很旧。现在我想知道合并它们的最简单方法。

也许甚至从 master 开始并将 iphone 的每个提交合并到它会更容易?喜欢这样做:

git checkout master -b iphone31
git merge A
git merge B
git merge C
...
git merge iphone

最后,当这个合并完成时(即所有冲突都已解决并且它正在工作),我想这样做:

git checkout master
git merge iphone31

【问题讨论】:

标签: git merge git-merge


【解决方案1】:

最好的方法实际上取决于其他人是否拥有您的代码的远程副本。如果 master 分支仅在您的本地机器上,您可以使用 rebase 命令以交互方式将功能分支中的提交应用到 master:

git checkout master -b iphone-merge-branch
git rebase -i iphone

请注意,这会改变您的新 iphone-merge-branch 分支的提交历史记录,这可能会给其他人稍后试图将您的更改拉入他们的结帐时出现问题。相比之下,merge 命令将更改应用为新的提交,这在协作时更安全,因为它不会影响分支历史记录。有关使用 rebase 的一些有用提示,请参阅 this article

如果您需要保持提交历史同步,最好执行合并。您可以使用 git mergetool 使用可视化差异工具以交互方式逐个修复冲突(有关此的教程可以是 found here):

git checkout master -b iphone-merge-branch
git merge iphone
git mergetool -t kdiff3

第三个选项,如果你想绝对控制这个过程,那就是使用 git cherry-pick。您可以使用 gitk(或您最喜欢的历史查看器)查看 iphone 分支中的提交哈希,记下它们,然后将它们单独挑选到合并分支中 - 随时修复冲突。这个过程的解释可以是found here。此过程将是最慢的,但如果其他方法不起作用,则可能是最好的后备选项:

gitk iphone
<note down the 35 commit SHA hashes in this branch>
git checkout master -b iphone-merge-branch
git cherry-pick b50788b
git cherry-pick g614590
...

【讨论】:

  • 与合并相比,rebase 有什么优势?我不太明白。为什么 git rebase 比 git merge 更智能?但无论如何,在这里变基不是一种选择。
  • 变基将提交按时间顺序单独应用到您的分支中,这通过在历史记录中适合的点插入提交来显着减少冲突。考虑到变基不是您的选择,我建议您研究 git-mergetool 方法。
  • 但我也可以这样做,分别合并每个提交。那么 rebase 会有什么不同呢?
  • 当你合并时,你是在 HEAD 之上应用每个提交,这已经从另一个分支中分离出来 - 导致冲突。当您重新设置基准时,您可以有效地倒带时间,并在提交的位置应用(not 合并)提交。例如,2010 年 1 月 12 日在“iphone”中的提交将在 2010 年 2 月 14 日在“master”分支中的提交之前应用。
  • 不,结果非常非常不同。唯一相同的是您的最终工作副本。我建议您阅读我在解决方案中提到的链接,了解 rebase 命令的一些背景:gitready.com/intermediate/2009/01/31/intro-to-rebase.html
【解决方案2】:

你说:

iphone 分支与 master 相比已经很老了

你真的想将它们合并成一个新的分支吗?

master 和 iphone 分支的目的现在会非常不同(因为 iphone 已经很老了)。一个新的分支将 iphone 与 主人的祖先合并会更好吗?想一想。

我强烈建议您阅读Fun with merges and purposes of branches

读完那篇文章后,如果你仍然想合并 iphone 和 master,那么 @seanhodges 会解释如何很好地处理冲突。

【讨论】:

  • “目的”是什么意思?真的,我想要的是让我在 iphone 中所做的更改成为大师。当我启动那个 iphone 分支时,它是为了添加 iPhone 支持。已经完成并且工作正常,所以我现在想将它放在主分支中。我的问题是我建议的两种方法是否有任何区别。或者,如果有更简单的方法可以得到我想要的。
  • 该链接开始有见地:“在 git 中合并分支时,所有分支都被视为平等。”这通常是问题,不是吗?虽然看看链接建议在哪里做与你建议相反的事情:If you started working on... 'frotz' back when the 'master' release was 1.0, and later the codebase of 'master' has substantially changed and [branch 'add-frotz' doesn't] cleanly merge anymore into 'master'... you could choose to change the purpose of your 'add-frotz'... to a more specific "add frotz feature in a way that it merges cleanly to the 2.0 codebase" [&amp; merge with master first].
  • 这不是在回答 OP 的“有区别吗”的问题。这个答案更像是“为什么不这样做呢?”。是的,有时我们确实想合并一些非常落后的东西。例如,合并 gitignore 文件更改。
【解决方案3】:

关于替代品

git checkout iphone -b iphone31
git merge master

git checkout master -b iphone31
git merge iphone

他们会有相同的轻松或困难,就像争论一个杯子是半满的还是半空的。

版本树感知

我们如何看待版本树在某种程度上只是我们任意的看法。 假设我们有一个如下所示的版本树:

    A----------+
    |          |
    |          |
   \_/        \_/
    B          X
    |          |
    |          |
   \_/        \_/
    C          Y
    |
    |
   \_/
    D
    |
    |
   \_/
    E

假设我们要从 Y 签出一个新版本 Z 基于从 C 到 E 的更改,但不包括从 A 到 C。

“哦,那会很困难,因为没有共同的起点。” 不是真的。如果我们只是将对象放置得稍微不同 在这样的图形布局中

      /
    C+---------+
    | \        |
    |          |
   \_/         |
    D          B
    |         / \
    |          |
   \_/         |
    E          A
               |
               |
              \_/
               X
               |
               |
              \_/
               Y

现在事情开始看起来很有希望了。注意我没有 在这里改变了任何关系,箭头都指向相同的方向 在上图中和版本 A 仍然是共同的基础。 只改变了布局。

但现在想象一棵不同的树是微不足道的

    C'---------+
    |          |
    |          |
   \_/        \_/
    D          B'
    |          |
    |          |
   \_/        \_/
    E          A
               |
               |
              \_/
               X
               |
               |
              \_/
               Y

任务就是正常合并版本 E。

所以你可以合并任何你想要的东西,唯一影响 难易度是在 您选择作为起点或共同基础以及合并到的位置。 您不仅限于使用您的自然起点 版本控制工具建议。

对于某些版本控制系统/工具,这可能并不简单, 但如果一切都失败了 没有什么可以阻止您手动执行此操作 签出版本 C 并将文件另存为 file1, 签出版本 E 并将文件另存为 file2, 签出版本 Y 并将文件另存为 file3, 并运行kdiff3 -o merge_result file1 file2 file3

回答

现在,对于您的具体情况,很难说具体是什么 产生最少问题的策略,但是 如果有很多变化会产生某种冲突 可能更容易拆分和合并较小的部分。

我的建议是 由于 last-working 和 iphone 之间有 32 次提交, 例如,您可以从 master 的分支开始,然后 合并前 16 个提交。如果结果是 太麻烦了,还原并尝试合并 8 个第一次提交。 等等。在最坏的情况下,您最终会一一合并 32 个提交中的每一个, 但这可能比必须处理所有 一次合并操作中累积的冲突(和 在这种情况下,您正在使用真正不同的代码库)。

提示:

在纸上画出版本树并用箭头记下你想要的 合并。如果您拆分流程,请在完成时划掉 在几个步骤中。这将使您更清楚地了解您想要什么 去实现,你到目前为止做了什么,还剩下什么。

我真的可以推荐KDiff3,它是一个出色的差异/合并工具。

【讨论】:

  • “假设我们要根据从 C 到 E 的更改从 Y 签出一个新版本 Z,但不包括从 A 到 C 所做的更改。”我不想排除任何提交。我真的不明白这与我想要的有什么关系。我只想将master与iphone合并。
  • "我的建议是,由于 last-working 和 iphone 之间有 32 次提交,因此您可以从 master 分支开始,然后合并前 16 次提交。"那会更容易吗?对于每次提交,我是否不必一次又一次地解决相同的冲突?就像我在一个提交中添加了一行,在另一个提交中添加了另一行。现在同时处理这两者比做两次更容易,不是吗?
  • “他们会有相同的轻松或困难,”这正是我的问题。那么我一开始建议的两种方式都会产生完全相同相同的结果吗?所以 Git 合并是 100% 对称的?
  • 再次引用我的主要问题:“现在我想知道。结果会有什么不同?合并的行为会不同吗?”
  • “我不想排除任何提交。”是的,我理解这一点,并且我的回答的那部分比严格需要的更笼统,很抱歉没有说得更清楚。我的观点是,你可以合并任何你想要的东西,只需指出一个起点/终点。
【解决方案4】:

您可能希望在实验之前对您的工作进行本地备份,以便在您不了解发生的情况时重新启动。

如果您想保留它以供参考,请为您的工作创建一个备份分支,以免在 rebase 期间丢失它。

git checkout iphone -b iphone_backup

从master创建一个新分支

git checkout master -b iphone31

然后在它之上重新设置基准。

git rebase -i --onto iphone31 iphone

上面的 Sean 关于 rebase 一次应用一个提交是正确的。但是不要担心你变基到的分支上的现有提交——它们不会被改变。 Rebase 将新的(适应的)提交放在它们之上。一次完成一个提交时,您可以更好地控制冲突。但重要的是,您必须将工作重新建立在 master 之上,而不是反过来,这样 master 的历史就不会改变。

或者,你只能这样做

git rebase -i master iphone

如果您不关心备份 iphone 分支。查看 rebase 文档以获取详细信息和替代方案http://git-scm.com/docs/git-rebase

【讨论】:

    【解决方案5】:

    有区别。


    在合并期间始终检查目标分支(即,如果将 A 合并到 B,则检查 B)。


    人们常说合并方向无所谓,但这是错误的。尽管无论合并方向如何,生成的内容都是相同的,但有几个方面是不同的:

    1. 最终的合并提交中列出的差异会因方向而异。
    2. 大多数分支可视化工具将使用合并方向来决定哪个分支是“主要”分支。

    详细来说,想象一下这个夸张的例子:

    • 您在 1000 次提交后从 MASTER 分支出来,并将其命名为 DEVELOP(或者在跟踪分支场景中,您已经有一段时间没有获取数据了)。
    • 您将一个提交添加到 DEVELOP。您知道此更改没有冲突。
    • 您想将更改推送到 MASTER。
    • 您错误地将 MASTER 合并到 DEVELOP(即 DEVELOP 在合并期间被签出)。然后,您将 DEVELOP 作为新的 MASTER 推送。
    • 生成的合并提交中的差异将显示 MASTER 中发生的所有 1000 次提交,因为 DEVELOP 是参考点。

    这些数据不仅毫无用处,而且很难了解正在发生的事情。大多数可视化工具会让您的 DEVELOP 行看起来一直是主要的,其中包含 1000 个提交。

    我的建议是:在合并期间始终检查目标分支(即,如果将 A 合并到 B,则检查 B)。

    • 如果您正在处理并行分支并希望定期从主分支引入更改,请检查您的并行分支。差异是有意义的——您将看到在主分支中对您的分支所做的更改。
    • 当您完成并行工作并希望将更改合并到主分支中时,请检查主分支并与并行分支合并。差异再次有意义 - 它会显示您对主分支的并行更改。

    在我看来,日志的可读性很重要。

    【讨论】:

    • 你说的是git中--first-parent的概念,当你显示第一个父级的日志图时,它的行为会有所不同。我还为它制作了一个示例存储库github.com/ChuckGitMerge/FirstParentTest
    猜你喜欢
    • 2021-01-18
    • 2012-04-23
    • 2014-09-23
    • 2013-05-23
    • 2019-04-15
    • 2013-04-11
    • 2012-05-24
    • 2011-09-20
    • 2012-03-05
    相关资源
    最近更新 更多