git:合并两个分支:什么方向?


69

我们有以下情况:

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

在最后工作和iPhone之间,进行了3​​2次提交。在最后工作和大师之间,进行了许多提交。

我现在想要的是一个新分支,我将iPhone和当前的主设备合并在一起。并在以后的某个时间将其合并到主服务器中。

首先,我计划做:

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

相关的(至少对我来说):stackoverflow.com/questions/1241720/...
cregox

git checkout -b iphone31相当于git checkout iphone -b iphone31
杰拉德

Answers:


45

关于替代品

git checkout iphone -b iphone31
git merge master

git checkout master -b iphone31
git merge iphone

他们将有相同的难易程度,就像争论玻璃杯是半满还是半空一样。

版本树感知

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

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

假设我们要基于从C到E的更改从Y签出新版本Z,但不包括从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

回答

现在,对于您的特定情况,很难确切地说出哪种策略将产生最少的问题,但是,如果发生许多更改而产生某种冲突,则可能更容易拆分和合并较小的部分。

我的建议是,由于最后工作和iphone之间存在32个提交,因此您可以例如从master分支开始,然后合并到前16个提交中。如果发现这很麻烦,请还原并尝试合并8个首次提交。等等。在最坏的情况下,您最终会逐一合并32个提交中的每一个,但它可能比必须在一个合并操作中处理所有累积的冲突要容易得多(在这种情况下,您正在使用真正不同的代码库) 。

提示:

在纸上画一个版本树,并用箭头标记您要合并的内容。如果您将过程分为几个步骤,请在完成工作时将它们划掉。这将使您更清楚地了解要实现的目标,到目前为止已完成的工作以及还剩下什么。

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


“并且假设我们要基于从C到E的更改从Y签出新版本Z,但不包括从A到C的更改。” 我不想排除任何提交。我真的不知道这和我想要的有什么关系。我只想将master与iphone合并。
艾伯特2010年

“我的建议是,由于最后工作和iphone之间有32次提交,因此您可以例如从master分支开始,然后合并到前16次提交中。” 这样会更容易吗?我是否不必每次提交一次又一次解决相同的冲突?就像我在一个提交中添加了一行,然后在另一提交中添加了另一行一样。现在将它们一起处理将比两次处理要容易得多,不是吗?
艾伯特2010年

1
“他们将有相同的难易程度”,这正是我的问题。所以我一开始建议的两种方式都会产生完全相同的结果?那么Git合并是100%对称的吗?
艾伯特2010年

再次引用我的主要问题:“现在我想知道。结果会有什么不同?合并的行为会有所不同吗?”
艾伯特2010年

1
“我不想排除任何提交。” 是的,我理解这一点,并且我的回答部分比一般需要的要笼统,对不起,我没有说清楚。我的观点是,您可以合并任何您想要的东西,只需指出一个起点/终点即可。
hlovdal10年

26

它们是有区别的。


始终在合并过程中签出目标分支(即,如果将A合并到B中,则签出B)。


人们经常说合并方向并不重要,但这是错误的。尽管不管合并方向如何,结果内容都是相同的,但是有几个方面是不同的:

  1. 结果合并合并中列出的差异将根据方向而有所不同。
  2. 大多数分支可视化工具将使用合并方向来确定哪个分支是“主要”分支。

详细说明一下,想象一下这个夸大的例子:

  • 您从MASTER分支到落后1000次提交,并将其命名为DEVELOP(或者在跟踪分支方案中,已经有一段时间没有提取了)。
  • 您将一个提交添加到DEVELOP中。您知道此更改没有冲突。
  • 您想将更改推送到MASTER。
  • 您错误地将MASTER合并DEVELOP中(即在合并过程中检出了DEVELOP)。然后,按DEVELOP作为新的MASTER。
  • 由于DEVELOP是参考点,因此生成的merge-commit中的差异将显示MASTER中发生的所有1000次提交。

这些数据不仅无用,而且很难读取正在发生的情况。大多数可视化工具都会使您的DEVELOP行始终是主要的,并带有1000次提交。

我的建议是: 始终在合并过程中签出目标分支(即,如果将A合并到B,则签出B)。

  • 如果您在并行分支上工作,并且想定期从主分支引入更改,请检出并行分支。差异会很有意义-您将在分支的主分支中看到所做的更改。
  • 当您完成并行工作并希望将所做的更改合并到主分支中时,请签出主分支并与并行分支合并。diff再次有意义-它将显示并行更改对主分支的作用。

我认为日志的可读性很重要。


6

最好的方法实际上取决于其他人是否具有您的代码的远程副本。如果master分支仅在本地计算机上,则可以使用rebase命令以交互方式将来自Feature分支的提交应用于master:

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

请注意,这会更改新的iphone-merge-branch分支的提交历史记录,这可能对以后尝试将所做更改放入其结帐的其他任何人造成问题。相比之下,merge命令将更改作为新提交应用,在协作时更安全,因为它不影响分支历史记录。有关使用rebase的一些有用技巧,请参阅本文

如果您需要使提交历史保持同步,则最好执行合并。您可以使用git mergetool通过可视化的diff工具来逐一地交互式解决冲突(有关此问题的教程,请参见此处):

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

如果要对流程进行绝对控制,第三个选择是使用git cherry-pick。您可以使用gitk(或您最喜欢的历史记录查看器)查看iphone分支中的提交哈希,记下它们,然后将它们分别挑选到合并分支中,以解决冲突。有关此过程的说明,请参见此处。如果其他方法无法解决,则此过程将是最慢的过程,但可能是最佳的后备选项:

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
...

与合并相比,该基础将具有什么优势?我不太明白 为什么git rebase在git merge上会更智能?但是无论如何,在这里重新部署是不可行的。
艾伯特2010年

变基化按时间顺序将提交分别应用到您的分支中,这可以通过在历史记录适合的位置插入提交来显着减少冲突。考虑到重新部署不是您的选择,建议您研究git-mergetool方法。
seanhodges

但是我也可以这样做,将每个提交单独合并。那么重新设置基准将有什么区别?
艾伯特2010年

1
合并时,您将在HEAD之上应用每个提交,此提交已与另一个分支分开-导致冲突。重新设定基准时,可以有效地缩短时间,并在提交的位置应用(而不是合并)提交。例如,在2010年1月12日在“ iphone”中提交的提交将应用在2010年2月14日在“ master”分支中提交的提交之前。
seanhodges

2
不,结果非常不同。唯一相同的是您的最终工作副本。我建议您阅读我在解决方案中提到的有关rebase命令背景的链接:gitready.com/intermediate/2009/01/31/intro-to-rebase.html
seanhodges 2010年

2

你说:

iphone分支比高手还老

您是否真的想将它们合并成一个新的分支?

现在,master和iphone分支的用途已经大不相同了(因为iphone很旧)。将iphone与master祖先合并的新分支会更好吗?想一想。

我强烈建议您阅读“有趣的分支合并和目的”

阅读该文章后,如果您仍然想合并iPhone和Master,则@seanhodges解释了如何很好地解决冲突。


“目的”是什么意思?真的,我想要的是让我在iphone中所做的更改成为大师。当我启动该iphone分支时,它就是添加iPhone支持。这样就可以正常工作了,所以我现在想在master分支中使用它。我的问题是我建议的两种方法是否有任何区别。或者,如果有更简单的方法来获得我想要的东西。
艾伯特2010年

该链接深刻地开始了:“在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" [& merge with master first].
ruffin 2012年

这不是在回答OP的“有区别”的问题。这个答案更像是“为什么不这样做呢?”。是的,有时候我们确实想合并一些远远落后的东西。例如,合并gitignore文件更改。

1

您可能需要在进行实验之前对工作进行本地备份,因此如果您不再了解发生了什么,则可以重新启动。

为您的工作做一个备份分支,以便在保留基准时不要丢失它,以备您参考。

git checkout iphone -b iphone_backup

从master创建一个新分支

git checkout master -b iphone31

然后重新建立基础。

git rebase -i --onto iphone31 iphone

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

或者,您只能做

git rebase -i master iphone

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.