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

5
假设我们使用 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 魔法可以帮忙?也许我们一开始就做得不好?
以下讨论更好地说明了这个问题,我们处于“仅错误修复!”状态:

方法0 -- 不要这么做?

自从我们团队开始采用这种版本增量和标记的方式,我一直不确定这是否是一个好的实践。但基本上它是有效的,我们一起定义了工作流程的细节,并且我们的客户、合作伙伴和测试团队要求我们发布候选版本,就像它们是实际发布的一样。当版本x.y.z经过成功测试后,它不会被改变地发布到生产环境中,然后将release合并到main中。但问题仍然存在:一旦在main中进行了热修复,并且需要将其回滚到develop中,我们又会遇到版本冲突。

方法1 -- 挑选代码?

抱歉,我不会这样做。 我太经常看到挑选代码的坏处了。 而且它们是违反Gitflow规范的。

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

这就是我们现在所做的。该过程并未自动化。每次更改release中的版本号时,以下合并都会出现冲突,必须手动解决。我们接受它,但对此感到不满意。 方法3--不要那么频繁地合并? 我想这将是不好的做法。我们希望将质量改进合并到所有分支中。 方法4--使用合并选项--ours或类似选项? 问题在于自动“解决”合并冲突是基于文件的,从我所了解的情况来看,并非基于行或块。 我们需要保留develop中的版本号,但是这些文件pom.xmlpackage.json中的其他更改可能在任一侧,不能盲目地覆盖这些更改,因此这些类型的冲突我们希望手动查看和解决。 我虽然愿意接受任何关于此方面的建议! 方法5--将版本号移至单独的文件中? 这样一来,我们就可以将冲突减少到一个单一的位置,可以使用 --ours 轻松解决。 虽然较新的 Maven 版本似乎可以实现,但我不知道如何在 package.json 中引用外部定义的版本号。 有人尝试过这种方法并且推荐继续走下去吗?

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

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

我不喜欢有与实际开发无关的额外提交,而且我看不到清理Git历史记录的可能性。
所以这将是一个有趣的后续问题:我知道我可以将D变基/压缩成C,但我不知道如何将A或B变基/压缩成C。还有其他人知道吗?
-----B---------
      \
---A---C---D---

方法七 -- 在release分支中准备版本?

类似于之前的方法,我们可以在release分支上提交一个版本号,然后将其合并到develop分支中,避免冲突。这样,我们就不需要在release分支中进行还原提交,只需使用git reset --hard HEAD^命令将分支指针移回即可,或者干脆不推送该提交,这样准备提交就位于两个分支之间。

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

以下文章描述了使用中间分支来完成拉取请求的类似事情,但这需要几个手动步骤,不能解决我的问题。

方法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

我在网上搜索发现需要隐藏本地更改,但这当然不是一个选项。

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

我考虑这些冲突是有结构的,甚至可以完全预测,因此应该可以编写一个小的shell脚本来grep/sed冲突以自动解决并提交合并。但我犹豫是否要在这里付出大量努力,希望其他人能给我启示!


1
我在想是否可以依靠Rerere来做到这一点:您可以在合并之前将版本冲突解决方案写入rr-cache,然后git就不会引起冲突。或者另外一种选择是...我刚刚发现了这个(也许有这个警告?) - Didier L
谢谢这些想法!我已经用Rerere做了一些测试,但它似乎只适用于非常特定的数据,没有通配符,没有模式,因此仅在您多次重新应用完全相同的合并时才有用。第二个想法是编写自定义合并策略,据我所知,这看起来很有前途。但是我暂时不愿意付出那种努力。此外,我更喜欢适用于任意文件格式的解决方案,而不仅仅是POMs。 - Lynax
我对这个主题还没有完全完成。我倾向于使用第7种方法“中间提交”,也许加上一些漂亮的脚本,或者计划B是编写使用启发式算法的自定义冲突解决器。 - Lynax
对于Rerere,我认为也许可以通过在两个分支上触发冲突来进行训练,这两个分支唯一的区别就是版本,使用git merge -X ours或类似的方法(不确定-X ours是否适用于Rerere)。对于自定义合并策略,请注意我提供的链接已经为pom.xml提供了解决方案。大多数项目只在1或2种不同的文件格式(例如package.json)中存储版本,因此如果您只需要第二个实现,那么这似乎是可以接受的。 - Didier L
2个回答

2
注意:类似这样的最佳实践很难定义,因为每个人的情况可能都不同。
话虽如此,我们的一个项目与您的情况类似:我们使用Git Flow,我们的develop分支构建号始终与release分支构建号不同。我们潜在的理想方案尚未实施,但可能类似于您提出的第8种方法,其中我们将在不将其硬编码到提交中的情况下将版本注入构建管道(即根本不修改版本文件)。不过,这样做的缺点是仅凭代码无法确定特定提交所代表的版本。但如果我们实施了这一点,我们可以用特定版本对提交进行标记。我们还可以将提交ID以及版本信息集成到工件元数据中,以供轻松查找。
我们目前使用的解决方案是结合了4、5和7这几种方法。我们将版本文件分开(你提到的第5种方法),每次我们创建一个发布(或修复)分支时,第一次提交只会将版本文件更改为即将发布的版本(你提到的第7种方法)。我们确保正在使用release的源代码库始终包含最新主分支上的代码,这样无论何时我们将release部署到生产环境中,都可以将其干净地合并到main。(请注意,虽然我们仍然像Git Flow建议的那样使用--no-ff, 但关键是我们如果想要快进,是可以快进的)
现在,在您将release分支合并到main之后,Git Flow建议将release合并回develop,但我们发现将main合并回develop稍微更有效一些,以便main的尖端也在develop上,但是如果release上出现重要的错误修复,我们有时会在部署之前将其合并回develop。无论哪种方式,这两个合并回develop的操作都将与develop上的版本文件产生冲突,我们使用您的第四种方法自动选择这些文件的develop版本。这使得合并可以完全自动化,但是有时仍然需要手动解决其他冲突,就像在developrelease同时进行正常开发的过程中一样。但是至少通常很干净。
请注意,我们方法的一个副作用是,我们的版本文件在developmain上始终不同,对我们来说这没关系。

一定点赞!抱歉回复晚了。你准确地描述了情况。虽然你的建议没有解决问题,但仍然提供了有用的见解,非常感谢!不幸的是,“--ours”选项对我们来说是不可行的。至于第5个问题,我仍然没有找到NPM package.json的解决方案。也许我会慢慢尝试使用第7或第9个选项。顺便说一句,我完全同意你关于“--no-ff”的评论。 - Lynax
1
@Lynax 关于#4,我们实际上不使用自动冲突解决(-X ours)。我们已经完全将版本文件分开,因此版本是该文件中唯一会更改的内容。 (任何其他元数据都在另一个文件中。)因此,我们完全跳过develop上的发布版本更改。自动化基本上是 git merge release-branch --no-commit,然后重置并恢复所有版本文件以其开发版本,最后提交以完成合并。 - TTT

0

那使用外部工具来管理版本怎么样呢? 我们使用GitVersion 来处理这个问题。现在我不确定是否有更聪明的方法,但一种强制的方法是将类似于 <version>${env.GitVersion_SemVer}</version> 的代码添加到你的pom.xml中,其中 env.GitVersion_SemVer 是从 GitVersion 输出的结果。


很抱歉,我并不真正理解那个工具的技术作用。它是在构建时将版本写入我的文件中吗?还是仅提供变量?我如何在NPM package.json中使用它? - Lynax
该工具将值设置到变量中,稍后可以访问这些变量。这是我在发布流水线中使用它的方式 https://github.com/mermaid-js/mermaid-cli/blob/master/.github/workflows/release-publish.yml#L46 和 https://github.com/mermaid-js/mermaid-cli/blob/master/.github/workflows/release-publish.yml#L103。 - Mindaugas Laganeckas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接