不同版本间 Git 合并的最佳实践

Posted

技术标签:

【中文标题】不同版本间 Git 合并的最佳实践【英文标题】:Best Practices for Git Merge between different versions 【发布时间】:2021-10-16 05:51:46 【问题描述】:

假设我们使用Gitflow,我们从develop 中分离出一个发布分支,最终将合并到maindevelop。 在release 上只有质量改进。 其中大多数需要部署到集成阶段,因此它们在几个 pom.xml(多模块)和 package.json 中的版本会更新并标记在 release 分支上。

develop 上,未来版本会定期(不稳定)开发功能,并且已相应地设置了版本。 有时,release 的改进会合并回develop。 出现合并冲突,在下图中用 X 标记。

main     ----------------------o----
                              /
release        o---o-----o-o-o
              /     \     \   \
develop  ----o---o---x--o--x-o-x----
                           ^
               we are here |

例子:

release 上,版本号为1.0.0-SNAPSHOT。 在develop 上,分支后版本号为1.1.0-SNAPSHOT。 新功能进入develop,版本号保持不变。 release 中的版本偶尔会递增(并标记)为1.0.11.0.21.0.3 等等。 当我想将版本 1.0.x 合并到 1.1.0 而共同祖先是 1.0.0 时,当然存在冲突。 (我们完全理解那里发生的事情,不需要解释。)
$ git checkout develop
$ git merge --no-commit --no-ff release

Auto-merging pom.xml
CONFLICT (content): Merge conflict in pom.xml
...
Auto-merging client/package.json
CONFLICT (content): Merge conflict in client/package.json
Automatic merge failed; fix conflicts and then commit the result.

我们正在寻找处理这种情况的想法。 一些研究表明,这不是一个不常见的问题,所以我找到了几个建议。 大多数情况下,据说只是手动解决冲突。 但我仍然渴望找到一种可以在脚本中自动化的方法。 也许有一些 Git 魔法可以提供帮助? 也许我们一开始就很糟糕?

下面的讨论带来了更好的形象,我们在“只有错误修正!”:

https://softwareengineering.stackexchange.com/questions/390536/git-flow-pull-requests-multi-destination-branches-and-conflicts-how-is-it-sup

方法 0 -- 不要这样做?

自从我们的团队开始进行这种版本增量和标记以来,我一直不确定这是否是一种好的做法。但它基本上是有效的,我们一起定义了工作流程的细节,我们的客户、合作伙伴和测试团队要求我们交付候选版本,就好像它是实际版本一样。当 x.y.z 版本测试成功后,它会原样进入生产环境,然后 release 合并到 main 中。但问题依然存在:一旦在 main 中制作了修补程序并应该向后移植到 develop,我们将再次遇到版本冲突。

方法一——樱桃采摘?

对不起,我不会这样做。 我经常读到樱桃采摘是邪恶的。 他们会反对 Gitflow。

http://www.draconianoverlord.com/2013/09/07/no-cherry-picking.html Git - Is cherry-picking a bad practice if I want to have a consistent workflow? https://medium.com/@vitalishcharbin/danger-of-cherry-pick-42a56141577f Why does cherry-picking make the repo unstable?

方法 2 -- 接受手动解决?

这就是我们现在所做的。该过程不是自动化的。每次更改release 中的版本号时,以下合并都会出现必须手动解决的冲突。我们接受它,但对此不满意。

方法 3——不要那么频繁地合并?

我想那将是不好的做法。我们希望将质量改进合并到我们所有的分支机构。

方法 4 -- 使用合并选项 --ours 或类似选项?

问题是合并冲突的自动“解决”是基于文件的,据我所知,不是基于行或基于块的。 我们需要保留来自develop 的版本号,但是这些文件中的其他更改 pom.xmlpackage.json 可能位于任一侧并且不能被盲目地覆盖,因此我们会处理此类冲突想要手动查看和解决。 不过,我愿意接受这方面的任何建议!

方法 5 -- 将版本号移动到单独的文件中?

通过这种方式,我们会将冲突减少到一个可以使用--ours 轻松解决的位置。 虽然使用较新的 Maven 版本似乎是可能的,但我不知道 package.json 引用外部定义的版本号的方法。 有没有人在这方面有很好的经验,并会推荐这种方式走得更远?

Maven version with a property https://maven.apache.org/maven-ci-friendly.html#Multi_Module_Setup

方法 6 -- 在 develop 中准备和重置版本?

我看到jgitflow-maven-plugin 的这种行为已经超过 6 年没有维护了。我们可以在develop 中提交,将release 版本写入文件,然后合并,将版本改回原来的版本。

我不喜欢有与实际开发无关的额外提交,而且我认为不可能清理 Git 历史记录。

所以这将是一个有趣的后续问题:我知道我可以将 D 变基/压缩为 C,但我不知道如何将 A 或 B 变基/压缩为 C。还有其他人吗?

-----B---------
      \
---A---C---D---

方法 7 -- 在 release 中准备版本?

与之前的方法类似,我们可以在release 中提交,写入目标版本,然后合并到develop 而不会发生冲突。然后,我们不需要在release 中进行还原提交,而只需将分支指针移回git reset --hard HEAD^ 和/或根本不推送它,因此这个准备提交将位于两个分支“之间”。

-----B-----------
      \
       B'
        \
---A-----C---D---

下面的文章描述了使用中间分支的类似事情(以满足拉取请求的要求),但它是几个手动步骤并不能解决我的挑战。

https://medium.com/@jshvarts/dealing-with-conflicts-when-merging-release-to-develop-da289a572f0d https://www.reddit.com/r/git/comments/e7zwl6/dealing_with_conflicts_when_merging_release_to/

方法 8 -- 准备版本而不提交?

我最喜欢的解决方案是只在本地 develop 中编写目标版本而不提交,然后将 release 合并到该版本上......但 git merge 不允许这样做。我没有看到任何覆盖此行为并忽略未合并的开关

error: Your local changes to the following files would be overwritten by merge:
        client/package.json
        ...
        pom.xml
Please commit your changes or stash them before you merge.
Aborting

搜索网络告诉我存储本地更改,但这当然不是一种选择。

方法 9 -- 编写程序来解决冲突?

我认为这些冲突结构良好,甚至可以完全预测,因此应该可以编写一个小的 shell 脚本来 grep/sed 冲突,以便自动解决并提交合并。 但我犹豫着在这里付出巨大的努力,希望得到其他人的启迪!

【问题讨论】:

【参考方案1】:

注意:此类事情的“最佳实践”很难定义,因为每个人的情况可能不同。

话虽如此,我们的一个项目与您的情况相似:我们使用 Git Flow,并且我们的 develop 分支构建号始终与 release 分支构建号不同。我们潜在的理想解决方案尚未实现,但可能类似于您建议的方法 8,我们将在提交中将版本注入构建管道而不对其进行硬编码(即,甚至不修改版本文件全部)。但是,这样做的缺点是您无法仅根据代码知道特定提交所代表的版本。但是你可以用一个特定的版本来标记提交,如果我们实现了这可能就是我们要做的。我们还可以将提交 ID 与版本信息一起烘焙到工件元数据中,以便于查找。

我们目前使用的解决方案是方法 4、5 和 7 的组合。我们分离版本文件(您的方法 5),并且每次创建release(或hotfix)分支时,第一次提交仅将版本文件更改为即将发布的版本(您的方法 7)。我们确保release 总是 中包含main 的尖端,因此无论何时我们将release 部署到生产环境中,我们都可以干净地将release 合并到main。 (请注意,我们仍然按照 Git Flow 的建议使用 --no-ff,但关键是如果我们愿意,我们可以快进。)

现在,在你完成 release 分支到 main 后,Git Flow 建议将 release 合并回 develop,但我们发现将 main 合并回 develop 效率稍高一些,因此提示的main 也在develop 上,但有时我们也会在部署之前将release 合并回develop,如果release 上出现重要的错误修复。无论哪种方式,这两个合并回develop 将始终与develop 上的版本文件发生冲突,我们使用您的方法4 自动选择这些文件的develop 版本。这使得合并返回可以完全自动化,但是,有时还有其他冲突需要手动解决,就像developrelease 同时发生的正常开发过程一样。但至少它通常是干净的。

请注意,我们方法的副作用是我们的版本文件在developmain总是不同,这对我们来说很好。

【讨论】:

【参考方案2】:

使用外部工具管理版本怎么样? 我们为此使用GitVersion。现在我不确定是否有更聪明的方法,但一个蛮力的方法是在你的 pom.xml 中有类似 <version>$env.GitVersion_SemVer</version> 的东西,其中 env.GitVersion_SemVer 是 GitVersion 的输出。

【讨论】:

以上是关于不同版本间 Git 合并的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

git最佳实践之feature和hotfix分支

Git最佳实践-Git flow

使用 TeamCity 合并语义版本控制的最佳实践是啥

版本控制最佳实践

Git分布式版本控制系统最佳实践

保留旧版本的 Git 最佳实践