Git的多种工作流
通过本文你可以了解到:
- git 的起源
- git 的基础知识
- gitflow 流程的基本方式
- 基于 git 的多种工作流
在讲 Git Flow 之前,我们先讲讲别的东西
-
什么是版本? 版是指印刷时的版,本就是印刷出来的书本;版本是一种称谓,用于描述同一事物的相互之间有差异的各种形式、状态或内容。 换言之,任何事物只要有差异化都会涉及到版本这个概念,但是,我们这里说的版本,包括后面聊到的东西,都应该是一些有意义的版本,举个例子,小明 1 月 1 日 至 1 月 31 日 每天都在改一份策划书,2 月 1 号小明的甲方说还是上一个版本好,此时对于小明来说,上一个版本是什么?也许是最近一次小明发给甲方的一个方案,也许是上一个甲方说还可以的方案,但小明可能已经不记得具体是几号改完给甲方的方案了。
-
常见的版本控制有哪些? copy 文件以命名区分的方式、本编辑器的撤回/前进功能、使用专业工具如 svn、git 等等都属于版本控制的范畴,不同的版本控制有不同的用途,比如文本编辑器的撤回,可以轻松撤销本次修改,比如 copy 文件,可以让新旧文件同时存在,方便对比,但这些方式太过简单了,而且中间过程都是一些临时性的东西,不足以作为一个修改历史参考或者完整版本来看待,为此,还需要一些专业工具,如 集中式版本管理系统 SVN、CVS,分布式版本管理系统 BitKeeper、Git 等。
-
Git 开发背景
同生活中的许多伟大事物一样,Git 诞生于一个极富纷争大举创新的年代。 Linux 内核开源项目有着为数众多的参与者。 绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。 到 2002 年,整个项目组开始启用一个专有的分布式版本控制系统 BitKeeper 来管理和维护代码。 到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统。 他们对新的系统制订了若干目标:
- 速度
- 简单的设计
- 对非线性开发模式的强力支持(允许成千上万个并行开发的分支)
- 完全分布式
- 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量) 自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。 它的速度飞快,极其适合管理大项目,有着令人难以置信的非线性分支管理系统(参见 Git 分支)。
-
1991 年 Linux 开发了 linux 系统这个开源项目,采用邮件发送源文件附带patch的方式进行写作开发,由 Linux 本人进行手工合并;
-
2002 年 BitKeeper 与 Linux 社区达成协议,允许 Linux 社区免费试用 BitKeeper,由于免费试用,协议内容更多地是保护 BitKeeper 自身。
-
2005 年 BitKeeper 不满 Linux 社区破坏协议内容(说白了就是反编译 BitKeeper,试图做破解版或其他),终止合作;
-
同 2005 年,Linux 花费了 2 周时间,开发了 Git 第一版,一个月内使用 Git 来管理 Linux 代码;
Git 基础知识
工作区(Workspace)、暂存区(Index)、版本库(Repository)
# 创建并进入 testGitFlow 目录
# 此时 testGitFlow 就是我们的工作区(Workspace),也就是工作目录
$ mkdir testGitFlow && cd testGitFlow
# 初始化 git 仓库
# 此时目录中增加了 .git 目录,.git 目录就是 git 仓库,不属于工作区
$ git init
# 新增两个文件
$ echo 111 > a.txt
$ echo 222 > b.txt
# 添加两个文件到暂存区/索引(Index)
$ git add .
# 把索引中的两个文件添加到版本库(Repository)
$ git commit -m 'init'
以上涉及的几个概念: Workspace: 简单理解就是我们的项目目录 Index: 简单理解就是存储即将提交的内容的区域 Repository: 版本仓库
Commit、Tree、Blob 对象
# 通过 git log 查看版本
$ git log
>
commit 2b304a56998989dbcfd77f370f4b43fcad9e5872 (HEAD -> master)
Author: huihuipan <huihuipan163@163.com>
Date: Mon Feb 27 17:56:53 2023 +0800
init
# 通过 git cat-file 查看 commit 信息
# 查看 commit 类型
$ git cat-file -t 2b304a
> commit
# 查看 commit 内容
$ git cat-file -p 10d717
>
tree 4caaa1a9ae0b274fba9e3675f9ef071616e5b209
author huihuipan <huihuipan163@163.com> 1677491813 +0800
committer huihuipan <huihuipan163@163.com> 1677491813 +0800
init
# 可以发现有 tree, author, committer 等信息
# 继续查看 tree 内容
$ git cat-file -t 4caaa1
> tree
$ git cat-file -p 4caaa1
>
100644 blob 58c9bdf9d017fcd178dc8c073cbfcbb7ff240d6c a.txt
100644 blob c200906efd24ec5e783bee7f23b5d7c941b0c12c b.txt
# 可以发现有 blob 信息
# 继续查看 blob 内容
$ git cat-file -t 58c9bd
> blob
$ git cat-file -p 58c9bd
> 111
# 可以看到里面存储的是 a.txt 的内容
以上涉及的几个概念: commit: commit 记录提交的版本 tree: tree 记录不同版本下的目录结构和文件名 blob: blob 记录文件内容
此时我们的 git 项目结构如下
修改文件及提交 commit 的时发生了什么?
- 首先,a.txt 内容 从 111 修改为 333,此时 git 仓库没有变化,只是工作区和索引的内容对不上了;
- 执行 git add 命令
- git 仓库根据新的 a.txt 内容(333)创建出一个新的 blob 结点,记录 a.txt 内容
- 索引从旧 blob 的指向新的 blob
- 执行 git commit 命令
- 根据索引的状态,生成 tree 对象
- 根据新生成的 tree 对象和 上一个 commit 对象,生成新的 commit 对象
- 把分支指针从旧的 commit 对象移动到新的 commit 对象
HEAD、Branch、Tag
Branch: 是指向 Commit 的指针,每一次提交新的commit,当前的 Branch 都会指向最新的 commit;
HEAD: 指向 Branch 的指针,当checkout 到非 branch 时,会提示处于分离头指针状态,可以做一些试验性的动作;
Tag: 指向 Commit 的指针,用作标签,通常用作记录固定版本,也可以理解为是指定 commit 的别名;
以上我们可以得知,git 的版本管理粒度去到了文件级别,blob 之间的对比即可得到 diff,这里也引申出了一个开发上的一个思考,当我们的程序设计的基础是一个比较小粒度的时候,后续开发和扩展就会更加灵活,事实上git 对commit 的操作也是非常灵活,灵活到稍不注意就有可能酿成事故。
Checkout、Merge、Rebase、Fetch、Pull
checkout 检出: 把 HEAD 检出到指定 branch 或 commit,或者检出指定版本指定文件的内容,由于在 git 里面checkout 承载了太多的功能,所有切换分支有专属命令 switch
。
merge 合并:
rebase 变基:
rebase 会修改版本历史,即使 rebase 前与 rebase 后的内容一致,但版本不再是同一个版本
fetch: 从另一个存储库下载对象和引用,如远程库
pull: git pull = fetch + merge
基于 Git 的几种工作流
Git Flow
简介
出自 Vincent Driessen 在 2010年写的一篇文章 《A successful Git branching model》
主要分支
有两个分支会贯穿整个版本的生命周期,也就是长期分支:
- master 分支:用于发布
- develop 分支:用于开发 master 分支和 develop 分支的关系如上,虚线部分指着两个分支并不是直接发生关联,而是通过 release/hotfix 分支发生关联
支撑分支
- feature branches: 用于需求开发
开发需求时从 develop 分支拉出 feature 分支,feature 分支开发完毕后(开发自测无问题)则合并回 develop 分支,合并后删除分支,后续出现 bug 则在 develop 分支修改。
- release branches: 用于发布
当 develop 分支处于一个相对稳定的状态时即可从 develop 分支拉出 release 分支准备发布,release 分支不进行功能开发,仅进行 bug 修复,直至无问题时合并到 master 分支进行发布,同时合并回 develop 分支后删除 release 分支。
- hotfix branches: 用于修复生产问题
hotfix 分支用于修复生产环境上急需修复的 bug, 当生产环境出现 bug 时,从 master 分支拉出 hotfix 分支,修复后合并回 master 分支进行发布,同时合并到 develop 分支后删除。
最后回顾一下完整的 gitflow
补充
在 2020年 Vincent Driessen 补充了一条反思笔记,大概说 Git Flow这种模式在持续交付的软件下显得复杂,可以考虑使用 Github Flow 而不是将 Git Flow 硬塞到项目中。
继 Git Flow 之后 Adam Ruka 针对 Git Flow 的技术细节做了优化,提出了 One Flow
Github Flow
相对于 Git Flow,Github Flow 只有一条主干分支,通过 github 平台加持增加 PR 流程: 进行某功能开发时,从 master 分支拉出 feature 分支,完成功能后提交 pr, 让相关人员进行 review, review 期间仍可以对 feature 进行提交,直至确认无问题后通过 pr, 可以把 feature 分支合并到 master 分支进行发布
GitLab Flow
GitLab Flow 使用 master 分支作为开发分支,基于 master 分支另起发布分支 production GitLab Flow 增加以下分支定义: 环境分支:当你需要在不同环境发布不同的版本时使用 发布分支:当项目需要发布不同的版本时使用,声明了一个发布分支后,这个分支只会合并严重的漏洞修复更新。
持续发布
gitlab-flow 推荐使用 master 分支进行开发,基于 master 分支另建 production 分支进行发布,另外提出了 环境分支的概念,根据不同环境,逐层合并,最后汇总到 production 发布分支后进行发布
版本发布
如果你的项目需要发布不同的版本, gitlab-flow 版本发布模式可能更适合,在持续发布模式下,不同的版本会有不同的发布分支进行发布。
Aone Flow
Aone-flow 是以 master 分支为基础,除 master 分支外其他都是临时分支。基于 master 分支拉出环境分支,环境分支之间不进行任何关联,独立发展,环境分支也不允许直接修改,而是通过合并不同的 feature 分支进行组合。 feature 分支直至合并到 发布分支后才会删除。有点是操作粒度更高更可控,缺点是环境分支的内容即使是一样的,但版本历史却有可能不一致。
怎样选择版本控制
上面介绍了好几种 flow, 从 gitflow 开始,gitflow 让自由度超高的 git 得到了指导性的使用方式; 而 github-flow 又针对了 gitflow 的复杂性提出了极简版的 flow; gitlab-flow 又针对 gitflow 和 github-flow 过于复杂或过于简单的方式,提出了自己折中的方案,同时还给出了两种交付方式(持续交付、版本交付)的方案; 最后也介绍了 AoneFlow,一种操作粒度更自由的方案。
其实没有一种万能方案,不同的团队/项目有着其特殊的情况,针对不同情况,flow 也在变化,合适的就是最好的。
最后
To conclude, always remember that panaceas don't exist. Consider your own context. Don't be hating. Decide for yourself.
引用 Vincent Driessen 的一句话:“最后,永远记住万能药不存在。考虑你自己的背景。不要讨厌。自己决定”
参考
转载自:https://juejin.cn/post/7208349474841526330