使用 git merge,如何将错误修正合并到多个长期分支中?

Using git merge, how can I merge a bugfix into multiple long-term branches?

最近在工作中,我们已经从使用 SVN 切换到使用 git。我们必须维护我们软件的多个版本,并且我们之前将它们设置为分支。所以在使用 git svn clone 之后,我们最终得到了 4 个分支:master、5.0、5.1 和 5.2。

注意:我们使用 master 作为我们的开发分支。 5.0、5.1 和 5.2 都是 master 的分支,用作生产分支。

几天过去了,对 master 进行了相当多的更改。刚刚在 5.2 中发现了一个错误,我们想修复它。我们尝试了 Merging one change to multiple branches in Git 的解决方案,但运气不错。我们总是可以轻松地合并回 master,但是每当我们合并回 5.2 时,我们都会遇到一堆冲突。我们所看到的显示此过程的所有内容(为错误修复创建一个新分支并将其合并回开发和生产)表明它应该像这样简单:

(master)$ git checkout -b bugfix
# fixed bug and committed

(bugfix)$ git checkout master
(master)$ git merge bugfix
# successful merge

(master)$ git checkout 5.2
(5.2)$ git merge bugfix
# successful merge

然而,当我们到达最后一行 git merge bugfix 时,我们会遇到无数的冲突。我们在错误修复中甚至没有触及的文件冲突。我们做错了什么?

注意:我们还尝试通过从 5.2 开始修复错误分支来执行此过程。然后将 bugfix 合并到 5.2 中很容易,但是将 bugfix 合并到 master 中会产生冲突。

其他显示过程的地方:

几年前,我们将一个非常大的项目从 SVN 转移到 git。这是我一直在思考的问题。

首先(这不是对您问题的回答,而是对问题 "why are you asking the question?" 的回答),请阅读以下内容: http://nvie.com/posts/a-successful-git-branching-model/ 并暂时接受它可能是正确的,这意味着放弃您的一些 SVN 思维方式。那对我来说是最难的。

你说完了,我来回答问题。

最佳路线是:

  • 确保您始终可以将任何旧分支合并到任何新分支而不影响新分支

  • 在您想要修复的最旧分支上执行您需要修复的任何错误修复。然后按顺序将其合并到新分支中,边走边修复合并冲突。

执行其中的第二项会使第一项始终有效(如果您考虑的话)。

此技术可扩展到许多不同年龄的分支,前提是任何给定分支中的变更集始终包括所有所有旧分支的变更集。

但是,上面的第二个要点假设了一个理想世界:

  • 您在执行错误修复时确切地知道哪个是您需要执行错误修复的最旧分支(即没有人来找您说“嘿,我们需要向后移植该错误修复,它也会给客户 X 带来问题)。

  • 每个分支的错误修复始终相同(例如,您对较旧的分支进行创可贴最小熵修复)。

您可以通过使用 git cherry-pick 选择提交到旧分支来解决第一个问题。但是然后(这是重要的一点)将修复合并到较新的分支(即使它已经存在)。您可以通过明智且极其谨慎地使用以下方法来做到这一点:

git checkout newer
git merge older   # check it's all merged up
git checkout older
git cherry-pick xxxxx
... fix up cherry pick, check it works ...
git checkout newer
git merge -s ours older

请注意,这标志着合并,但实际上忽略了旧分支中已更改的所有内容,因此在更改之前检查它是否已合并非常重要。

第二种情况可以类似处理。对 newer 应用真正的修复。检查 older 是否合并到 newer,然后将创可贴应用到 older,然后使用 git merge -s ours older

git merge是自共同基础以来的所有变化。如果你想记录一个变化到多个历史的合并,你必须让它成为所有历史中一些祖先内容的唯一变化,像这样:

git checkout -b bugfix `git merge-base 5.0 5.1 5.2 master`
# so now bugfix is an ancestor of all of the above

# fix the bug here, then:
git commit -m 'bug fixed'

git checkout 5.0; git merge bugfix     # bugfix has just the one change
git checkout 5.1; git merge bugfix
git checkout 5.2; git merge bugfix
git checkout master; git merge bugfix

最好一直返回并从最初引入错误的提交中分支错误修复,但如果它已经存在足够长的时间,这并不总是会产生干净的合并。

我们的问题源于 git svn clone。虽然它确实给了我们一个 git 回购,而且它似乎给了我们正确的分支,但这些分支实际上并不像我们预期的那样源于 master。这相当令人困惑,因为当我们查看日志时,我们可以看到分支创建之前的日志在两个分支上具有相同的哈希值。事实上,唯一让我们知道这正在发生的事情是查看来自 TortoiseGit 的 Revision Graph。我没有屏幕截图,但这基本上是图表的样子:

    5.2          master
 origin/5.2   origin/master
      `-.       .-'
         `-. .-'
        a3f2d6205

所以我们可以看到两个分支都源于根提交 a3f2d6205。我们需要让 5.2 脱离 master。因此,这是我们为实现目标而采取的步骤。

(master)$ git reset --hard 0b73ab0
(master)$ git branch 5.2.new
(master)$ git merge --no-ff --log origin/master
(master)$ git push origin master
(master)$ git checkout 5.2.new
(5.2.new)$ git merge -Xtheirs --log origin/5.2
(5.2.new)$ git push -u origin 5.2.new
(5.2.new)$ git push origin --delete 5.2

# hopped on the "central" git repo
(master)$ git branch -m 5.2.new 5.2

# run these commands for all of the developers repos
(master)$ git pull
(master)$ git remote prune origin
  • (master)$ git reset --hard 0b73ab0 - 0b73ab0 是 5.2 分支的提交。
  • (master)$ git branch 5.2.new - 从这一点开始创建一个新分支
  • (master)$ git merge --no-ff --log origin/master - 我们需要将 master 恢复到原来的位置,但无论出于何种原因,如果它进行快进合并,它会弄乱词干,因此将其标记为 --no-ff以确保不会发生这种情况。
  • (master)$ git push origin master - 将我们的新主控推送到服务器
  • (master)$ git checkout 5.2.new - 结帐 5.2.new
  • (5.2.new)$ git merge -Xtheirs --log origin/5.2 - 我们需要将 5.2 恢复到它应该在的位置。使用 -Xtheirs 这样我们就不必处​​理冲突了。
  • (5.2.new)$ git push -u origin 5.2.new - 将此分支推送到 "central" git 仓库。
  • (5.2.new)$ git push origin --delete 5.2 - 删除 dumb 5.2 分支。

跳转到 "central" git 存储库。

  • (master)$ git branch -m 5.2.new 5.2 - 将 5.2.new 分支重命名为 5.2.

确保每个人都是最新的:

  • (master)$ git pull - 拉取最新信息
  • (master)$ git remote prune origin - 删除任何陈旧的分支

最后,说完一切后,修订图应该看起来像:

             5.2
         origin/5.2
          .-'
       .-'
    master
origin/master
      |
      |
  a3f2d6205