git:合并两个分支:啥方向?

Posted

技术标签:

【中文标题】git:合并两个分支:啥方向?【英文标题】: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

【问题讨论】:

相关(至少对我而言):***.com/questions/1241720/… git checkout -b iphone31 等价于git checkout iphone -b iphone31 git log --first-parent 的行为会有所不同 【参考方案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)。


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

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

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

您在 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

以上是关于git:合并两个分支:啥方向?的主要内容,如果未能解决你的问题,请参考以下文章

你啥时候会使用不同的 git 合并策略?

GIT 分支管理:创建与合并分支解决合并冲突

Git学习笔记

Git分支操作

Git分支操作

Git 分支合并