作为一名技术人员,git 是我们每天都会接触到的。但你真的了解它吗?你知道 git 是如何存储我们的代码数据,又是如何保障数据安全的吗?接下来带你用五分钟的时间了解 git 的方方面面。Let's go!
虽然 git 很多时候被用来管理代码。但如果抛开代码,git 其实是一个版本控制系统,而且是世界上最先进的版本控制系统。主要体现在三个方面:
因此,git 作为版本控制系统,解决了手动管理文件变更历史的问题;
此外,git 还是一个对象数据库。数据在 git 内部就是通过一系列对象来存储的。具体内容见后文。
同样是版本控制系统,我们为什么不用其他工具,而使用 git 呢?主要得益于 git 有以下几个特性:
git 的操作命令从使用程度上可分为上层命令和底层命令。上层命令就是大部分人平时用到的 git add xxx
、git commit -m xxx
、git push
等。底层命令则是 git 真正的内部操作,需对 git 内部原理有所了解才能熟练驾驭。相关命令展示如下:
命令语法 | 作用 |
---|---|
git init | 初始化一个空仓库 |
git add | 添加某个文件到 git 仓库 |
git commit | 提交本次改动 |
git push | 推送到远程仓库 |
git reset | 将当前分支重置到某个版本 |
git checkout | 切换分支或版本 |
git log | 查看提交历史 |
git hash-object | 将任意数据保存于 git 数据库中,并返回 key 值 |
git cat-file | 查看 git 数据库中的文件内容或类型 |
git update-index | 暂存区处理 |
git read-tree/write-tree | 将树对象读入暂存区/将暂存区内写入一个树对象 |
git commit-tree | 创建一个提交对象 |
git symbolic-ref | 对 HEAD 进行操作 |
git update-ref | 更新引用 |
... | ... |
git 是一个分布式的版本控制系统,有非常多的特性,如分支管理、工作区、fast forward 等。但也有不少难以使用的地方:
此时 git-flow 出现了,我将它称为「git 工作流」,从字面意思来看,就是按照一个规定好的工作流程来操作,以保证规范性(此处解决了上述第三个问题)。git flow 严格来说是一种思想、机制,git 相关的大部分操作都可以用某些「流」来完成。它不同于许多开发人员惯用的方式。在传统开发模式下,许多人只维护了「master」和「develop」分支,相关的新功能在 develop 上开发,完成后 merge 到 master 分支上,更有甚者只有一个 master 分支。这样的工作模式对于对于并发进行多个 feature 的管理简直是个噩梦。
git flow 设定了五个分支:master、develop、hotfix、feature、release。各个分支通过不同的耦合,形成一个「流」,共同完成代码从编写到上线的一系列流程。各个分支之间的关系如下图:
(图片来自网络)
下面来逐一解释:
由此,git flow 成功将各个分支串起来,形成一个工作流,以便于快速开发迭代。
git 本地数据管理,大概可以分为三个区,工作区,暂存区和版本库。
git 相关的所有数据均存储在 .git 目录中。我们新建一个仓库,来看一下目录结构:
比较重要的主要是以下几个,分别在后文介绍:
git 内存存在四种对象,分别是 blob、tree、commit 和 tag 对象。每种对象都会生成一个长度为 40 的 sha1 值来标记并存储。
blob 对象,可以类比为 linux 系统上的 inode 节点或具体文件,是真正存储数据的对象。其布局如图所示:!
可见,一个 blob 对象由 header 和 body 组成。其中 header="blob"+数据长度,body 为实际存储的数据。然后对整个对象做 sha1 计算,得到上文所说的 sha1 值。以该值前两位作为目录名,后 38 位作为文件名,存储于 objects 目录下。文件内容为对整个对象做 zlib 压缩后的结果。如下图所示:
从上图可以看出,我们可以通过 git hash-object
命令,将一个文件的内容写入到一个 blob 对象中。但 blob 对象还是存在两个问题:
因此,就引出了 tree 对象。
如果说 blob 对象,可以类比为 linux 系统上的 inode 节点或具体文件的话,tree 对象就可以类比未 linux 系统上的目录,指向 blob 对象或者子目录对象。其结构如下图所示:
可见,tree 对象的 header 跟 blob 对象较为类似,但其 body 包含好多条记录,每条记录均指向一个子 blob 对象或子 tree 对象,并记录了子对象的文件模式(图中未画出)、类型、sha1 值和文件名。下图演示了如何生成一个 tree 对象并将上文中生成的 blob 对象添加进去:
同样,tree 对象也有一些问题,最大的问题就是:该 tree 对象及其子对象是谁保存的?为什么保存?什么时间保存的?该问题可以由 commit 对象来解决。
commit 对象的 body,包含其所指向的 tree 对象的 sha1 值、父 commit 对象的 sha1 值(如果有)、提交者以及相关说明信息。布局如图所示:
下图演示了如何生成一个 commit 对象:
tag 对象有两种:轻量 tag 对象和附注 tag 对象。
若使用 git tag xxx
命令,生成的为轻量 tag 对象。轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。
而附注 tag 是存储在 git 数据库中的一个完整对象, 它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间等。通过 git tag xxx -m 'yyy'
即可生成一个附注 tag 对象。其布局如下:
git 有三种引用类型:HEAD 引用、标签引用和远程引用。均存储于 .git/refs 目录下。下图演示了 HEAD 引用及标签引用:
可以看出,我们平时所说的分支,其实就是一个引用,对应 .git/refs/heads 下面的一个文件,文件内容为其所指向的 commit 对象指针。另外,也印证了轻量 tag 对象其实就是一个引用。
.git 目录下面还有一个特殊的 HEAD 文件:
可以看出,该文件内容为 refs/heads/master,指向上文提到的分支引用的路径。因此 HEAD 文件相当于「引用的引用」,也可以理解为「指向指针的指针」,即二级指针,用于记录当前 git 工作在哪一个分支之上。
如图为 git 中各对象、引用之间的关系,可以看出,HEAD 指向引用,引用指向 commit 对象,commit 对象指向 tree 对象,其 parent 指针指向父对象。tree 对象指向 blob 对象,blob 对象中存储具体数据。