ChatGPT解决这个技术问题 Extra ChatGPT

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

我们有以下情况:

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

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

我现在想要的是一个新的分支,我将 iphone 和当前的 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
相关(至少对我而言):stackoverflow.com/questions/1241720/…
git checkout -b iphone31 是否等同于 git checkout iphone -b iphone31
git log --first-parent 的行为会有所不同

h
hlovdal

关于备选方案

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

回答

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

我的建议是,由于 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% 对称的吗?
再次引用我的主要问题:“现在我想知道。结果会有什么不同?合并的行为会不同吗?”
“我不想排除任何提交。”是的,我明白这一点,我的回答的那部分比严格需要的更笼统,很抱歉没有说得更清楚。我的观点是你可以合并任何你想要的东西,这只是指出一个起点/终点的问题。
R
Ryuu

它们是有区别的。

始终在合并期间检出目标分支(即,如果将 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
s
seanhodges

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

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

请注意,这会更改您的新 iphone-merge-branch 分支的提交历史记录,这可能会导致其他人稍后尝试将您的更改拉入他们的结帐时出现问题。相比之下,merge 命令将更改应用为新的提交,这在协作时更安全,因为它不会影响分支历史记录。有关使用变基的一些有用提示,请参阅 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 之上应用每个提交,这已经与另一个分支分道扬镳 - 导致冲突。当您变基时,您可以有效地倒带时间,并在提交的位置应用(而不是合并)提交。例如,2010 年 1 月 12 日在“iphone”中的提交将在 2010 年 2 月 14 日在“master”分支中的提交之前应用。
不,结果非常不同。唯一相同的是您的最终工作副本。我建议您阅读我在解决方案中提到的链接,了解有关 rebase 命令的一些背景知识:gitready.com/intermediate/2009/01/31/intro-to-rebase.html
G
Gordon

你说:

与master相比,iphone分支非常古老

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

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

我强烈建议您阅读 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" [& merge with master first].
这并没有回答 OP 的“有区别”的问题。这个答案更像是“为什么不这样做呢?”。是的,有时我们确实想合并一些非常落后的东西。例如,合并 gitignore 文件更改。
D
Doug Chamberlain

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

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

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