原文地址:http://shellhue.github.io/2017/03/22/gitcommit/
深入理解 git
中 commit
和 branch
,有助于在日常开发中,更好的运用 git
。
commit的本质
初始化目录
为便于讲解,先创建一个如下目录结构的 BeiLiao
(贝聊是我所在的公司)目录:
在命令行中输出文件的目录结构如下:
~ qingmo$: tree
.
└── BeiLiao
├── Component
│ └── App.js
├── index.html
└── index.js
如果以竖向文件层级树的样式看是这样的(深色表示文件目录,浅色表示文件),
其中 App.js
、 index.html
和 index.js
都填充了相应的内容。
初始化git并提交第一个commit
先初始化 git
,然后添加所有文件到 git
的暂存区,成功后提交一个 commit
,最后 log
一下版本历史记录:
~ 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
,形成新的文件目录结构:
~ qingmo$: tree
.
└── BeiLiao
├── Component
│ ├── App.js
│ └── Footer.js
├── index.html
└── index.js
然后添加一个新的提交
~ qingmo$: git add .
~ qingmo$: git commit -m “add footer”
此时生成的 object
引用图表如下图:
可以看出,新添加的 Footer.js
生成了一个快照 efdfb8
,其所在的文件夹 Component
也因新添加的文件而产生了内容的变化,因此也生成了新的快照 017bde
,同样的根文件夹 BeiLiao
也生成了新的快照 3d7c8c
。最终的形成新的commit d34903
指向最新的根文件 BeiLiao
的快照 3d7c8c
。
需要注意的是,只有变化的文件或文件夹才会形成新的快照,没有变化的文件不会形成新的快照。
一个branch是什么
branch
的信息记录在 .git/refs/heads/
目录下,
用文本编辑器打开 master
文件,内容是:
d34903acaba24d2eccc8f5227ca61e6b0f7bd783
这是最新 commit
的键值,所以 branch
仅仅是指向一个 commit
的指针而已,指向一个 commit
,而一个 commit
同时指向其父 commit
,如此循环最终形成了一个 branch
。
HEAD指针
那 git
是怎么知道项目在 master
分支上呢? HEAD
指针。 git
有一个独立的 HEAD
指针,记录项目现在所在的位置,比如现在我们在 master
分支上,查看 .git/HEAD
文件,内容为:
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 object
的 commit
类型 object
作为项目的快照。
branch
其实只是指向一个 commit
的指针而已, HEAD
记录了当先项目的位置。