Git 使用指南之 WorkFlow (工作流)
Git 多人协作必须有一个规范的工作流程,让大家有效地合作,使得项目井井有条地发展下去,这个流程叫做 WorkFlow(工作流),也称为 Git 分支管理策略。工作流不涉及任何命令,因为它就是一个规则,完全由开发者自定义,并且自我遵守。
版权说明: 本文思路以及内容主要来自廖雪峰老师的 Git 教程 (强烈推荐膜拜原文),并结合个人使用所作,只作为学习记录使用。如内容有侵权请联系删除,禁止转载!
更多 Git 相关内容,请关注博主 Git 博文系列:
之五 >>> Git 使用指南之 WorkFlow (工作流)
何为 Git 工作流?
WorkFlow 的字面意思,工作流,即工作流程。Git 中因为有分支的存在,才构成了多工作流的特色。
事实的确如此,因为项目开发中,多人协作,分支很多,虽然各自在分支上互不干扰,但是我们总归需要把分支合并到一起,而且真实项目中涉及到很多问题,例如版本迭代,版本发布,bug 修复等,为了更好的管理代码,需要制定一个约束的工作流程。
简言之,Git 工作流指的是多人协作过程中的 git 的使用流程,不涉及技术细节,是一种项目管理、开发约定的方式。
工作流最受欢迎榜
目前使用度最高的工作流前三名(排名不分先后)分别是以下三种:
- Git Flow
- GitHub Flow
- GitLab Flow
其中 Git Flow 出现的最早;GitHub Flow 是在 Git Flow 的基础上,做了一些优化,适用于持续版本的发布;而 GitLab Flow 是综合前面两种工作流的优点,制定而成的一个工作流。
PopCharts Introduction
你可以根据这里关于上述三种工作流的特性描述,为自己的项目构筑适配的工作流,毕竟适合自己的才是最好的~~~
Git Flow
Git Flow 工作流,是 Vincent Driessen 2010 年发布出来的他自己的分支管理策略,一经发布就广为留下,目前位置使用度非常高,也是我们日常项目的常用工作流。
Git Flow 的分支结构很特别,按功能来说,可以分支为 5 种分支。从分支生命周期来看,可以分别归类为:长期分支 && 短期分支,或者更贴切描述为,主要分支 && 协助分支。
主要分支
Git Flow 分支管理策略项目中,代码的中央仓库会一直存在以下两个 长期分支:
- master
- develop
其中,origin/master
分支上的最新提交永远是提供给用户使用的正式发布版本(任何时候 master 分支上拿到的,都是稳定的分布版)。origin/develop
分支用于日常开发,存放最新的开发进度。
当 develop 上的代码达到一个稳定的状态,可以发布版本的时候,develop 上的修改可以以某种特别方式(–no-ff,有时也需要借助后面将要提到的协助分支 {非必须} 等)被合并到 master 分支上,然后标记上对应的版本标签(tag),为以后项目排查定位提供便利。
事实上,常设分支只需要这两条主要分支就够了,不需要其他了~~~
协助分支
然而,除了常设分支以外,Git Flow 的开发模式还需要一系列的协助分支,来帮助更好的功能的并行开发,简化功能开发和问题修复。
协助分支是暂时分支,它们非常无私奉献,在需要它们的时候,迫切地创建,用完它们的时候,又挥挥衣袖地彻底消失。
协助分支分为以下三类:
- Feature Branch(功能分支)
- Release Branch(预发布分支)
- Hotfix Branch(热修复分支)
1 –> Feature Branch
Feature 功能分支,是为了开发某种特定功能,从 develop 分支上面分出来的。Feature Branch 命名可以采用:feature-xxx
的方式。
待功能模块开发完成之后,会将其重新合并到 develop 分支上,然后删除 Feature Branch。
如图,有两个功能模块在开发,其中一个已经完成,重新合并到 develop 分支;另一个仍处于开发状态。
2 –> Hotfix Branch
Hotfix 热修复分支,是用来做线上的紧急 Bug 修复的,建议命名为 hotfix-xxx
。
当线上(master)某个版本出现了问题,将检出对应版本的代码,创建 Hotfix 分支,问题修复后,合并回 master 和 develop ,然后删除 Hotfix Branch。
这里注意,合并到 master 的时候,也要打上修复后的版本标签。
3 –> Release Branch
Release 预发布分支,是指发布正式版本之前(即 develop 分支合并到 Master 分支之前),我们可能需要从 develop 分支上分出来一个预发布的版本分支进行测试,预发布(测试)结束以后,必须将其合并进 develop 和 master 分支,然后删除 Release Branch。Release Branch 命名可以采用:reature-xxx
的方式。
例如,在软件 1.0.0 版本的功能全部开发完成提交后,从 develop 检出 release-1.0.0 进行测试,测试中出现的小问题,在 release 分支进行修改提交,测试完毕准备发布的时候,代码会合并到 master 和 develop,master 分支合并后会打上对应版本标签 v1.0.0, 合并完成后删除 release-1.0.0。
这样做的好处是:在 测试的时候,不影响下一个版本功能并行开发。
最后我们来看发布之后的目前的日志记录情况,这里将没有用的分支 hotfix、release、feature 均删除了,可以看出常驻分支只有 master/dev,最下面的 feature 表示仍在开发中。
No Fast Forward
需要说明的是,Git Flow 的作者 Vincent Driessen 非常建议,合并分支的时候,加上 no-ff 参数,采取禁用快速合并模式,使用递归策略合并模式。
好处:保证一个非常清晰的提交历史,防止丢失分支信息。
Git Flow Diagram
这里给出 Git Flow 整体工作流程的图解,可以看出,Git Flow 的五种分支,master,develop,feature branchs ,release branchs , hoxfixes。
其中,master 和 develop 字体被加粗代表主要分支。master 分支每合并一个分支,无论是 hotfix 还是 release ,都会打一个版本标签。通过箭头可以清楚的看到分支的开始和结束走向,例如 feature 分支从 develop 开始,最终合并回 develop ,hoxfixes 从 master 检出创建,最后合并回 develop 和 master,master 也打上了标签。
Git Flow Evaluation
Git Flow 综合考虑了开发、测试、新功能开发、临时需求、热修复,理想很丰满,现实很骨干,这一套运行起来实在是太复杂了/(ㄒoㄒ)/~~
Git Flow 的优点是清晰可控,缺点除了:1.频繁在两个长期分支(master && develop)之间切换;2.没有 GUI 图形页面,只能命令行操作;
更大的问题在于 >>>>>
Git Flow 是基于 “版本发布” 的,目标是一段时间以后产出一个新版本。但很多网站项目是”持续发布”(快速迭代),代码一有变动,就部署一次。这时,master 分支和 develop 分支的差别不大,没必要维护两个长期分支。
GitHub Flow
Github flow 是 Git flow 的简化版,专门配合 ”持续发布”,它是 Github.com 使用的工作流程。
Github flow 只有一个长期分支,就是 master
,因此用起来非常简单。
Introduction
官方推荐的流程如下:
GitHub Flow 模型简单说明:
第一步:根据需求,只有一个长期分支 master ,而且 master 分支上的代码,永远是可发布状态。并且 master 分支会设置
protected
分支保护,只有有权限的人才能推送代码到 master 分支。第二步:根据需求,从 master 拉出新分支,不区分功能分支或热修复分支等。
第三步:新分支开发完成后,或者需要讨论的时候,就向 master 发起一个
pull request
(简称 PR)。第四步:Pull Request 既是一个通知,让别人注意到你的请求;又是一种对话机制,大家一起评审和讨论你的代码。对话过程中,你还可以不断提交代码。
第四步:你的 Pull Request 被接受,合并进 master,重新部署后,原来你拉出来的那个分支就被删除。
特色之 Pull Request
GitHub Flow 最大的特色就是 Pull Request 的提出,这是一个伟大的发明,它的用处并不仅仅是合并分支,还有以下功能:
- 可以很好控制分支合并权限:分支不是你想合并就合并,需要对方同意
- 问题讨论 或者 寻求其他小伙伴们的帮助:和拉个讨论组差不多,可以选择相关的人参与,而且参与的人还可以向你的分支提交代码,非常适合代码交流
- 代码 Review:如果代码写的很烂,有了 pull request 提供的评论功能支持,准备好接受来自 review 的实时吐槽吧。
Github Flow 这种方式,要保证高质量,对于贡献者的素质要求很高,换句话说,如果代码贡献者素质不那么高,安安心心小板凳做好膜拜大佬就好了~~~
特色之 issue tracking
日常开发中,会用到很多第三方库,然后使用过程中,出现了问题,是不是第一个反应是去这个第三方库的 GitHub 仓库去搜索一下 issue ,看没有人遇到过,项目维护者修复了没有???一般未解决的 issue 是 open 状态,已解决的会被标记为 closed,这就是 issue tracking(问题追踪)。
如果你是一个项目维护者,除了标记 issue 的开启和关闭,还可以给它标记上不同的标签,来优化项目。当提交的时候,如果提交信息中有 fix #1 等字段,可以自动关闭对应编号的 issue。
issue tracking 非常适合开源项目。
GitLab Flow
Gitlab Flow 集百家之长,补百家之短。
Gitlab Flow 是 Git Flow 与 Github Flow 的综合。它吸取了两者的优点,既有适应不同开发环境的弹性,又有单一主分支的简单和便利。它是 Gitlab.com 推荐的做法。
GitLab 既支持 Git Flow 的分支策略,也有 GitHub Flow 的 Pull Request( Merge Request ) 和 issue tracking。
Git & GitHub Flow 的瑕疵
当 Git Flow 出现后,它解决了之前项目管理的很让人头疼的分支管理,但是实际使用过程中,也暴露了很多问题:
- 默认工作分支是 develop,但是大部分版本管理工具默认分支都是 master,开始的时候总是需要切换很麻烦。
- Hotfix 和 Release 分支在需要版本快速迭代的项目中,几乎用不到,因为刚开发完就直接合并到 master 发版,出现问题 develop 就直接修复发布下个版本了。
- Hotfix 和 Release 分支,一个从 master 创建,一个从 develop 创建,使用完毕,需要合并回 develop 和 master。而且在实际项目管理中,很多开发者会忘记合并回 develop 或者 master。
随着,GitHub Flow 的出现,极大程度上简化了 Git Flow ,因为只有一个长期分支 master,并且提供 GUI 操作工具,一定程度上避免了上述的几个问题,然而在一些实际问题面前,仅仅使用 master 分支显然有点力不从心,例如:
- 版本的延迟发布(例如开发 iOS 应用审核过程中(等待审核上架),可能也要在 master 上推送代码,导致线上版本落后 master 分支)
- 不同环境的部署 (例如:测试环境,预发环境,正式环境)
- 不同版本发布与修复 (只有一个 master 分支真心不够用)
GitLab Flow 解决方案
在给出解决方案之前,需要明确 GitLab Flow 中的一个最大原则:”上游优先”(upsteam first)。
”上游优先”原则:即只存在一个主分支master,它是所有其他分支的”上游”。只有上游分支采纳的代码变化,才能应用到其他分支。只有紧急情况,才允许跳过上游,直接合并到下游分支。
关于”上游优先”原则的具体应用,请参看下文 >>>>
为了解决 Git Flow & GitHub Flow 中那些毛茸茸的小问题,GitLab Flow 给出了以下的解决方案:
1 –> 版本的延迟发布
解决思路:Prodution Branch & Upsteam First
master 分支不够,于是添加了一个 prodution 分支,专门用来发布版本。
但要注意需要遵循 ”上游优先”(upsteam first),代码的变化,必须由”上游”向”下游”发展。比如,生产环境出现了 bug,这时就要新建一个功能分支,先把它合并到 master,确认没有问题,再 cherry-pick
到 production
。
2 –> 不同环境的部署
应对”持续发布”的项目,解决思路:Environment Branches & Upstream First
每个环境,都对应一个分支,例如下图中的 pre-production 和 prodution 分支都对应不同的环境,GitLab Flow 模型比较适用服务端,测试环境、预发环境、正式环境,一个环境建一个分支。
比如,”开发环境”的分支是 master,”预发环境”的分支是 pre-production,”生产环境”的分支是 production。
开发分支是预发分支的”上游”,预发分支又是生产分支的”上游”。代码的变化,必须由”上游”向”下游”发展。比如,生产环境出现了 bug,这时就要新建一个功能分支,先把它合并到master,确认没有问题,再 cherry-pick 到 pre-production,这一步也没有问题,才进入 production。
只有紧急情况,才允许跳过上游,直接合并到下游分支。
3 –> 版本发布分支
应对”版本发布”的项目,解决思路:Release Branches & Upstream First
只有当对外发布软件的时候,才需要创建 release 分支。作为一个移动端开发来说,对外发布版本的记录是非常重要的,如果线上出现了一个问题,需要拿到问题出现对应版本的代码,才能准确定位问题。
GitLab Flow 中建议的做法是:每一个稳定版本,都要从 master 分支拉出一个分支,比如 2-3-stable、 2-4-stable 等等。发现问题,就从对应版本分支创建修复分支,完成之后,先合并到 master,才能再合并到 release 分支,遵循 “上游优先” 原则,并且记得要更新小版本号。
Useful Tips
Pull Request
GitHub Flow 中的 Pull Request 见上文相关部分,GitLab Flow 中也支持 Pull Request,只是叫法改为 Merge Request!!!
Protected branch
master 分支应该受到保护,不是每个人都可以修改这个分支,以及拥有审批 Pull Request 的权限。
Github 和 Gitlab 都提供”保护分支”(Protected branch)这个功能。
Issue tracking
Issue 用于 Bug追踪和需求管理。建议先新建 Issue,再新建对应的功能分支。功能分支总是为了解决一个或多个 Issue。
功能分支的名称,可以与issue的名字保持一致,并且以 issue 的编号起首,比如:”15-require-a-password-to-change-it”。
开发完成后,在提交说明里面,可以写上 “fixes #14” 或者 “closes #67”。Github 规定,只要 commit message 里面有下面这些 “动词 + 编号”,就会关闭对应的 issue。
1 | close |
Merge With NO-FF
需要说明的是,合并分支的时候,加上 no-ff 参数,采取禁用快速合并模式,使用递归策略合并模式。
这样可以保证一个非常清晰的提交历史,防止丢失分支信息。
Squash commits
为了便于他人阅读你的提交,也便于 cherry-pick 或撤销代码变化,在发起 Pull Request 之前,应该把多个 commit 合并成一个(前提是,该分支只有你一个人开发,且没有跟 master 合并过)。
这可以采用 rebase 命令附带的 squash 操作
=====================================================
至此,Git 中的分支管理策略就也就介绍完了。事实上,Git 的使用真的很灵活自由,你完全可以采取搭配式的工作流模式,比如:GitLab + Git Flow,一定要把思路打开。
这里再扩展一种工作流:Forking Flow
Forking Flow
开源项目常用的工作流 ——Forking 工作流,介绍之前首先需要了解什么是 fork 操作,见下图:
fork 操作是在个人远程仓库新建一份目标远程仓库的拷贝,操作很简单,比如 github 上在项目的主页点击 fork 按钮即可。
明白了 fork 操作之后,我们来看下 forking 工作流的流程,如下:
首先开发者 A 拥有一个远端仓库 >>>
这时候有一个开发者 C 也想参与 A 的这个项目的开发工作,那他就可以 fork 一份 A 的这个仓库,之后在 C 的个人仓库里就有了这份代码库,后续开发者 C 就可以在自己的这个项目里进行开发工作,C 在完成了某个功能的实现之后,可以给 A 的仓库发一个 PR 请求,这时候会通知到开发者 A 有新的 PR,A 如果有问题可以直接在这个 PR 里提,开发者 C 可以进行进一步的修改,最后 A 通过了 C 的这份 PR 请求,就会将 C 的代码合并进 A 的仓库,这样就完成了 A / 代码库新特性的开发。同时如果有其他开发者对 A 的项目有兴趣也会进行相同的操作。
这里注意到 开发者 B/C 并不是 A 代码库的开发人员,而是第三方开发者,所以这种工作流主要用于开源项目!
Tag Management
在开发过程中, Git 支持使用 标签(Tag) 给仓库历史中的某一个提交打上标签,以示重要。 例如版本发布, 有重大修改, 升级的时候,开发人员会使用标签来标记发布(修改、升级)结点:v1.0 、 v2.0 等等,这可以为以后项目排查、定位提供极大便利。
将来无论什么时候,可以通过之前打好的标签(v1.0 、 v2.0),拿到那个时刻的历史快照,相当于版本库某个特殊时刻的快照。Git 标签虽然是 版本库的快照,但本质上它是 指向某个 commit 的指针(是不是跟分支很像???),所以创建和删除标签也都是瞬间完成的。
注意和分支指针不同的是:标签是指向 commit 的死指针,分支是指向 commit 的活指针!!!事实上,你可以将标签简单理解为 某个 Commit 的别名。
为何引入 Git Tag
思考一下:为什么 Git 有 commit,为什么还要引入 tag ???
你:“请把上周一的那个版本打包发布,commit 号是 6a5819e..”
同事:“一串乱七八糟的数字不好找!”
如果换一个办法:
你:“请把上周一的那个版本打包发布,版本号是v1.2”
同事:“好的,按照 tag v1.2 查找 commit id 就行!”
所以,tag 就是一个让人 容易记住的有意义的名字,它跟某个 commit 绑在一起(别名)。
查看标签
在开始标签操作说明之前,需要先介绍一下 Git 中的标签查看命令,后续标签操作需要查看命令的配合。
1 –-> git tag:列出标签
在 Git 中列出已有的标签非常简单,只需要输入 git tag
:
1 | 如果仓库中已创建了标签:v1.0,v2.0,可以使用命令进行查看 |
git tag 列出的标签,会以字母顺序列出,但是它们显示的顺序并不重要。
这时会有一个问题:当项目经过长时间维护,仓库中包含的标签数量较多时,以上命令会全部进行显示,不方便精确查找到目标。
这时,你可以通过通配符的方式,使用 git tag -l "tag version*"
进行精确查找,例如只对 1.8.5 系列感兴趣,可以运行:
1 | git tag -l "v1.8.5*" |
2 –> git show:查看标签信息
git show
命令可以用来查看某个标签对应的提交的信息:
1 | git show v0.8 |
Git WorkFlow 学习之后,你应该知道标签(Tag)正式情况下更多运用在 master 分支上(生产线上)。
创建标签
Git 中支持两种类型的标签:
- 轻量标签(Lightweight)
- 附注标签(Annotated)
轻量标签,轻量级的标签,它只是某个特定提交的引用。
而附注标签见名知义,除了引用作用之外,还包含了丰富的信息:打标签者的名字、电子邮件地址、日期时间等。
通常,推荐创建附注标签,这样你可以拥有以上所有信息。但是如果你只是想用一个临时的标签, 或者因为某些原因不想要保存这些信息,那么也可以用轻量标签。
轻量标签
创建轻量标签非常简单,没有保存任何其他信息,故创建时只需要提供标签名字即可:
命令格式 –>>> git tag <name>
1 | 1.首先,切换到需要打标签的分支上(master): |
附注标签
在 Git 中创建附注标签也十分简单,可以配合 -a
&& -m
参数一起使用,例如创建名为 v1.0 的附注标签:
1 | git tag -a v1.0 -m "Version 1.4: User Authorization" |
参数 -m
,你可以理解为提交时的提交信息。如果没有为附注标签指定一条信息,Git 会启动编辑器要求你输入信息。
通过使用 git show
命令查看标签信息和与之对应的提交信息:
1 | git show v1.0 |
可以看到,输出显示了打标签者的信息、打标签的日期时间、附注信息,然后显示具体的提交信息。
后期打标签
默认标签是打在最新提交的 commit 上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?
方法是你可以对历史提交的 commit id 打标签。 语法规则:git tag -a <tag name> <commit id>
假设当前提交历史是这样的:
1 | git log --pretty=oneline --abbrev-commit 6a9789d (HEAD -> master, tag: v1.0) Tag Test |
现在,假设在 v1.2 时你忘记给项目打标签,也就是在 “Merge branch ‘featureB’” 提交。你可以在之后补上标签:
1 | git tag -a v1.2 5f264b1 |
这时,Git 会启动编辑器要求你输入标签信息(类似于 git commit)。
可以看到你已经在那次提交上打上标签了:
1 | git tag |
注意:标签总是和某个 commit 挂钩。如果这个 commit 既出现在 master 分支,又出现在 dev 分支,那么在这两个分支上都可以看到这个标签。
共享标签
默认情况下,创建的标签都只存储在本地,git push
命令并不会自动推送标签到远程仓库服务器上。
想要将本地创建的标签同步到远程仓库中,在创建完标签后你必须显式地推送标签到远程仓库上。这个过程就像共享远程分支一样——你可以通过 git push origin <tagname>
实现:
1 | git push origin v1.2 |
如果想要一次性推送很多标签,也可以使用带有 --tags
选项的 git push
命令。 这将会把所有不在远程仓库上的标签全部传送到那里:
1 | git push origin --tags |
从远程仓库已经可以看到推送上来的所有标签了:
删除标签
1 –> 本地标签删除
要删除掉你本地仓库上的标签,可以使用命令: git tag -d <tagname>
。 例如,可以使用以下命令删除一个标签:
1 | git tag |
注意上述命令并不会从任何远程仓库中移除这个标签,你必须用下面命令更新你的远程仓库:
1 | git push <remote> :refs/tags/<tagname> |
实测如下:
1 | git push origin :refs/tags/v0.8 |
登陆 GitHub 查看,发现远程仓库标签已经被删除:
检出标签
如果你想查看某个标签所指向的文件版本,可以使用分支介绍中我们提到过的 git checkout
命令,但这会使我们仓库处于 “头指针游离(detached HEAD)” 的状态:
1 | git checkout v1.2 |
为什么会进入游离态?前面介绍 HEAD 指针时说过:HEAD 不指向 branch,而是指向分支上的某一个 commit 时就会进入游离态,再结合命令 git checkout
移动 HEAD 指针来实现切换分支,这里移动 HEAD 指针指向被打了标签的某一个特定提交,所以游离是显而易见的。
HEAD 处于 “游离态” 会产生不好的副作用,如果你做了某些更改然后提交它们,标签不会发生变化, 但你的新提交将不属于任何分支,并且将无法访问。如果你需要进行更改,比如你要修复旧版本中的错误(修复 Bug),那么通常需要创建一个新分支,然后在新分支上进行提交,这时新分支上的内容和 v1.2 就不一样了。
更多关于 HEAD 游离态的说明可以参看博文:Git 使用指南之 HEAD 指针。
Git 使用指南之 WorkFlow (工作流)
https://www.orangeshare.cn/2017/07/05/git-shi-yong-zhi-nan-zhi-workflow-gong-zuo-liu/
install_url
to use ShareThis. Please set it in _config.yml
.