我们有以下情况:
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
git checkout -b iphone31
是否等同于 git checkout iphone -b iphone31
?
关于备选方案
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,它是一个出色的差异/合并工具。
它们是有区别的。
始终在合并期间检出目标分支(即,如果将 A 合并到 B,则检出 B)。
人们常说合并方向无所谓,但这是错误的。尽管无论合并方向如何,生成的内容都是相同的,但有几个方面是不同的:
结果合并提交中列出的差异将根据方向而有所不同。大多数分支可视化器将使用合并方向来决定哪个分支是“主要的”。
为了详细说明,想象一下这个夸张的例子:
您在 1000 次提交后从 MASTER 分支出来,并将其命名为 DEVELOP(或者在跟踪分支场景中,您已经有一段时间没有获取了)。
您将一个提交添加到 DEVELOP。您知道此更改没有冲突。
您想将更改推送到 MASTER。
您错误地将 MASTER 合并到 DEVELOP(即 DEVELOP 在合并期间被签出)。然后,您将 DEVELOP 作为新的 MASTER 推送。
结果合并提交中的差异将显示在 MASTER 中发生的所有 1000 次提交,因为 DEVELOP 是参考点。
这些数据不仅无用,而且很难阅读正在发生的事情。大多数可视化工具会让你的 DEVELOP 线看起来一直是主要的,其中包含 1000 个提交。
我的建议是:在合并期间始终检查目标分支(即,如果将 A 合并到 B,则检查 B)。
如果您正在处理并行分支并希望定期从主分支引入更改,请检查您的并行分支。差异是有意义的——您将看到在主分支中对您的分支所做的更改。
当您完成并行工作并希望将您的更改合并到主分支中时,请检查主分支并与您的并行分支合并。差异再次有意义 - 它会显示您对主分支的并行更改。
在我看来,日志的可读性很重要。
--first-parent
的概念,当您显示与第一个父级的日志图时,它的行为会有所不同。我还为它制作了一个示例存储库 github.com/ChuckGitMerge/FirstParentTest
最好的方法实际上取决于其他人是否拥有您的代码的远程副本。如果主分支仅在您的本地计算机上,您可以使用 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
...
你说:
与master相比,iphone分支非常古老
你真的想把它们合并成一个新的分支吗?
master 和 iphone 分支的目的现在会非常不同(因为 iphone 已经很老了)。将iphone与master的祖先合并的新分支会更好吗?想想看。
我强烈建议您阅读 Fun with merges and purposes of branches。
阅读完那篇文章后,如果您仍然想合并 iphone 和 master,那么 @seanhodges 会解释如何很好地处理冲突。
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].
您可能希望在实验之前对您的工作进行本地备份,以便在您停止了解发生的情况时重新启动。
如果您想保留它以供参考,请为您的工作创建一个备份分支,以免在变基期间丢失它。
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。
不定期副业成功案例分享