前言
由于新的项目在建立,我们逐渐把SVN的用法切换到GIT上。
GIT的工作区
前期我们先了解GIT的工作区域:
1、本地库
2、暂存区
3、未暂存区
4、原区
Git的区域分为 工作区、暂存区、本地仓库区,主要的一命令工作区间为以下几点:
远程仓库(Remote Repository)
远程仓库就是服务器所在的地址,一般最终提交于服务器端。
工作区(Work Directory)
一般就是我们项目的根目录,在电脑目录上。
本地仓库(Repository)
我们在为项目添加本地库之后,会在工作区生成一个如下图所示的隐藏目录“.git”,.git目录即为当前工作区的本地版本库。
暂存区(Stage)
版本库中设立一个暂存区(Stage or Index),作为用来直接跟工作区的文件进行交互,工作区文件的提交或者回滚首选都是通过暂存区,而版本库中除了暂存区之外,文件的提交的最终存储位置是分支(Branch),在创建版本库的时候默认都会有一个主分支(Master)。
各区交互流程如下:
以下的命令操作能满足日常的工作。
GIT的分支管理
GIT有一个分支管理的策略,它可以使得版本库的演进保持简洁,主干清晰,各个分支各司其职、井井有条。理论上,这些策略对所有的版本管理系统都适用,GIT只是用来举例而已,同样其实SVN上也有。
一、主分支Master
代码库应该有且仅有一个主分支,所有提供给用户使用的正式版本,都在这个主分支上发布,类似SVN的主干(trunk)。
二、开发分支Develop
主分支只用来分布重大版本,日常开发应该在另一条分支上完成。我们把开发用的分支叫做Develop,类似SVN的(branch)。
三、临时性分支
Master和Develop。前者用于正式发布,后者用于日常开发。其实,常设分支只需要这两条就够了,不需要其他了。但是,除了常设分支以外,还有一些临时性分支,用于应对一些特定目的的版本开发。临时性分支主要有三种:
* 功能(feature)分支
* 预发布(release)分支
* 修补bug(fixbug)分支
这三种分支都属于临时性需要,使用完以后,应该删除,使得代码库的常设分支始终只有Master和Develop。
-
功能分支
它是为了开发某种特定功能,从Develop分支上面分出来的。开发完成后,要再并入Develop。
-
预发布分支
第二种是预发布分支,它是指发布正式版本之前(即合并到Master分支之前),我们可能需要有一个预发布的版本进行测试。预发布分支是从Develop分支上面分出来的,预发布结束以后,必须合并进Develop和Master分支。它的命名,可以采用release-*的形式。
-
BUG分支
最后一种是修补bug分支。软件正式发布以后,难免会出现bug。这时就需要创建一个分支,进行bug修补。修补bug分支是从Master分支上面分出来的。修补结束以后,再合并进Master和Develop分支。它的命名,可以采用fixbug-*的形式。
我们可以看一下由Git flow 发明人Vincent Driessen提出的分支管理模型。篇著名的 "A successful Git branching model",Vincent Driessen 前些时间更新了他 10 年前那篇著名的 "A successful Git branching model",大意是 Git flow 已不适用于当今持续交付的软件工程方式,推荐更简单的 Github flow 等模型
我们需要明白GIT的分支管理,首先我们得先明白GIT的工作流是如何进行的,对于工作流中并行开发管理中如何解决冲突的。
一 GitHub Flow
GitHub Flow —— 以部署为中心的开发模式,通过简单的功能和规则,持续且高速 安全地进行部署。在实际开发中往往一天之内会实施几十次部署,而支撑这一切的,就是足够简单的开发流程以及完全的自动化。
GitHub Flow 特点:
- 令master 分支时常保持可以部署的状态
- 进行新的作业时要从master 分支创建新的分支,新分支名称要具有描述性
- 在
2
新建的本地仓库分支中进行提交 - 在Github 端仓库创建同名分支,定期push
- 需要帮助、反馈,或者branch已经准备merging时,创建Pull Request,以Pull Request 进行交流
- 让其他开发者进行审查,确认作业完成后与master分支进行合并(合并的代码一定要测试
- 与master分支合并后,立刻部署
使用Github Flow 的前提条件:
- 团队规模最好控制在15-20人之内,具体见 how-github-works
- 部署作业完全自动化。必须自动化,一天之类需要多次部署
- 使用部署工具(Capistrano,Mina,Fabric,Webistrano,Strano等),让部署时所需的一系列流程自动化。
- 通过Web界面进行部署,Capistrano 等部署工具需要命令执行操作,开发者以外的人很难实施部署
- Capistrano http://github.com/capistrano/capistrano //Ruby开发的代表性部署工具
- Webistrano http://kentaro/webistrano //可以通过Web执行Capistrano的工具
- 导入开发时注意事项:随着团队人数的增多及成熟度的提高,开发速度会越来越快。往往一个部署尚未完成,另一名开发者就已经处理完下一个pull request,开始实施下一个部署。在这种情况下,一旦正式环境出现问题,很难分辨哪个部署造成了影响。为了应对该情况,建议在部署实施过程中通过工具加锁。
- Git Hook 自动部署
- 重视测试
- 让测试自动化
- 编写测试代码,通过全部测试
- 维护测试代码
二 Git Flow
荷兰程序员 Vincent Driessen 曾发表了一篇博客,让一个分支策略广为人知。具体流程见下图(引用该博客的一幅图片)
这一流程最大的亮点是考虑了紧急Bug的应对措施,整个流程显得过于复杂,所以在实施该方案前,需要对整个开发流程进行系统的学习。也需要借助Git flow 等工具的辅助。
下面根据上图,按不同分支 进行 说明:
master 分支和 develop分支
在Git Flow 中,这两个分支至关重要,它们会贯彻整个流程始终,绝对不会被删除。
master 分支
master 分支时常保持着软件可以正常运行的状态。由于要维护这一状态,所以不允许开发者直接对master 分支的代码进行修改和提交。
其他分支的开发工作进展到可以发布的程度后,将会与master分支进行合并,并且这一合并只在发布成品时进行。发布时将会附加版本编号的Git标签。
develop分支
develop分支是开发过程中代码中心分支。与master 分支一样,这个分支也不允许开发者直接进行修改和提交。
程序员要以develop分支为起点新建feature 分支,在feature 分支中进行新功能的开发或者代码的修正。也就是说develop分支维系着开发过程中的最新代码,以便程序员创建feature分支进行自己的工作。
在feature 中工作
feature 分支以develop分支为起点,是开发者直接更改代码发送提交的分支。开发流程:
- 从develop分支创建feature分支
- 从feature分支中实现目标功能
- 通过Github 向develop发送pull request
- 接受其他开发者审核后,将Pull Request合并至develop分支
具体指令:
$ git checkout develop
$ git pull
$ git flow feature start add-user //add branch feature/add-user
$ git branch
// feature/add user start commit commit ....
$ git push orgin feature/add-user
//到github 上去代码审查,切到develop分支,进行pull request
$ git checkout develop
$ git pull // 当feature/add-user 合并到 develop后,本地develop 需要更新到最新状态
注意,默认状态是pull request 到master。这时需要手动切换到develop分支,再进行pull Request 操作。
如果采用该开发策略,那么可以在setting 中 Option 中,修改Default Branch 为 develop ,这样就省去了手动修改的麻烦。
与develop分支合并后,已经完成工作的feature分支可以在适当的时机删除
更新本地的develop分支
我们发送的pull request 在github 端与develop 合并后,为了让其反应到本地的develop分支中,我们需要进行以下操作:
- 切换到develop分支
- 执行git pull (fetch & merge)
每当需要从develop分支创建feature等分支时,记得一定要先执行上述操作,保证develop分支处于最新状态。
release分支
创建 release分支 ,在这个分支,我们只处理与发布前准备相关的提交,比如版本编号变更的元数据的添加工作。如果软件部署到预演环境后测试出bug,相关修正也要提交到这个分支。
注意:该分支绝对不能包含需求变更或者功能变更等重大修正。这一阶段的提交数应该限制到最低。
$ git checkout develop
$ git pull
$ git flow release start '1.0.0'
当所有修正处理完后,我们结束这分支
$ git flow release finish '1.0.0'
//期间会需要填写 提交信息、这个版本的提交信息、合并的提交信息。无特殊情况,一般默认。
查看版本tag
通过前面一系列的操作,我们创建了与发布版本号相同的Git标签
$ git tag
1.0.0
更新到远程仓库
对此,我们对多个分支进行了修改,所以需要利用push操作将修改更新到Github端的远程仓库。先从develop开始
$ git push origin develop
然后是master
$ git checkout master
$ git push origin master
再push 标签信息
$ git push --tags
这样版本号 1.0.0 的标签信息就已经push 完成
在hotfix 分支下进行工作
下述情况需要创建 hotfix 分支
- release 版本中发现了bug 或者漏洞
- develop 分支正在开发新功能,无法面向用户进行发布
- 漏洞需要及早处理,无法等到下一次版本发布
假设修复BUG 后的版本至 1.0.1
$ git fetch origin
现在以1.0.0的标签信息为起点,创建名为1.0.1 的hotfix分支。
$ git flow hotfix start '1.0.1' '1.0.0'
修复工作结束后,将hotfix 分支push 到github端的远程仓库,并向master分支发起Pull Request
$ git push origin hotfix/1.0.1
创建标签和进行发布
在Github项目主页,点击release ,为本次hotfix 创建1.0.1标签。点击 Draft a new release 按钮,输入相关标签信息,在Target中指定master分支(master分支已经合并了hotfix1.0.1的修改)。然后填写相关信息,点击Publish release 进行发布
1.0.1发布后,之前发布的成品也就完成了生命周期
$ git fetch origin
从 hotfix 分支合并到develop 分支
登录到Github,从hotfix1.0.1分支向develop分支发送Pull Request即可。审查后便会被合并到develop分支
Git Flow 的小结
建议把开发流程图放大贴在墙上,这样能够有效帮助团队成员理解流程内容
版本号的分配规则 x.y.z
x: 在重大功能变更,或者版本不向下兼容+1,此时y z归零
y: 在添加新功能或者删除已有功能+1 此时z归零
z: 只在进行内部修改后+1.
问题与矛盾
目前一般互联网公司开发都是小步快跑快速迭代的持续集成进行产品开发、产品测试、产品发布,比较少像传统方式进行瀑布式的研发、巨大的项目计划,长时间跨度的里程碑计划,需要灵活开展业务,所以Git Flow发明者也自认不太符合持续集成这些模型,以下举例说明。
master和release是重复吗?
对于不需要管理多个“旧版本”的产品来说,开发流程master和release是几乎重复的。一般产品无论是APP还是后台服务,现在做法都可以强制升级,所以大部分是不需要保留太多的“历史”版本,在gitflow里release分支的出现主要是为了应对“预发布”流程的。正如上一小节介绍的传统开发流程所述的开发流程所述。
但对于持续交付的开发流程,就没有这么多事情有了新功能需求就开发,开发完了测,测了就直接上线了(我们就是直接合并master),不需要再多折腾一个release分支。
其实就算是gitflow,也可以通过在master上打tag的方式来区分哪个是正式对外的,哪个是内部预发布的。但很多人对master有蜜汁洁癖,认为master上任何一个commit都必须可以对外。而我觉得代码的版本和发布的版本可以分开管,尤其是有了docker之后完全可以把对外的docker image单独记录和关起来,不一定非得用git搞定一切。为了对照,docker image的名字上只要记录对应的commit id即可。
环境需要定制的分支
如果偶尔有非常大的功能开发,需要持续占用一个环境很长时间。如做一个相当复杂的功能,升级Spring这种底层组件等,不只有这一个这样的开发任务,有可能有2~3个并且还有一些要与外部联调。对于这种工作就需要一个为这个环境定制的分支,并为这个分支单独定制一套环境。这个分支会包括那个重大的修改,并不断的把平时上线的功能都merge进来。我们可以称呼他为“环境分支“,而gitflow并没提供这样的分支。
环境分支另外一个好处是容易形成约定。大家都知道某个环境就对应到一个特定分支里,自己的代码合并过去就可以测了。广义上,联调环境,回归测试环境,手工测试环境,压测环境,甚至生产环境都是环境。如果master/develop/任何功能分支都不适合直接用在这个环境上,那么就是需要“环境分支“的时候了。
总体说来,环境分支工作流大概是这么处理。
这样做保证了灵活性,但带来了一个问题:即随着时间的积累,不同环境分支的代码的差异会越来越大。所以要每隔一段时间用master来重新sync一遍所有的环境分支。为此需要开发一些工具。
谁来关闭功能分支
每个功能开一个新的功能分支,在git里是很轻松简单的事。但谁来关闭就有些迷了。gitflow并没有对此有任何规定。一个简单的办法是,如果一个功能被上线了,其功能分支就可以删除了。
但有的时候需要多留这个分支一段时间,这样比较容易产生diff,看到代码的短期变更,以便于修复某些bug,或者确认下代码的变化。因此我们内部差不多会对2周之前的分支进行清理,除非谁要求必须留下某个分支更长时间。
小结一下
gitflow是个很好的流程,这个好并不是说他是万金油,任何场景都有效。而是说,他是一个目标清晰且自洽的流程。对于不适合的地方,作者就承认不适合。我们从中学到的不仅仅是这个流程本身,而应该是根据自己的业务和开发需要来定制流程和工具的方法。
GIT命令大全
略
GIT参考文档出处:
1、分支管理:http://www.ruanyifeng.com/blog/2012/07/git.html
2、成功的模型:https://nvie.com/posts/a-successful-git-branching-model/