Git 之术与道 -- 对象

庖丁为文惠公解牛,游刃有余。
文惠公曰:“善哉,技盖至此乎?”
庖丁释刀对曰:“臣之所好者道也,进乎技矣。”

-- 庄子

你已经见识过 Git 的威力,正是因为 Git,使得社区协作变得如此简单易行。也许你会认为,强大功能的背后,是一套复杂艰涩的抽象模型。然而,强大并不意味着复杂,越是优雅的程序,往往也越是高效。事实上,Git 作为眼下最为流行的版本管理工具,所依托的是一组至为简洁的数据结构,简洁到只需要很短的篇幅就能够把其中的核心概念讲解清楚。

Git 维护着一个微型的文件系统,其中的文件也被称作数据对象。所有的数据对象均存储于项目下面的 .git/objects
目录中。

例如,在项目 dota-game 中,创建一个 README 文件并且添加到版本库中:

$ git init dota-game && cd dota-game
$ echo -n "42 is the answer to life the universe and everything." > README
$ git add README

此时,我们看到,Git 已经把这个文件记录在案:

$ find .git/objects -type f
.git/objects/81/f41231377346156ef312dffb6716c88826b97c

这样的一个数据对象,被称作 Blob 对象。我们可以通过下面的命令把文件内容重新打捞回来:

$ git cat-file -p 81f41242 is the answer to life the universe and everything.

版本库中的每一个文件,不论是图片、源文件还是二进制文件,都被映射为一个 Blob 对象。除了 Blob 对象,在 Git 的文件系统中还存储着另外三种数据对象:Tree 对象,Commit 对象和 Tag 对象。

Blob 对象

Blob 是英文 Binary large object 的缩写,一个 Blob 对象就是一段二进制数据。

让我们添加另一个文件到版本库中:

$ echo -n "print 'PHP is the best language in the universe.'" > main.py
$ git add main.py
$
$ find .git/objects -type f
.git/objects/64/fe72272a79bff953d7de2062d3f52b4679c659    *
.git/objects/81/f41231377346156ef312dffb6716c88826b97c

通过下面的命令查看数据对象的类型:

$ git cat-file -t 64fe72
blob

为了把文件映射为 Blob 对象,Git 做了下面这些工作:

  1. 读取文件内容,添加一段特殊标记到头部,得到新的内容,记为 content;
  2. 对该 content 执行 SHA-1 加密,得到一个长度为40字符的 hash 值,例如 64fe72272a79bff953d7de2062d3f52b4679c659;
  3. 取该 hash 值的前两位作为子目录,剩下的38位作为文件名,在本例中,子目录名是'64/',文件名是'fe72272a79bff953d7de2062d3f52b4679c659';
  4. 对 content 执行 zip 压缩,得到新的二进制内容,存入文件中。

这段 Python 代码帮助我们理解整个过程:

import hashlib
import zlib

src = open('README', 'r')
file_content = src.read()    # 42 is the answer to life the universe and everything.
src.close()

# 添加特殊标记到内容的头部
new_content = 'blob %u\0%s' % (len(file_content), file_content)

# 对内容执行 SHA-1 加密
sha1 = hashlib.sha1()
sha1.update(new_content)
hash_str = sha1.hexdigest()  # 81f41231377346156ef312dffb6716c88826b97c

# 对内容执行 zip 压缩
compressed_content = zlib.compress(new_content)

# 存储
dst = open('.git/objects/%s/%s' % (hash_str[:2], hash_str[2:]), 'wb+):
dst.write(compressed_content)
dst.close()

Tree 对象

Git 使用一种与 UNIX 文件系统相似的方式来管理内容,Blob 相当于磁盘文件,Tree 则相当于文件夹。Tree 中既可以包含 Blob,也可以包含其他 Tree。

向版本库中提交当前的修改:

$ git commit -m "first commit"
$
$ find .git/objects -type f
.git/objects/2b/afd8d408af85faf951445e3aea7d7f874cb806    *
.git/objects/64/fe72272a79bff953d7de2062d3f52b4679c659
.git/objects/81/f41231377346156ef312dffb6716c88826b97c
.git/objects/e5/526c066cdb2b17fc37ba2f2f44cdaca86b7bf2    *

.git/objects 目录下面多出了两个对象,这两个对象的类型分别是 commit 和 tree:

$ git cat-file -t 2bafd8
commit
$
$ git cat-file -t e5526c
tree

下文会讲到 Commit 对象,暂且先不管它。查看 e5526c 这个对象的内容:

$ git cat-file -t e5526c
100644 blob 81f41231377346156ef312dffb6716c88826b97c    README
100644 blob 64fe72272a79bff953d7de2062d3f52b4679c659    main.py

可见这颗树就相当于项目的根目录。

添加另一个文件 src/hero.py 到版本库中:

$ mkdir src
$ echo -n "print 'hero'" > src/hero.py
$ git add src/hero.py
$ git commit -m "second commit"
$
$ find .git/objects -type f
.git/objects/24/6474cab5a5019936a54041ccdddd07398cdf94    *
.git/objects/2b/afd8d408af85faf951445e3aea7d7f874cb806
.git/objects/57/e44b9798892d4ac1b63963d7e6a5653dddde7e    *
.git/objects/64/fe72272a79bff953d7de2062d3f52b4679c659
.git/objects/81/f41231377346156ef312dffb6716c88826b97c
.git/objects/bc/6f978c49b6a6f1190fdb25eabba78494e2606b    *
.git/objects/c5/cbfa0f491087c575d8856632451f8d8763b94f    *
.git/objects/e5/526c066cdb2b17fc37ba2f2f44cdaca86b7bf2

现在,版本库中又多出来4个对象:24647457e44bbc6f97 以及 c5cbfa。除去 c5cbfa2bafd8 两个 commit 对象之外,其他对象的关系如下图所示:

Commit 对象

一个 Commit 对象代表了一次提交对象,它包含了下面这些信息:

  • 何人何时作了该次提交
  • 该次提交的简略说明
  • 一棵树
  • 父级 Commit 对象

其中,这颗树也被称作项目快照(snapshort),通过项目快照,我们可以把项目还原成项目在该次提交时的样子。一般来说,commit 对象总有一个父级 commit 对象,一个又一个 commit 对象通过这种方式链接起来,就构成了一条提交历史。第一次提交的 commit 对象没有父级 commit 对象,分支合并所产生的新的 commit 对象可以有两个或者多个父级 commit 对象。

例如,c5cbfa 这个对象的内容为:

$ git cat-file -p c5cbfa
tree 57e44b9798892d4ac1b63963d7e6a5653dddde7e
parent 2bafd8d408af85faf951445e3aea7d7f874cb806
author xxx <xxx@gmail.com> 1434966496 +0800
committer xxx <xxx@gmail.com> 1434966496 +0800

second commit

经过两次提交之后,版本库中所有对象的关系如下图所示:

Tag 对象

Tag 指向一次特征提交。

在 Git 中有两种 tag,第一种 tag 并不在 .git/objects 目录下面创建新的对象,只是在 .git/refs/tags 目录中新建一个文件,文件的内容就是所指向的 commit 对象的 hash 值:

$ git tag v1.0
$
$ find .git/refs/tags -type f
v1.0
$
$ cat .git/refs/tags/v1.0
c5cbfa0f491087c575d8856632451f8d8763b94f

另一种 tag 则会在 .git/objects 目录下面创建对象,这种 tag 被称作注解标签(annotated tag):

$ git tag -a v1.0 -m "Version 1.1"
$
$ find .git/objects -type f
.git/objects/24/6474cab5a5019936a54041ccdddd07398cdf94
.git/objects/2b/afd8d408af85faf951445e3aea7d7f874cb806
.git/objects/57/e44b9798892d4ac1b63963d7e6a5653dddde7e
.git/objects/64/fe72272a79bff953d7de2062d3f52b4679c659
.git/objects/81/f41231377346156ef312dffb6716c88826b97c
.git/objects/bc/6f978c49b6a6f1190fdb25eabba78494e2606b
.git/objects/c5/cbfa0f491087c575d8856632451f8d8763b94f
.git/objects/e5/526c066cdb2b17fc37ba2f2f44cdaca86b7bf2
.git/objects/ec/7ed5c26520dd5d16b5189b6fbc7914c56b081a    *

git cat-file 命令同样可以用在 tag 对象上面:

$ git cat-file -t ec7ed5
tag
$
$ git cat-file -p ec7ed5
object c5cbfa0f491087c575d8856632451f8d8763b94f
type commit
tag v1.1
tagger xxx <xxx@gmail.com> 1434970701 +0800

Version 1.1

总结

在 Git 的底层,有四种数据结构,它们分别是:

  • Blob
  • Tree
  • Commit
  • Tag

Git 把版本库中的每一个文件都转换为一个 blob 对象进行存储,而用 tree 对象来表达文件的层次结构。

Commit 对象代表了一次提交操作,它包含了当前的项目快照以及提交人和提交日期等诸多信息。所有的 commit 对象串接起来,组成一个有向无环图。从版本控制的角度看,这些 commit 对象构成了一个完整的版本提交记录;从项目开发的角度看,它们描述了项目是如何从无到有一点一滴地构建起来的。

Tag 对象指向一个 commit 对象,我们可以通过 tag 对象快速访问到项目的某一次特征提交。

敬请期待笔者的下一篇文章:《Git 之术与道 -- 索引》。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容