`
gaofen100
  • 浏览: 1184130 次
文章分类
社区版块
存档分类
最新评论

一个成功的Git分支模型

 
阅读更多
  • 能力所限,本文的翻译多处都很不地道,如果哪些地方难于理解,还烦请查看原文。—— Dbzhang800 20110921

在本文中,我向大家介绍的是在大约一年前我为自己的项目(包括工作和私人项目)引入的且已被证实非常成功的一个开发模型(development model)。这段时间我一直想写点关于它的东西,但在此之前,我却从未能抽出充足的时间来完成这件事。我不会谈论项目的任何细节,只涉及分支策略(branching strategy)和发布管理(release management)。


它的焦点是Git,我们所有源码的版本管理工具。

为什么是git?

要找一个对Git(与集中式的源码控制系统比较)的优点和缺点的完整且透彻的讨论,可参阅这个网页。那儿的火药味很浓。作为一名开发人员,我喜欢Git胜过喜欢现有的其他所有工具。Git真正改变了开发人员对合并和分支和认识。在经典的CVS/SVN的世界中,合并/分支一直被认为有点吓人("提防合并冲突,它会咬你!"),且很少被使用的东西(每隔一段时间才可能用一次)。

但对于Git来说,这些操作是非常低廉和简单的,而且它们被认为是日常工作​​流程中的核心操作之一,真的是如此。举例来说,在CVS/SVN的书籍中,分支和合并在后面章节(面向高级用户)才会被讨论,而在每一个git中,它在第3章(基础部分)已被介绍。

简单和高频率使用的结果就是,分支和合并不再是让人恐惧的事情。版本控制工具比其它工具更被寄希望于辅助分支/合并操作。

对工具的介绍够多了,让我们向开发模式进军吧。我在这儿要展示的模式,本质上不过是,为了使软件开发过程可管理,每一个团队成员要遵守的一套流程。

去中心却集中(Decentralized but centralized)

我们使用的且与本分支模型配合很好的软件仓库的配置,需要有一个中心“真”仓库。注意,它只是被考虑成中心仓库(因为Git是一个DVCS,在技术层面上并不存在一个像中心仓库这样的东西)。我们用origin指代这个仓库,因为所有的Git用户都熟悉这个名字。


每个开发者拉取(pull)并推送(push)到origin。但除了这种集中式的推送拉取关系,每个开发者也可能会从其他的开发者处拉取代码的变更,从而形成一个子团队。例如,当与两个或更多的开发者协同工作于一个大的新特性时,在将工作代码推送到持久的origin之前,这可能很有用。在上图中,Alice和Bob,Alice和David,以及Clair和David,分别构成了子团队。

从技术上讲,这只不过意味着Alice定义了一个名为bob的Git的remote,它指向了Bob的软件仓库。反之亦然。

主分支(main branches)

本质上,该开发模型是从现有的模型中获得灵感而产生的。中心仓库中维护着两个永生不灭的主分支:

  • master
  • develop


每一个Git用户应该都熟悉origin中的master分支。与master平行,还存在另一个被称为develop的分支。

我们将origin/master考虑成这样的一个主分支,其源码的HEAD始终代表了产品就绪的状态。

我们将origin/develop考虑成这样的一个主分支,其源码的HEAD总是代表已经并入了最新的开发变更的状态,这些变更将用于下次的发布。有人喜欢将其称之为“集成分支(integration branch)”。这也是自动的nightly build的源码来源。

当develop分支中的源码达到一个稳定点且准备发布时,所有的变更应当被合并回master分支,然后将打上版本号标签。至于具体是怎么实现的,接下来将进一步讨论。

因此,按(我们的)定义,每当变更被合并回master,都将导致一个新产品发布。我们倾向于采用非常严格的策略,因此理论上,我们可以使用一个Git的脚本钩子,当master上有一个提交(commit)时,可以自动构建并将软件转到产品服务器。

辅助分支(Supporting branches)

除了主分支master和develop,我们的开发模型使用了各种各样的辅助分支来——帮助团队成员间的并行开发,减轻特性的追踪的痛苦,准备产品的发布以及快速修复发布产品中的缺陷。不同于主分支,这些分支的生存周期总是有限的,因为它们最终会被移除。

我们可能用到的不同的分支类型:

  • 特性分支(Feature branches)
  • 发布分支(Release branches)
  • 快速修复分支(Hotfix branches)

这些分支中的每一个都有着特定的目的,也必须遵守严格的规则:哪些它们所起源于的分支,也是它们必须要合并到的目标分支。我们将很快将介绍一遍它们。

从技术角度看,这些分支绝没有任何“特殊性”。分支类型是依据我们的使用方式划分的。毫无疑问,它们仍是普通的Git分支。

特性分支(Feature branches)

可能起源于分支:develop

必须合并回分支:develop

分支命名惯例:除 master、develop、release-* 或 hotfix-* 外的任何名字


Feature分支(有时被称为topic分支),用于为即将到来的或远期的版本开发新功能。当开始开发一个新的功能时,此时包含该功能的目标可能尚未很好定义。Feature分支的本质是,在新功能开发期间始终存在;但是它最终要合并回develop分支(将添加到下次发布的版本中)或者废弃(在实验让人沮丧的情况下)。

典型情况下,feature分支只存在于开发者的仓库中,而不在origin中出现。

创建feature分支

当开始工作于一个新功能时,从develop分支创建新的分支:

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

将完成的feature并入develop

完成的特性将会合并回develop分支,并终会被加入将到来的release中:

$ 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

标记--no-ff使得,即使在可以fast-forward的条件下,合并操作也总是生成一个commit对象。这可以避免feature分支中历史信息的丢失,并可使feature分支中的所有commit保持仍在一块。比较:


在后一种情况下,从Git的历史中是不可能看出,哪些commit对象共同实现了一个特性——你必须手动阅读所有的日志信息。而要移除整个特性(即,一组commit),在后一种情况下真真切切地让人头痛;而若使用了--no-ff标记,则很容易做到。

当然,这会创建一些空的commit对象,但收益比这点浪费要大得多。

不幸的是,我尚未找到使得git merge中默认行为启用--no-ff的方法,但它真的很需要如此。

发布分支(Release branches)

可能起源于分支:develop

必须合并回分支:develop 和 master

分支命名惯例:release-*

Release分支用于做新产品发布前的准备。它使得在最后一刻可以进行细枝末节的完善。更进一步讲,它允许小bug的修复以及发布所需元数据(版本号,构建日期等)的准备。通过在release分支做这些工作,develop分支可以干净地接收为下一个大发行版准备的功能。

从develop创建release分支的关键时机是,develop分支(几乎)可以反映新版本就绪状态的时刻。至少是将要建构的发行版所有的feature分支都已经被合并到了develop分支的时刻。所有的针对未来版本开发的feature可能不会被并入——它们必须等到该release分支创建以后。

正是在release分支开始时,即将发布的产品才被分配一个版本号——而不是先前。直到这一刻,develop分支才开始反映“下一版”的变化,但是在下一个release分支开始前,下一版最终将变成0.3还是1.0都是不明确的。这种决定是在release分支开始时,通过项目的版本号规则实施的。

创建发布分支

Release分支是从develop分支中创建而来。例如,假定1.1.5是当前产品的版本,而我们即将有一个大的发行版。Develop分支对于“下一版”已经就绪,而且我们已经确定这将是版本1.2(而不是1.1.6或2.0)。因此,我们创建release分支并赋予它一个反映新版本号的名字:

$ 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 是一个虚构的脚本,被用来修改工作区中的一些文件以反映出新版本。(当然,这也可以是手动修改的)。而后,修改后的版本号被提交。

在发布最终完成之前,这个新的分支可能一直存在。在这期间,修复的bug会应用到该分支(而不是develop分支)。在这儿添加大的新特性是严格被禁止的。它们必须被合并到develop,并去等待下一个大的发行版本。

完成发布分支

当release分支的状态已经符合正式发行的要求时,一些动作需要被实施。首先,release分支被合并进master(回想一下,按定义,master上的每一个提交都导致一个新的版本)。其次,master上的该次提交必须被打上标签,以方便以后引用这个历史版本。最后,release分支中的所有变更都需要合并回develop,使得未来版本也包含这些bug补丁。

在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

release现在已经完成,且打上标签以备未来引用。

注: 你可能也想使用 -s 或 -u <key> 标记来私密签名。

要保持我们在release分支所做的变更,我们需要将其合并会develop。在Git中:

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

这步操作可能引起合并冲突(很可能,因为我们已经改变了版本号)。如果是这样,修复并提交。

现在,我们真的完成了,且该release分支将被移除,因为我们不再需要它了:

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

快速修复分支(Hotfix branches)

可能起源于分支:master

必须合并回分支:develop 和 master

分支命名惯例:hotfix-*


Hotfix分支非常像release分支,因为它也是为新产品(虽然是计划外的)发布做准备的。它们起源于对当前已发布产品中不理想状态的立即响应的需求。当产品中出现一个紧急bug需要被立即解决时,一个hotfix分支可以从master分支中标记该产品版本的标签开始创建。

其实质是,当一个人在准备产品的快速修复时,团队其他成员在develop分支的工作能够继续进行。

创建hotfix分支

Hotfix分支从master分支中创建。例如,假定1.2是当前已经发布的产品,却由于严重的bug导致了问题。但是develop分支中的变更尚不稳定。我们可能选择创建hotfix分支并开始修复这个问题:

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

创建分支以后不用忘记修改版本号!

然后,修复bug并将代码以一个或多个commit的形式提交。

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

结束hotfix分支

当完成时,对bug的修复需要合并进master分支,也需要合并回develop分支,以确保补丁代码也被包含在下一个版本中。这与release分支结束时的情况是完全类似的。

首先,更新master并对该次发布打上标签。

$ 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

注: 你可能也想使用 -s 或 -u <key> 标记来私密签名。

接着,使develop中也包含该修复代码:

$ 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分支,而不是develop。合并进release分支的补丁代码最终也会在release分支完成时被合并进develop。(如果develop中的工作急需这个补丁且等不及release最终完成,你也可以安全地将补丁合并进develop。)

最后,移除这个临时分支:

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

小结

虽然这个分支模型没有什么令人震惊的新东西,本文一开始给出的“大图片”在我们的项目中被证明了非常有用。它形成了一个优雅的很容易理解的模式,并强化团队成员对分支和发布过程的共享的理解。

这里提供一个该图片的高品质PDF版本。打印它然后挂在墙上以供随时快速参考。


分享到:
评论

相关推荐

    一个成功的git分支模型收集.pdf

    一个成功的git分支模型收集.pdf

    什么是成功的Git分支模型

    在这篇文章中,我提出一个开发模型。我已经将这个开发模型引入到我所有的项目里(无论 在工作还是私人)已经一年有余,并且它被证明是非常成功的。我打算写这些已经很久了, 但我一直找不到时间来做,现在终于有时间...

    成功的Git分支模型.pdf

    作为开发人员,我比今天其他所有工具都更喜欢Git。Git确实改变了开发人员对合并和分支 的看法。文章谈论了git分支策略和发布管理技巧,帮助开发人员有效管理各个开发版本。

    应用系统双模研发的GIT分支模型介绍.docx

    应用系统双模研发的GIT分支模型介绍.docx

    关于两种CI/CD策略以及git分支模型的思考

    CI/CD(持续集成/持续交付)策略以及git分支模型和以前的项目做一下分析比较,希望对各位有所帮助,也能有所思考,尤其是那些期望 搭建项目部署流水线或者想了解git分支模型的开发、运维人员。 废话不多说,由于近期...

    gitflow:复制 Vincent Driessen 的“一个成功的 Git 分支模型”

    降价 正文文本 -- mono text 预科 $ do `it` UL (http://site/) [site] OL 0 0

    git-glow:一个新的git扩展,可以在Github上自动使用git flow分支模型

    一个新的git扩展,可在Jira和Github中自动使用git流分支模型 要求 吉特 Python 3.7以上 在git开发和主要分支的存储库中。 有关更多信息。 去做 分解finish_release和finish_hotfix 切换project-key-id 切换...

    git-graph:可视化Git数据模型

    它可以帮助Git学习者通过可视化更好地理解Git数据模型。 您可以尝试各种git操作(例如,添加/删除/编辑文件或目录,合并分支等),提交,然后运行此工具以查看引擎盖下.git目录中的数据发生了什么。 要求:Python ...

    用于提供高级存储库操作的 Git 扩展集合 对于 Vincent Driessen 的分支模型

    Git 扩展的集合,用于为 Vincent Driessen 的分支模型提供高级存储库操作。

    Progit英文版2.1.6

    在*第一章*,我们将介绍...分支模型通常被认为是 Git 的杀手级特性。这里,你将学习到究竟是什么让 Git 与众不同。学习完本章,你可能需要一段时间来思考,在 Git 分支成为你的生活的一部分之前,你到底是如何生 活的。

    用于提供高级存储库操作的 Git 扩展的集合 文森特·德里森的分支模型

    用于提供高级存储库操作的 Git 扩展的集合 文森特·德里森的分支模型

    Git Community Book(中文版)

    获得一个Git仓库 正常的工作流程 分支与合并@基础 查看历史 -Git日志 比较提交 - Git Diff 分布式的工作流程 Git标签 4. 中级技能 忽略某些文件 rebase 交互式rebase 交互式添加 储藏 Git树名 追踪分支 使用Git ...

    nvie_com_posts_a_successful_git_branching_model

    git branch git 分支模型

    Git-2.20.1-64-bit.exe,zip

    5、Git的分支和合并:分支模型是Git最显著的特点,因为这改变了开发者的开发模式,SVN等版本控制工具将每个分支都要放在不同的目录中,Git可以在同一个目录中切换不同的分支。  6、分支即时性:创建和切换分支几乎...

    git_flow工具介绍

    git_flow工具介绍 Gitflow工作流是经典模型,体现了工作流的经验和精髓。...Gitflow工作流没有用超出功能分支工作流的概念和命令,而是为不同的分支分配一个很明确的角色,并定义分支之间如何和什么时候进行交互。

    [Android小组]Git开发模型--Gitflow工作流1

    1. 创建开发分支 2. 小红和小明开始开发新功能 3. 小红完成功能开发 4. 小红开始准备发布 5. 小红完成发布 6. 最终用户发现Bug

    git-sweep:一种命令行工具,可帮助您清理已合并到master的Git分支

    git-sweep 一个命令行工具,可帮助您清理已合并到master的Git分支。 Git的最佳功能之一是便宜的分支机构。 现有的分支模型(例如和Vincent Driessen的描述了使用此功能的方法。问题通常,您的所有分支都位于您的...

    Progit中文版2.1.4

    Pro git 中文版 ,...分支模型通常被认为是 Git 的杀手级特性。这里,你将学习到究竟是什么让 Git 与众不同。学习完本章,你可能需要一段时间来思考,在 Git 分支成为你的生活的一部分之前,你到底是如何生 活的。

    Git权威指南PDF完整版

    4.2 思考:为什么工作区根目录下有一个 .git 目录/ 60 4.3 思考:git config 命令的各参数有何区别/ 63 4.4 思考:是谁完成的提交/ 65 4.5 思考:随意设置提交者姓名,是否太不安全/ 67 4.6 思考:命令别名是干什么...

Global site tag (gtag.js) - Google Analytics