Git 五分钟入门

作为一名技术人员,git 是我们每天都会接触到的。但你真的了解它吗?你知道 git 是如何存储我们的代码数据,又是如何保障数据安全的吗?接下来带你用五分钟的时间了解 git 的方方面面。Let's go!

一、什么是 git?

虽然 git 很多时候被用来管理代码。但如果抛开代码,git 其实是一个版本控制系统,而且是世界上最先进的版本控制系统。主要体现在三个方面:

因此,git 作为版本控制系统,解决了手动管理文件变更历史的问题;

此外,git 还是一个对象数据库。数据在 git 内部就是通过一系列对象来存储的。具体内容见后文。

二、为什么要用 git

同样是版本控制系统,我们为什么不用其他工具,而使用 git 呢?主要得益于 git 有以下几个特性:

三、git 的使用

1. 相关命令

git 的操作命令从使用程度上可分为上层命令和底层命令。上层命令就是大部分人平时用到的 git add xxxgit commit -m xxxgit 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 更新引用
... ...

2. git-flow

git 是一个分布式的版本控制系统,有非常多的特性,如分支管理、工作区、fast forward 等。但也有不少难以使用的地方:

  1. 如何开发一个新的 feature?对小型项目还好,稍大一点的项目同时开发的 feature 可能达到十几个,这么多 feature 如何管理?
  2. 线上出了 bug 如何快速修复?并且保证之后发布的所有版本都包含这个修复的 commit?
  3. 每次修改代码,重新发布的流程很多很繁琐,如何保证 RD 严格按照规范来操作?

此时 git-flow 出现了,我将它称为「git 工作流」,从字面意思来看,就是按照一个规定好的工作流程来操作,以保证规范性(此处解决了上述第三个问题)。git flow 严格来说是一种思想、机制,git 相关的大部分操作都可以用某些「流」来完成。它不同于许多开发人员惯用的方式。在传统开发模式下,许多人只维护了「master」和「develop」分支,相关的新功能在 develop 上开发,完成后 merge 到 master 分支上,更有甚者只有一个 master 分支。这样的工作模式对于对于并发进行多个 feature 的管理简直是个噩梦。

git flow 设定了五个分支:master、develop、hotfix、feature、release。各个分支通过不同的耦合,形成一个「流」,共同完成代码从编写到上线的一系列流程。各个分支之间的关系如下图:

(图片来自网络)

下面来逐一解释:

由此,git flow 成功将各个分支串起来,形成一个工作流,以便于快速开发迭代。

四、git 实现原理

1. 暂存区

git 本地数据管理,大概可以分为三个区,工作区,暂存区和版本库。

2. 存储结构

git 相关的所有数据均存储在 .git 目录中。我们新建一个仓库,来看一下目录结构:

比较重要的主要是以下几个,分别在后文介绍:

3. 对象

git 内存存在四种对象,分别是 blob、tree、commit 和 tag 对象。每种对象都会生成一个长度为 40 的 sha1 值来标记并存储。

blob 对象

blob 对象,可以类比为 linux 系统上的 inode 节点或具体文件,是真正存储数据的对象。其布局如图所示:!

可见,一个 blob 对象由 header 和 body 组成。其中 header="blob"+数据长度,body 为实际存储的数据。然后对整个对象做 sha1 计算,得到上文所说的 sha1 值。以该值前两位作为目录名,后 38 位作为文件名,存储于 objects 目录下。文件内容为对整个对象做 zlib 压缩后的结果。如下图所示:

从上图可以看出,我们可以通过 git hash-object 命令,将一个文件的内容写入到一个 blob 对象中。但 blob 对象还是存在两个问题:

  1. sha1 哈希值难以记忆,直接给人使用较为困难;
  2. blob 对象中只存储文件内容,无文件名;

因此,就引出了 tree 对象。

tree 对象

如果说 blob 对象,可以类比为 linux 系统上的 inode 节点或具体文件的话,tree 对象就可以类比未 linux 系统上的目录,指向 blob 对象或者子目录对象。其结构如下图所示:

可见,tree 对象的 header 跟 blob 对象较为类似,但其 body 包含好多条记录,每条记录均指向一个子 blob 对象或子 tree 对象,并记录了子对象的文件模式(图中未画出)、类型、sha1 值和文件名。下图演示了如何生成一个 tree 对象并将上文中生成的 blob 对象添加进去:

同样,tree 对象也有一些问题,最大的问题就是:该 tree 对象及其子对象是谁保存的?为什么保存?什么时间保存的?该问题可以由 commit 对象来解决。

commit 对象

commit 对象的 body,包含其所指向的 tree 对象的 sha1 值、父 commit 对象的 sha1 值(如果有)、提交者以及相关说明信息。布局如图所示:

下图演示了如何生成一个 commit 对象:

tag 对象

tag 对象有两种:轻量 tag 对象和附注 tag 对象。

若使用 git tag xxx 命令,生成的为轻量 tag 对象。轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。

而附注 tag 是存储在 git 数据库中的一个完整对象, 它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间等。通过 git tag xxx -m 'yyy' 即可生成一个附注 tag 对象。其布局如下:

4. 引用

git 有三种引用类型:HEAD 引用、标签引用和远程引用。均存储于 .git/refs 目录下。下图演示了 HEAD 引用及标签引用:

可以看出,我们平时所说的分支,其实就是一个引用,对应 .git/refs/heads 下面的一个文件,文件内容为其所指向的 commit 对象指针。另外,也印证了轻量 tag 对象其实就是一个引用。

5. HEAD 引用

.git 目录下面还有一个特殊的 HEAD 文件:

可以看出,该文件内容为 refs/heads/master,指向上文提到的分支引用的路径。因此 HEAD 文件相当于「引用的引用」,也可以理解为「指向指针的指针」,即二级指针,用于记录当前 git 工作在哪一个分支之上。

6. git 组织结构

如图为 git 中各对象、引用之间的关系,可以看出,HEAD 指向引用,引用指向 commit 对象,commit 对象指向 tree 对象,其 parent 指针指向父对象。tree 对象指向 blob 对象,blob 对象中存储具体数据。