git

Git 工作流・一

Git Flow

Posted by mingfer on April 17, 2019

本文来自 a successful git branching modelwhy arent you using git flow

git flow 分支模型是一个基于 git 分支的项目管理和发布工作流,可以方便开发人员对项目中的功能,BUG 和发布版本进行跟踪。通过 git 的原生命令来使用这个工作流程需要记忆和输入非常多的命令,我们可以通过 gitflow 来实现一些命令的自动化,方便我们更容易的使用这个工作流程。

但是,必须要注意的是 git flow 的工作流模型仅适合于进行版本化发布的项目。

分支策略和发布管理

通常情况下我们使用下面这张图来说明 git flow 的整个过程:

git-flow

这里需要注意,下面所说的分支均在 origin 远程仓库上建立的。

项目主要分支

git flow 工作流的核心在于为项目划分出两个主要分支,并且这两个分支的生命周期是无限的:

  • master
  • develop

其中 master 是 origin 远程仓库的默认分支,在这里 master 里面源代码的 HEAD 总是指向生产就绪状态的代码。而 develop 分支指的是正在开发的代码,这些代码的可能在每一次提交的时候都会被自动构建。当 develop 的代码足够稳定的时候,master 可能会将稳定的 develop 代码进行合并,并使用一个新的版本号标注这部分合并。

所以,每次将 develop 的更改合并回 master 的时候,都意味着产生了一个新的生产版本。从理论上严格来说,我们每一次提交代码到 master 的时候,都应该自动构建出新的软件版本更新到我们的生产服务器上。

main-branches

项目支持分支

不可避免的是,在项目开发的过程中我们有需要对一些额外的功能进行实现,准备对应的生产版本或进行 BUG 的快速修复。为了应对这些场景,我们设计了一些寿命有限的分支,这些分支在解决问题之后就会被删除。

我们可能使用以下三种类型的分支:

  • feature 功能分支
  • release 准备进行生产发布的分支
  • hotfix 进行紧急 BUG 修复的分支

对于这些分支,我们需要严格的界定哪些分支可以作为他们的起始分支和它们可以进行合并的分支。

feature 分支

feature 分支可能是从 develop 分支出来的,并且必须合并到 develop 分支。feature 分支可能命名为除了 masterdeveloprelease-*hotfix-* 的任意名称。

feature 分支一般从 develop 分支分离,用于开发即将发布或将来版本发布的新功能,而在开发这些功能的时候是否会将该功能合并到目标版本中是不确定的。但是只要是在开发阶段,那么就一定会存在 feature 分支,这些分支最终会因为需要将功能添加到新版本而合并到 develop 分支,或者因为需求变更不需要此功能而放弃。一般来说,feature 分支只存在于开发人员的本地仓库中,而不是远程 origin 仓库中。

创建 feature 分支

在开始开发一个新功能的时候,需要在 develop 建立一个关于该功能的分支:

1
$ git checkout -b myfeature develop

向 develop 合并 feature 分支

开发完成的功能需要合并到 develop 分支,以确保在之后的新版本中包含此功能:

1
2
3
4
5
6
7
8
$ git checkout develop
切换分支到 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
删除分支 myfeature (was 05e9557).
$ git push origin develop

通常情况下我们使用 --no-ff 参数来进行分支的合并,这样可以避免丢失关于功能分支的一些历史信息,避免将所有的功能提交组合到了一起。

对比上面两种合并方式,右边快速合并的方式明显看不出哪一些提交对象是在一起实现一个功能的,除非你手动的去读取所有的日志信息。当我们遇到要 check 出整个功能的情况的时候,右边的提交方式可能会让我们遇到巨大的麻烦。而使用 --no-ff 的方式却可以一目了然的进行区分,当然 --no-ff 的方式可能给我们带来一些空的提交对象(分支合并),但是这点代价无疑是值得的。

release 分支

release 分支主要是从 develop 迁移出来的,开发完毕之后必须合并回 develop 分支和 master 分支,建议的命名方式是 release-*

release 分支用于支持准备新的生产版本,它允许开发人员进行一些小错误的修复和添加一些发布的元数据,如版本号,构建日期等。之所以在 release 分支上执行这些操作,是因为 develop 分支需要继续进行下一个大版本的功能开发。

release 分支总是反映的在 develop 分支中所期望的新版的所有状态,所有对于新版本的功能分支都应该在构建新的 release 版本之前合并到 develop 分支中。而对于将来版本的功能分支应该在这之后进行合并。

在创建 release 分支的那一刻,我们需要为该分支分配一个新的版本号。对于 develop 分支来说,它反映了下一个版本的变化,但是不会知道下一个版本是版本 0.3 还是版本 1.0。版本号的确定是在迁移 release 分支的时候决定的,并且由具体项目本身的版本号定义规则产生。

创建一个 release 分支

release 分支是从 develop 分支创建的。这里假设我们要在 1.1.5 的版本上发布一个 1.2 的版本,因此我们为我们新的 release 分支提供一个反映版本号的名称 release-1.2

1
2
3
4
5
6
7
$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)

这里的 bump-version.sh 是一个虚构的 shell 脚本,用于更改整个项目的版本号。当我们迁移出了 release 分支后,该分支下的版本号也应该一并提升。在 release 分支存在的期间,关于该版本的 BUG 修复都应该在该分支上,而不是 develop 分支上。同时严禁在该分支上添加大功能 ,这些功能应该添加到 develop 分支上,等待下一个版本发布。

完成 release 分支

当 release 分支开始真正的进行发布的时候。首先要将该分支合并到 master 分支,并在 master 上标记本次提交,以便将来进行历史版本的参考。

1
2
3
4
5
6
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2

最后需要将 release 分支上的更改合并回 develop 分支,以便在将来的版本中也包含这些修改。在这一步,由于我们更改版本号等原因,大概率会出现冲突,所以也要一并修复在合并它们:

1
2
3
4
5
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)

前面两步完成之后,我们就可以删除这个分支了:

1
2
$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

hotfix 分支

hotfix 分支迁移至 master 分支,开发完成后合并到 develop 和 master 分支,推荐的命名方式为 hotfix-*。hotfix 分支类似于 release 分支,也是为了准备一个生产版本,当然这是一个计划外的版本,它通常来自于当前的生产版本出现了必须立即解决的错误。hotfix 版本通常从生产版本的一个具体的版本 tag 处迁移出来。在这种情况下,处于 develop 分支的开发团队可以继续工作,只需要有人进行 hotfix 分支的修复工作即可。

创建 hotfix 分支

hotfix 分支是从 master 分支创建的。这里假设生产上的 1.2 版本发生了严重的错误,但是 develop 分支是不稳定的,无法从 develop 分支去解决这个错误,所以我们创建一个名为 hotfix-1.2.1 的分支来解决这个问题:

1
2
3
4
5
6
7
$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

然后修复对应的错误,并在一个或多个的提交中提交修复的结果:

1
2
3
$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

完成 hotfix 分支

完成 hotfix 分支的流程和 release 分支的流程是一致的。

首先合并到 master,并使用一个 tag 标记这次合并:

1
2
3
4
5
6
$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1

然后合并代码到 develop 分支:

1
2
3
4
5
$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

这里有一个例外是,如果此时 release 分支还存在的话,应该将 hotfix 合并到 release 分支,然后 release 分支完成的时候会一并合并回 develop 分支。

最后,删除这个分支:

1
2
$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).