一个靠谱的GIT分支模型(GIT Flow)

作者:Vincent Driessen

原文连接: http://nvie.com/posts/a-successful-git-branching-model/

译者:罗春晖

译文连接:http://www.luochunhui.com/blog/a-successful-git-branching-model/

译者注:这是一套靠谱的git分支指导。充分的适配于团队的日常开发、新功能开发、紧急bug修复,通过该模型可建立一套行之有效的上线发布流程。本模型后来作为 GIT Flow 的标准被推行。

 

In this post I present the development model that I’ve introduced for some of my projects (both at work and private) about a year ago, and which has turned out to be very successful. I’ve been meaning to write about it for a while now, but I’ve never really found the time to do so thoroughly, until now. I won’t talk about any of the projects’ details, merely about the branching strategy and release management.

本文我将介绍近一年内,在我项目中(包括公司项目和个人项目)应用的开发模型。实践证明,这个模型发挥了非常良好的效果,相当靠谱。很早之前我就想写点东西,但直到今天才抽出时间。本文不涉及任何的项目细节,仅仅讨论分支策略和发布管理方法。

It focuses around Git as the tool for the versioning of all of our source code.

本文版本管理工具使用GIT。

Why git?

为什么使用GIT

For a thorough discussion on the pros and cons of Git compared to centralized source code control systems, see the web. There are plenty of flame wars going on there. As a developer, I prefer Git above all other tools around today. Git really changed the way developers think of merging and branching. From the classic CVS/Subversion world I came from, merging/branching has always been considered a bit scary (“beware of merge conflicts, they bite you!”) and something you only do every once in a while.

经过一些讨论和对比,我们最终选用GIT作为我们的版本管理工具,详情参考文章: GitSvnComparison 。GIT有非常多的亮点,作为一个开发人员,GIT是我最喜欢的版本管理工具,没有之一。 GIT改变了开发人员对代码分支和合并的思维方式。在经典的CVS/SVN的世界中,分支和合并被认为是危险的(小心合并冲突,他真的会咬你!),我们经常不得不考虑在很长一段时间,绝对必要时才去进行一次合并操作。

But with Git, these actions are extremely cheap and simple, and they are considered one of the core parts of your daily workflow, really. For example, in CVS/Subversion books, branching and merging is first discussed in the later chapters (for advanced users), while in every Git book, it’s already covered in chapter 3 (basics).

但在GIT中,分支和合并工作是很方便和简单的,他们可以作为日常工作的一部分。比方来说,在CVS/SVN的书籍中,分支合并的讨论一般会作为高级教程,放在全书的后面章节。但在GIT的书籍中,分支介绍则作为基础章节中进行提前介绍。

As a consequence of its simplicity and repetitive nature, branching and merging are no longer something to be afraid of. Version control tools are supposed to assist in branching/merging more than anything else.

因为GIT作为新的版本管理工具,给我们提供了相当完善的分支和合并支持,妈妈再也不用担心我们在分支和合并时难受了。

Enough about the tools, let’s head onto the development model. The model that I’m going to present here is essentially no more than a set of procedures that every team member has to follow in order to come to a managed software development process.

介绍完工具,接下来我们将定义一个开发模型。通过这个模型,我们约定所有的开发人员的操作规范,建立对应的处理流程,使得软件的开发过程更易于管理。

Decentralized but centralized

去中心化和中心化

The repository setup that we use and that works well with this branching model, is that with a central “truth” repo. Note that this repo is only considered to be the central one (since Git is a DVCS, there is no such thing as a central repo at a technical level). We will refer to this repo as origin, since this name is familiar to all Git users.

在这个模型中,我们需要一个唯一的中心仓库(从技术角度来看,GIT并没有中心仓库概念)。我们将其命名为origin,这个名字GIT用户都比较熟悉。

 

Each developer pulls and pushes to origin. But besides the centralized push-pull relationships, each developer may also pull changes from other peers to form sub teams. For example, this might be useful to work together with two or more developers on a big new feature, before pushing the work in progress to origin prematurely. In the figure above, there are subteams of Alice and Bob, Alice and David, and Clair and David.

每一个开发人员都从origin中拉取和推送代码。在中心仓库之外,模型也允许开发人员相互之间进行代码的拉取和推送,形成一个小项目组,允许两个或两个以上的人员共同开发一个新功能。在新功能完成之前,这个小项目组不必将代码提交给origin。在上图中,Alice和Bob,Alice和David,Clair和David分别组成了三个小项目组。

Technically, this means nothing more than that Alice has defined a Git remote, named bob, pointing to Bob’s repository, and vice versa.

技术方面,Alice需要定一个GIT远程仓库,命名为Bob,并连接至Bob的本地代码仓库。其他的子小组成员也是一样。

The main branches

主要分支

At the core, the development model is greatly inspired by existing models out there. The central repo holds two main branches with an infinite lifetime:

  • master
  • develop

核心模型与传统的GIT简单开发模型相似。在中心仓库中,包含了两个主要的分支,这些分支在项目生命周期中一直存在:

  • master 主分支
  • develop 开发分支

The master branch at origin should be familiar to every Git user. Parallel to the master branch, another branch exists called develop.

master主分支是GIT用户的常用名称。与master主分支同时存在的分支,称之为develop开发分支。

We consider origin/master to be the main branch where the source code of HEAD always reflects a production-ready state.

origin/master作为主要分支之一,是因为这个分支上的最新代码总是代表着生产环境的发布版本。

We consider origin/develop to be the main branch where the source code of HEAD always reflects a state with the latest delivered development changes for the next release. Some would call this the “integration branch”. This is where any automatic nightly builds are built from.

origin/develop作为另一个主要分支,是因为这个分支上的最新代码代表着开发人员为下一个发布版本提交的最新代码。有时我们也将其称之为『集成分支』。它一般可用于自动集成工具发布『每日构建』。

When the source code in the develop branch reaches a stable point and is ready to be released, all of the changes should be merged back into master somehow and then tagged with a release number. How this is done in detail will be discussed further on.

当源代码在develop分支中被开发完成,并准备发布时,所有的提交变更都应该被合并至master分支,并使用tag工具标记一个版本号。详细的操作方法我们稍后会继续讨论。

Therefore, each time when changes are merged back into master, this is a new production release by definition. We tend to be very strict at this, so that theoretically, we could use a Git hook script to automatically build and roll-out our software to our production servers everytime there was a commit on master.

也就是说,在master上出现新的代码提交和合并时,就代表一个新的生产发布需求产生了。在实际中工作中,我们可以建立一个git钩子脚本,自动检测master分支的变更。当master上有新的提交时,我们就自动编译代码并发布至生产环境。

Supporting branches

辅助分支

Next to the main branches master and develop, our development model uses a variety of supporting branches to aid parallel development between team members, ease tracking of features, prepare for production releases and to assist in quickly fixing live production problems. Unlike the main branches, these branches always have a limited life time, since they will be removed eventually.

讨论完主分支和开发分支后,接下来我们介绍一些辅助分支。他们将用于支持团队成员的并行开发,包括新功能开发、生产发布准备,生产环境的问题修复等。和主要分支不同,辅助分支一般只在需要时被建立,生命周期结束后即被释放删除。

The different types of branches we may use are:

  • Feature branches
  • Release branches
  • Hotfix branches

我们使用的辅助分支有如下几类:

  • 新功能分支
  • 发布分支
  • BUG修复分支

Each of these branches have a specific purpose and are bound to strict rules as to which branches may be their originating branch and which branches must be their merge targets. We will walk through them in a minute.

每一种分支都有特殊的用途,对分支的创建和合并操作进行了严格定义。它们只能从指定的来源分支建立,然后必须合并至另一指定的目标分支。我们很快会讨论细节。

By no means are these branches “special” from a technical perspective. The branch types are categorized by how we use them. They are of course plain old Git branches.

技术上将,这些分支和普通的GIT分支没什么两样。我们只是按照他们的用途进行分类。

Feature branches

新功能分支(别名:特性分支)

May branch off from:
develop
Must merge back into:
develop
Branch naming convention:
anything except master, develop, release-*, or hotfix-*
分支来源:
develop
分支合并目标:
develop
分支命名规范:
任何名字,但不要使用 master, develop, release-* 或 hotfix-*

Feature branches (or sometimes called topic branches) are used to develop new features for the upcoming or a distant future release. When starting development of a feature, the target release in which this feature will be incorporated may well be unknown at that point. The essence of a feature branch is that it exists as long as the feature is in development, but will eventually be merged back into develop (to definitely add the new feature to the upcoming release) or discarded (in case of a disappointing experiment).

新功能分支(有时也称之为特性分支)被用于即将开发的或更长期的功能开发。新功能分支建立时,我们可能还无法准确预知它的能完成时间。新功能分支在该功能开发过程中存在,当开发完毕后,被合并至develop分支,完成其生命周期。

Feature branches typically exist in developer repos only, not in origin.

新功能分支一般在开发人员的仓库中存在,不需要在origin中被创建。

Creating a feature branch

创建一个新功能分支

When starting work on a new feature, branch off from the develop branch.

从develop分支中创建新功能分支:

$ git checkout -b myfeature develop
Switched to a new branch "myfeature"

Incorporating a finished feature on develop

将新功能分支合并至develop分支

Finished features may be merged into the develop branch to definitely add them to the upcoming release:

当新功能完成时,我们将其合并至develop分支,准备下一次发布。

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop

The --no-ff flag causes the merge to always create a new commit object, even if the merge could be performed with a fast-forward. This avoids losing information about the historical existence of a feature branch and groups together all commits that together added the feature. Compare:

--no-ff 标记强制创建一个新的提交,禁止合并操作使用『快速前进(fast-forward)』方式进行。这样可以避免在合并时丢失了新功能分支脚力和消亡过程。标记功能的比较图示如下:

In the latter case, it is impossible to see from the Git history which of the commit objects together have implemented a feature—you would have to manually read all the log messages. Reverting a whole feature (i.e. a group of commits), is a true headache in the latter situation, whereas it is easily done if the--no-ff flag was used.

在右侧图中,新功能分支中的所有提交,都被嵌入到了develop分支中,除了认真阅读提交的日志信息,没法把他们分离出来。当我们试图回顾这个新功能分支中做了哪些改动时,将是个很麻烦的事情。但我们加上--no-ff标记,则可以很容易的解决这种麻烦。

Yes, it will create a few more (empty) commit objects, but the gain is much bigger than the cost.

当然,这会导致新创建了一个空的提交。但它带来的好处,远大于这一点点消耗。

Release branches

发布分支

May branch off from:
develop
Must merge back into:
develop and master
Branch naming convention:
release-*
分支来源:
develop
分支合并目标:
develop 和 master
分支命名规范:
release-*

Release branches support preparation of a new production release. They allow for last-minute dotting of i’s and crossing t’s. Furthermore, they allow for minor bug fixes and preparing meta-data for a release (version number, build dates, etc.). By doing all of this work on a release branch, the develop branch is cleared to receive features for the next big release.

发布分支用于新版本发布前的准备工作。它允许我们在发布前,做最后一点点改动,包括少量BUG的修改、元数据(如版本信息、编译参数等)的修改等。当所有工作完成后,develop分支再将这些修改全部合并回来,开始下一个版本的开发工作。

The key moment to branch off a new release branch from develop is when develop (almost) reflects the desired state of the new release. At least all features that are targeted for the release-to-be-built must be merged in to develop at this point in time. All features targeted at future releases may not—they must wait until after the release branch is branched off.

发布分支仅在我们决定发布新版本时进行创建,从develop分支中拉取并标记新版本的发布状态信息。此时所有准备上线的功能代码,都必须先合并至develop分支。针对未来的功能改动不得提交至此分支,它们必须等待下一个发布计划进行提交。

It is exactly at the start of a release branch that the upcoming release gets assigned a version number—not any earlier. Up until that moment, the develop branch reflected changes for the “next release”, but it is unclear whether that “next release” will eventually become 0.3 or 1.0, until the release branch is started. That decision is made on the start of the release branch and is carried out by the project’s rules on version number bumping.

发布分支决定了新版本及新版本号的开始。在此之前,develop分支并不决定下一个版本的版本号,不清楚下一个版本应该是『0.3』还是『1.0』,直到下一个发布分支建立时,才能知道。版本号命名交由将由项目版本管理计划决定。

Creating a release branch

创建一个发布分支

Release branches are created from the develop branch. For example, say version 1.1.5 is the current production release and we have a big release coming up. The state of develop is ready for the “next release” and we have decided that this will become version 1.2 (rather than 1.1.6 or 2.0). So we branch off and give the release branch a name reflecting the new version number:

发布分支从develop分支创建。举例来说,假设我们现在已经发布的版本为1.1.5 。开发人员已经基本完成了代码开发工作。经过评估,我们认为这次改动较大,决定将其作为版本1.2(不是1.1.6,也不是2.0)进行发布,然后创建这个新的发布分支:

$ 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(-)

After creating a new branch and switching to it, we bump the version number. Here, bump-version.sh is a fictional shell script that changes some files in the working copy to reflect the new version. (This can of course be a manual change—the point being that some files change.) Then, the bumped version number is committed.

创建新的发布分支并切换后,我们声明了版本号。在bump-version.sh脚本中,我们修改了一些工作文件,以便新版本生效(也可以手工修改)。然后提交掉这些代码。

This new branch may exist there for a while, until the release may be rolled out definitely. During that time, bug fixes may be applied in this branch (rather than on the develop branch). Adding large new features here is strictly prohibited. They must be merged into develop, and therefore, wait for the next big release.

这个分支将存在一段时间,直到这次发布完成。在此期间,可能发生一些bug修复(这些修改不在develop分支进行)。 不允许提交大功能的改进代码,它们必须被合并提交到develop,然后等待下一个发布工作。

Finishing a release branch

完成发布分支

When the state of the release branch is ready to become a real release, some actions need to be carried out. First, the release branch is merged into master (since every commit on master is a new release by definition, remember). Next, that commit on master must be tagged for easy future reference to this historical version. Finally, the changes made on the release branch need to be merged back into develop, so that future releases also contain these bug fixes.

当一个发布分支已经准备好分布时,需要可以进行如下操作。首先,该发布分支需要被合并至master分支(注意,所有master分支的提交都代表一个新的发布工作);然后,对master增加一个tag,以便未来对历史进行跟踪;最后,所有发布分支上的提交都需要合并回develop分支,确保下一个发布工作中包括了所有的bug修复。

The first two steps in Git:

前两项工作的GIT操作如下:

$ 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

The release is now done, and tagged for future reference.

通过这些操作,一次发布工作已经完成,并标记了tag,以供未来参考之用。

Edit: You might as well want to use the -s or -u <key> flags to sign your tag cryptographically.

提醒:在tag操作中,你可能需要增加-s-u <key> 参数,用来对tag代表的代码进行签名,防止纂改。

To keep the changes made in the release branch, we need to merge those back into develop, though. In Git:

为确保下一个发布工作中包括了本次发布分支中所有的变更,我们还需要在develop中进行合并工作,git命令行如下:

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

This step may well lead to a merge conflict (probably even, since we have changed the version number). If so, fix it and commit.

本操作可能会导致一些冲突(可能仅仅是版本号冲突)。如果发生了,就修复并且再次提交。

Now we are really done and the release branch may be removed, since we don’t need it anymore:

现在我们已经完成了发布分支的全部工作,我们可以删除它了:

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

Hotfix branches

紧急修复分支

May branch off from:
master
Must merge back into:
develop and master
Branch naming convention:
hotfix-*
分支来源:
master
分支合并目标:
develop 和 master
分支命名规范:
hotfix-*

Hotfix branches are very much like release branches in that they are also meant to prepare for a new production release, albeit unplanned. They arise from the necessity to act immediately upon an undesired state of a live production version. When a critical bug in a production version must be resolved immediately, a hotfix branch may be branched off from the corresponding tag on the master branch that marks the production version.

紧急修复分支和发布分支相似,但它并不是计划中的工作。它应用于生产环境中出现的紧急bug修复。紧急修复分支基于线上运行的tag号签出,并以此为基础进行修改。

The essence is that work of team members (on the develop branch) can continue, while another person is preparing a quick production fix.

这样,此处修复可以不影响当前的开发工作(在develop分支中),专门抽取一个人力来快速的修复生产环境的问题。

Creating the hotfix branch

创建一个紧急修复分支

Hotfix branches are created from the master branch. For example, say version 1.2 is the current production release running live and causing troubles due to a severe bug. But changes on develop are yet unstable. We may then branch off a hotfix branch and start fixing the problem:

紧急修复分支基于master创建。比方来说,现在生产环境运行的是1.2版本,不幸的是,它发生了一个严重的bug。与此同时,我们的开发工作正在进行中,还没有准备下一次发布。我们需要创建一个紧急修复分支,来解决现在生产服务器上发生的问题:

$ 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(-)

Don’t forget to bump the version number after branching off!

建立分支时,别忘了声明新的版本号。

Then, fix the bug and commit the fix in one or more separate commits.

然后,并通过一两次代码提交工作,来完成bug的修复工作。

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

Finishing a hotfix branch

完成紧急修复分支

When finished, the bugfix needs to be merged back into master, but also needs to be merged back into develop, in order to safeguard that the bugfix is included in the next release as well. This is completely similar to how release branches are finished.

当修复工作完成后,代码重新合并至master进行发布。同时将其合并至develop分支,已确保在下一次发布工作中正常的包含了本次修复。合并工作于发布分支操作类似。

First, update master and tag the release.

首先切换到master分支,合并,然后标记tag。

$ 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

Edit: You might as well want to use the -s or -u <key> flags to sign your tag cryptographically.

提醒:在tag操作中,你可能需要增加-s-u <key> 参数,用来对tag代表的代码进行签名,防止纂改。

Next, include the bugfix in develop, too:

然后,在develop中合并本次分支:

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

The one exception to the rule here is that, when a release branch currently exists, the hotfix changes need to be merged into that release branch, instead of develop. Back-merging the bugfix into the release branch will eventually result in the bugfix being merged into develop too, when the release branch is finished. (If work in develop immediately requires this bugfix and cannot wait for the release branch to be finished, you may safely merge the bugfix into develop now already as well.)

这里需要做一个特别说明,如果此时已经创建了一个发布分支,正准备下一次上线工作,则紧急修复分支应该被合并至该发布分支,而不是develop分支。紧急修复的代码将在发布分支完成时,经由发布分支合并至develop分支中(如果develop分支也需要立即使用这个紧急修复的代码,则也可以将紧急修复分支同时合并在develop中)。

Finally, remove the temporary branch:

最后,移除这个临时的修复分支。

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

Summary

总结

While there is nothing really shocking new to this branching model, the “big picture” figure that this post began with has turned out to be tremendously useful in our projects. It forms an elegant mental model that is easy to comprehend and allows team members to develop a shared understanding of the branching and releasing processes.

这个分支模型没什么可惧怕的,在文章最开始的概览图中已经包含了全部有用的信息。相信你能够很容易的理解,并指导团队建立一个可协作的、易于使用和理解的分支和发布流程。

A high-quality PDF version of the figure is provided here. Go ahead and hang it on the wall for quick reference at any time.

PDF的阅读版本附在文后,便于你可以翻阅查看。

下载PDF版本(英文版)