commit和branch理解深入

深入理解gitcommitbranch,有助于在日常开发中,更好的运用git

commit的本质

初始化目录

为便于讲解,先创建一个如下目录结构的BeiLiao(贝聊是我所在的公司)目录:

在命令行中输出文件的目录结构如下:

1
2
3
4
5
6
7
~ qingmo$: tree
.
└── BeiLiao
├── Component
│   └── App.js
├── index.html
└── index.js

如果以竖向文件层级树的样式看是这样的(深色表示文件目录,浅色表示文件),

其中App.jsindex.htmlindex.js都填充了相应的内容。

初始化git并提交第一个commit

先初始化git,然后添加所有文件到git的暂存区,成功后提交一个commit,最后log一下版本历史记录:

1
2
3
4
5
6
7
8
9
10
11
~ qingmo$: cd ~/../BeiLiao(切换到BeiLiao这个文件目录)
~ qingmo$: git init
~ qingmo$: git add .
~ qingmo$: git commit -m “init project”
~ qingmo$: git log

commit 5bb33508dd4f00dd4d63c6e28546e044ebb51ac0
Author: qingmo <qingmo@163.com>
Date: Sun Mar 26 15:18:01 2017 +0800

init project

完成上述我们开发中的日常操作后,git便开始成功追踪BeiLiao整个文件夹,很炫很酷,但问题是git到底是如何做到的,而我们日常开发熟悉不能再熟悉的commit的本质又是什么?

其实当我们运行git commit -m “init project”命令时,git主要进行了三个操作。

  • 为每一个文件生成一个快照
    每一个文件其实是真的数据,所以git会把整个文件内容转成二进制,然后经过压缩直接存在键值对数据库中,对应的键值就是文件中的内容再附加一些头信息的40位校验和sha-1。既然是真数据,所以文件快照的类型为blob类型(binary large object)即大型二进制对象类型,

  • 为每一个文件夹生成一个快照
    文件夹并不是直接的文字数据,其主要记录的是文件夹的结构和每个文件或者文件夹所对应的快照键值,所以文件夹的快照内容主要是其包含的所有文件和文件夹的键值信息总和,附加一些头信息,如文件名,文件夹名。对应快照键值为快照内容的40位校验和sha-1。既然不是直接数据,数据类型与文件快照必然不同,文件夹快照对应的类型为tree类型。

  • 生成一个项目快照
    也即生成一个commit,项目快照的内容主要包含四部分信息,根项目目录的快照、提交人信息、项目快照说明(即commit信息)和父项目快照。其中项目文件快照,只要根目录即BeiLiao的目录快照即可。项目快照commit的键值为项目快照内容的40位校验和sha-1。项目快照类型为commit类型。

git commit -m “init project”后,生成的object引用图表如下图(浅灰色的表示文件快照,为blob类型,深蓝色的表示文件夹快照,为tree类型,绿色的表示项目快照,为commit类型):

git中生成的所有object都存在.git/objects/文件夹内,每一个object保存时,取其40位校验和sha-1的前两位生成文件夹,后38位作为文件名,存储对应的数据。git commit -m “init project”后,git的.git/objects/文件夹内容如下图:

下图把.git/objects/中的每一个object对应的内容文件一一标注了出来,可以很清晰的看到每一个数据object到底是什么。

添加文件并提交第二个commit

Component文件夹添加一个新文件Footer.js,形成新的文件目录结构:

1
2
3
4
5
6
7
8
~ qingmo$: tree
.
└── BeiLiao
├── Component
│   ├── App.js
│   └── Footer.js
├── index.html
└── index.js

然后添加一个新的提交

1
2
~ qingmo$: git add .
~ qingmo$: git commit -m “add footer”

此时生成的object引用图表如下图:

可以看出,新添加的Footer.js生成了一个快照efdfb8,其所在的文件夹Component也因新添加的文件而产生了内容的变化,因此也生成了新的快照017bde,同样的根文件夹BeiLiao也生成了新的快照3d7c8c。最终的形成新的commitd34903指向最新的根文件BeiLiao的快照3d7c8c

需要注意的是,只有变化的文件或文件夹才会形成新的快照,没有变化的文件不会形成新的快照。

一个branch是什么

branch的信息记录在.git/refs/heads/目录下,

用文本编辑器打开 master文件,内容是:

1
d34903acaba24d2eccc8f5227ca61e6b0f7bd783

这是最新commit的键值,所以branch仅仅是指向一个commit的指针而已,指向一个commit,而一个commit同时指向其父commit,如此循环最终形成了一个branch

HEAD指针

git是怎么知道项目在master分支上呢?HEAD指针。git有一个独立的HEAD指针,记录项目现在所在的位置,比如现在我们在master分支上,查看.git/HEAD文件,内容为:

1
ref: refs/heads/master

此时HEAD指针指向master,所以项目在master分支上。

当我们创建一个新的分支test时,git会在.git/refs/heads/目录下生成一个文件test,并将其指向当前HEAD所指向的分支master所指向的提交d34903(有点绕),并把HEAD指向新的分支test

当我们在新的分支生成新的commit时,git会将HEAD所指向的分支test所指向的commit作为新commit的父commit,然后将HEAD所指向的分支test移动指向新的提交。

可以看出HEAD指针在整个git中的重要性,没有HEAD,就不知道当前项目的位置,就不会生成新的提交,也不会创建新的分支(除非用git的底层命令)。

总结

git中每一个文件会生成一个blob类型的object记录这个文件的状态,每一个文件夹都会生成一个tree类型的object就录这个文件夹的状态,一个项目会生成一个指向根目录tree objectcommit类型object作为项目的快照。

branch其实只是指向一个commit的指针而已,HEAD记录了当先项目的位置。

分享到 评论