多迭代并行经验

多个迭代并行开发,一种可行的办法是使用泳道,把一个迭代作为一个泳道隔离开来。但是如果受限于基础设施和成本,只有一套开发环境,势必会需要合并到同一个分支。这个分支,如dev,它的版本号全部是 dev0.0.1-SNAPSHOT,不因为有新迭代合并上来而改变。

合并到一个分支会遇到代码冲突问题。业务代码冲突是无法解决的,这个只能从架构设计的角度,尽量让每次迭代不要修改同一个文件,除了业务代码还有一些公共文件,例如 pom.xml

首先项目要有版本号,这样在本地开发时可以使用本次迭代的版本号,这样别人在公共分支合并代码推包不会影响你的本地分支。在合并到公共分支后,pom.xml 合并冲突的问题就需要解决了。

这里分情况讨论。pom.xml 会有哪些冲突。

parent.version,version

git 合并的原理是:首先有一个 公共的祖先分支(ancestor),当前分支(current),其它分支(other), 当这三个不一样时,会使用特殊标记字符来标识。
底层是调用 git merge-file 命令,传入三个文件。
参考: https://cloud.tencent.com/developer/section/1138705

这样如果 parent.version 和 version 冲突,我们知道肯定是要使用当前分支的版本号(dev0.0.1-SNAPSHOT)。
要实现自定义合并策略,我们需要借住 git 提供的自定义合并驱动(custom merge driver)
简单的说

1
2
3
4
5
6
7
8
9
10
// 执行下面两行命令
git config --global merge.version_current.name "choose current when parent version and version conflict"
git config --global merge.version_current.driver "/home/relengxing/app/version_current %O %A %B"


// 根目录添加 .gitattributes 文件
// 复制以下内容到文件
pom.xml merge=version_current

// 当 parent.versionversion 有冲突时,会直接使用当前分支的版本的内容

当合并时,所有pom.xml 的合并策略会走 version_current 这段代码。
源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

package main

import (
"encoding/xml"
"fmt"
"github.com/vifraa/gopom"
"os"
"os/exec"
"strings"
)

func main() {
//ancestor := flag.String("ancestor", "", "祖先的")
//current := flag.String("current", "", "当前的")
//other := flag.String("other", "", "其他的")

ancestor := os.Args[1]
current := os.Args[2]
other := os.Args[3]
ancestorBuf, _ := os.ReadFile(ancestor)
currentBuf, _ := os.ReadFile(current)
otherBuf, _ := os.ReadFile(other)
//ancestorStr := string(ancestorBuf)
//currentStr := string(currentBuf)
otherStr := string(otherBuf)

var ancestorPom gopom.Project
var currentPom gopom.Project
var otherPom gopom.Project
xml.Unmarshal(ancestorBuf, &ancestorPom)
xml.Unmarshal(currentBuf, &currentPom)
xml.Unmarshal(otherBuf, &otherPom)

var parentVerion *string = nil
if currentPom.Parent != nil && *currentPom.Parent.Version != *otherPom.Parent.Version {
parentVerion = currentPom.Parent.Version
}
var version *string = nil
if *currentPom.Version != *otherPom.Version {
version = currentPom.Version
}
versionFmt := "<version>%s</version>"
if parentVerion != nil {
otherStr = strings.Replace(otherStr, fmt.Sprintf(versionFmt, *otherPom.Parent.Version), fmt.Sprintf(versionFmt, *parentVerion), 1)
}
if version != nil {
otherStr = strings.Replace(otherStr, fmt.Sprintf(versionFmt, *otherPom.Version), fmt.Sprintf(versionFmt, *version), 1)
}
// 重写文件
os.WriteFile(other, []byte(otherStr), os.ModePerm)

//git merge-file -L current -L ancestor -L other $2 $1 $3

cmd := exec.Command("git", "merge-file", "-L", "current", "-L", "ancestor", "-L", "other", current, ancestor, other)
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
os.Exit(1)
}
fmt.Println(out)
os.Exit(0)
}

这样就实现了,当parent version 和version 冲突时,默认选择当前分支的版本号。
自定义合并策略是git提供的扩展机会,也可以运用到其它地方

dependency version

依赖版本冲突。
例如有一个common包,一个是 1.0.0,一个是1.0.1, 这些其实是不需要合并到公共分支的,哪怕合并到公共分支,也是需成 dev0.0.1-SNAPSHOT的,但是这样就很不方便了。
正确的做法是,使用一个独立的工程来维护版本号。
这个项目只有一个 pom.xml,然后里面用 dependencyManagement 维护好版本号。
然后通过 import的方式导入,一下是springboot的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<!-- 重要:版本号要和父模块中预定义的spring boot版本号保持一致 -->
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

dependency

这块如果冲突,说明是需要合并的,这块建议手动处理,没有使用自定义合并驱动了。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!