成功的GIT开发分支模型和策略
详细图文并茂以及git flow工具解释参考: http://danielkummer.github.io/git-flow-cheatsheet/index.zh_CN.html
原文地址:http://nvie.com/posts/a-successful-git-branching-model/
本文中我将介绍我在多年的项目开发中使用的开发模型,这个模型被实践检验为正确有效的模式。本文中,我将不会涉及到项目的任何细节,只讨论关于分支策略和release管理
为什么要使用Git?
网上有很多关于Git和集中式代码管理控制系统的比较,作为一个开发人员,我更喜欢Git,Git实际上颠覆了开发人员关于merge/branch的想法。从经典的CVS/SVN世界过来,merging/branching总是被认为是一个痛苦和令人胆寒的事情。但是在Git中,这些行为是非常简单和代价低廉的。比如在CVS/SVN的书籍中,branching和merging往往都在最后几章中讨论(专为高级用户所写),而在每一本Git的书籍中,这些merge/branch总在前三章中介绍(因为它们很基础)。
正是源于这些操作在Git中简单,重复的特性,branching/merging不再是一个令人生畏的事情。
Decenteralized but cdentralized:
库作为中央“truth”repo.在Git中,虽然这个repo可能被认为是中央的repo,但是你要明白GIT是一个分布式的版本控制系统,也就是说从技术角度来说没有这个central repo!我们更确切地将这个central repo称为origin
每个开发人员pull/push to origin.但是除了中央式的pull/push关系,每一个开发人员之间也可能从他们的子team之间pull changes。例如,对于两个或更多的开发人员同时工作在一个大的feature上,这种模型就更加有用了(特别是正在工作中的feature代码永久性push到origin前)。上面的图中,在alice和bob,alice和david,clair和david之间形成了subteam。
技术上说,实际上就是Alice定义了一个Git remote, 命名为bob,指向bob的repo,同样反之亦然。
The main branches
核心上说,开发模型被已有的模型所启发,中央repo将永久保留两个主要的branches:
- master
- develop
origin上的master分支对于每一个GIT用户来说是非常熟悉的.和master branch平行的,有一个另外叫做develop的分支。
我们考虑orgin/master作为main branch,这个分支上的HEAD总是反映了一个production-ready的状态;
我们将origin/develop视作一个HEAD永远指示为了下一个release而最近递交的开发变化。有些人比较喜欢将此称为integration branch.在这个分支上,每日的build将从这里构建。
当在develop分支上的代码达到一个稳定状态点,达到可以发布的状态时,所有的变更都应该merge back到master,并且在master上tag一个release number。
这样每次当变更merge back到master时,这就是一个新的production release by definition.我们更倾向于对此非常严格,因此理论上,我们可以使用一个Git hook sciprt实现每当有一个向master的commit,我们自动构建和部署master上的软件到生产环境上去(因为master总是一个Production ready的状态)
Supporting branches:
除了main branches: master和develop,我们的开发模型中也将使用一系列的supporting branches以便支持在team member之间实现并行开发,更加容易地实现feature的tracking,更方便地准备产品release,更好的辅助解决产品的问题。不像main branch(master,develop),这些branch总是有一个生命周期,因为他们最终将被删除。
我们建议以下几种类型的分支:
- feature branches;
- release branches
- hotfix branches
每种非main branch都有一个特定的目的,并且有严格的定义规则:这个分支应该从那个分支来创建,以及该分支最终的merge targets都有明确的定义。下面我们将明确说明。
从技术角度来说,这些branch本身并无特别之处。这些branch的类型是由"how we use them"来分类的。他们就是简单的普通的Git branch.
Feature branches:
May branch off from: develop;
Must merge back into: develop;
Branch naming convention: 任何除了master,develop,release-*,hotfix-*之外都可以
Feature branch(或者被叫做topic branch)被用来为了即将到来的(upcoming)或者长远计划的release而做开发之用的。当开始开发一个feature时,这个feature的target release(目标release)在那时可能根本还确定。feature branch的本质是:只要feature仍在开发中,它将一直存续,但是最终它将被merged back到dvelop分支(to definitely add the new feature to the upcoming release)或者被discarded(in case of a disappointing experiment)。
Feature branche典型地只存在于开发人员的repo中,并不在origin repo中存在。
当开始一个新的feature开发时,feature branch从develop branch中创建出来
$ git checkout -b myfeature develop Switched to a new branch "myfeature"
结束的feature将被merged into the develop branch以便在即将到来的release中添加这个feature.
$ 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 flag使得这个merge动作即便是这个merge属于fast-forward(no conflict)也总会产生一个新的commit object。这样就避免了关于曾近为了开发一个新的feature而做了feature branch并递交了多个文件的历史。
上图中,后一个fast-forward merge的场景,你将无法得知哪些commit objects最终形成了那个feature---你可能必须手动的查阅所有的log信息才能得知。同样在后一个场景中,你想rollback一个feature也是一个非常头疼的问题,然而如果我们增加了--no--ff flag则很容易做到这些。
当然,使用--no-ff flag确实会产生一些可能是空的commit objects,但是这个坏处相比其带来的好处是可以忍受的。
Release branches:
May branch off from: develop
Must merge back into: develop and master
Branch naming convention: release-*
注意release branch一般命名只包含major+minor,而不包含patch。比如release-1.0,release-2.3,对于release-1.5.1这样的tag只存在于hotfix生成的版本中
Release branches支持准备一个新的产品release而存在。They allow for last-minute dotting of i’s and crossing t’s.而且,他们允许晓得bug fix并且为那个release准备一些meta-data(比如version number, build dates,etc)。这些工作都在一个release branch上做,那么develop分支总是很清晰地准备为下一个大的release接受新的feature.
开始从develop branch off一个新的release branch的时机往往是在当develop基本反映了新的release的可接受状态时。至少所有的必须在这个release中deliver的feature必须被merge到了develop分支,这个时刻就是创建release branch的时机。所有为将来release计划的feature都不能merge下来直到上一个release branch已经被创建。
通常直到将要到来的release被赋予一个版本号,这将是这个release branch的一个起点。直到那个时刻,develop分支反映了下一个release的变化,但是关于是否那个下一个release会最终变成0.3或者1.0并不是很清晰,直到release branch开始工作。这个决定在release branch 开始时决定下来,由项目的版本号命名规则来决定
Release branch由develop branch来创建。比如,版本v1.1.5是当前产品release,而我们将有一个大的release将被release。develop分支的状态已经为下一个release做好了准备,而我们决定这个release将命名为v1.2(而不是v1.1.6或者v2.0)。那么我们可以branch off并且给这个release branch一个1.2的名称:
$ 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(-)
在创建一个新的release branch并且切换到该branch后,我们bump the version number。这里bump-verison.sh是一个虚构的shell脚本,该脚本更改一些文件以便反映新的版本号变更。(这当然也可以是一个手工的变更)然后,这个bumped version number is commited.
这个新的branch可能会存续一段时间,直到这个release被正式rollout.在这个时间段内,bugfix可能会被应用到这个release branch上去(而不是应用到develop branch上去)。在这里增加大的新功能将被严格禁止。这些bugfix必须被merge到develop分支上去,这样又开始等待下一个大的release.
当releasea branch的状态真正达到实际能够release质量时,也需要做一些工作。首先,release branch需要merge到master(可能包含一些bug fix)(since every commit on master is a new release by definition, remember!)。接着,那个对master的commit必须被打上tag,以便该历史版本将来能够容易地被引用。最后,在release branch上做的bugfix变更需要被merge到develop上去,以便将来的release中包含这些bugfix。
前两步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现在已经结束了,并且被打了标签1.2以便将来引用
为了将release branch上做的bugfix保持好,我们需要将这些fix merge到develop分支上:
$ git checkout develop Switched to branch 'develop' $ git merge --no-ff release-1.2 Merge made by recursive. (Summary of changes)
在这一步,我们可能会碰到merge conflict(probably even, since we have changed the version number),如果发现conflict,则解决它并且commit掉
现在我们确实完成了这个release的工作,release branch可以被删除了:
$ 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-*
hotfix branch和release branch非常相似,因为他们本身也是用于为准备一个新的产品release而存在的(尽管是非计划的,往往由某个release现场发现紧急问题而触发的)。他们往往是由一个现场运行的产品release上报的问题来触发产生的。当一个critical bug必须马上解决时,一个hotfix branch将从master branch中的相应tag版本处branch off出来。这样做的本质是:团队其他成员可以在develop branch上继续工作,而指定团队一个人在这个hotfix branch上准备一个快速产品fix版本。
hotfix branch由master branch来创建。比如,假设v1.2是当前报严重问题的产品release版本,但是由于在develp branch上的变更还不稳定,所以我们开始从master上拉一个hotfix branch开始工作:
$ 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(-)
不要忘记bump the version number after branching off~!
然后,fix the bug,commit the fix in one or more seperate commits:
$ 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:
当完成该问题的fix时,bugfix需要merged back到master中去,同时也需要merge到develop branch中去,只有这样才能保证bugfix本身将会在下一个release中自动包含。这和release branch结束的动作是一样的。
首先,更新master并且tag the release:
$ 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
下一步,也需要同时包含bugfix到develop分支中去:
$ git checkout develop Switched to branch 'develop' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive. (Summary of changes)
在这里有一个例外的rule:当一个release branch当前存续,那么这个hotfix change需要merge到那个release branch中去,而不是develop分支。Back-merging the bugfix into the release branch will eventually result in the bugfix being merged into the 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 the develop now already as well.)
最后,删除这个临时的branch
$ git branch -d hotfix-1.2.1 Deleted branch hotfix-1.2.1 (was abbe5d6).
原创:在github上大型项目团队branch策略:
Summary:
虽然本篇文章本身对于分支策略并无令人震惊之处,但是那个big picture图片确实对我们的项目成功有非常重要的意义。它构建了一个优雅地心智模型,而该模型易于理解并且允许团队充分理解整个产品的branching/releasing流程。
两个team member有两个repo,互为remote,当git push时,有可能出现conflict,这时需要做的事情是必须先git pull操作,解决冲突以后,再做git push。这和在一个repo时merge冲突是类似的。
https://github.com/nvie/gitflow 是一个对git扩充的flow惯例,非常实用