git init
使用git init
初始化一個(gè)新的目錄時(shí),會(huì)生成一個(gè).git
的目錄向图,該目錄即為本地倉庫泳秀。一個(gè)新初始化的本地倉庫是這樣的:
├── HEAD
├── branches
├── config
├── description
├── hooks
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
-
description
用于GitWeb程序 -
config
配置特定于該倉庫的設(shè)置(還記得git config的三個(gè)配置級(jí)別么) -
hooks
放置客戶端或服務(wù)端的hook腳本 -
HEAD
傳說中的HEAD指針标沪,指明當(dāng)前處于哪個(gè)分支 -
objects
Git對(duì)象存儲(chǔ)目錄 -
refs
Git引用存儲(chǔ)目錄 -
branches
放置分支引用的目錄
其中description
、config
和hooks
這些不在討論中嗜傅,后文會(huì)直接忽略金句。
git add
Gitcommit
之前先要通過git add
添加文件,這個(gè)操作Git內(nèi)部會(huì)做些什么呢吕嘀?
執(zhí)行如下操作:
- 用
echo "Hello Git" > a.txt
生成一個(gè)a.txt
- 再通過
git add a.txt
添加文件 - 查看
.git
目錄
├── HEAD
├── branches
├── index
├── objects
│ ├── 9f
│ │ └── 4d96d5b00d98959ea9960f069585ce42b1349a
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
可以看到违寞,多了一個(gè)index
文件。并且在objects
目錄下多了一個(gè)9f
的目錄偶房,其中多了一個(gè)4d96d5b00d98959ea9960f069585ce42b1349a
文件趁曼。
其實(shí)9f4d96d5b00d98959ea9960f069585ce42b1349a
就是一個(gè)Git對(duì)象,稱為blob對(duì)象棕洋。
這個(gè)文件名(或者叫對(duì)象名)是怎樣來的呢挡闰?簡(jiǎn)單的說,就是Git會(huì)先生成一個(gè)文件頭拍冠,其中包含這個(gè)對(duì)象的類型(比如blob)和原始文件長度加上一個(gè)空字節(jié)尿这。文件頭再加上原始文件內(nèi)容,然后算出一個(gè)SHA-1
庆杜。這個(gè)SHA-1
有40位射众,前兩位會(huì)用于新建目錄,后38位用于文件名晃财。所以叨橱,完整的對(duì)象名應(yīng)該把上一級(jí)目錄名給包含進(jìn)去的。
可以通過Git的底層命令git cat-file -p
查看其內(nèi)容:
$ git cat-file -p 9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello Git
可以看到断盛,其中的內(nèi)容和a.txt
文件是一模一樣的罗洗。
通過git cat-file -t
查看對(duì)象的類型:
$ git cat-file -t 9f4d96d5b00d98959ea9960f069585ce42b1349a
blob
確實(shí)是blob類型。那index
文件又是什么鬼钢猛?

通過git add
的文件會(huì)先放到Staging Area
(有些書也叫Cached Area)伙菜。而index
文件就是這個(gè)Staging Area
。index
本身是一個(gè)二進(jìn)制文件命迈,有自己專有的存儲(chǔ)格式贩绕,詳情可見。
我們可以通過git ls-files --stage
查看index
文件的內(nèi)容:
$ git ls-files --stage
100644 9f4d96d5b00d98959ea9960f069585ce42b1349a 0 a.txt
小結(jié):git add
命令會(huì)將我們的文件保存成一個(gè)blob對(duì)象
壶愤,然后更新index
文件表明該文件已經(jīng)暫存淑倾。
git commit
通過git commit -m "first commit"
提交,然后再查看.git
目錄:
├── HEAD
├── branches
├── index
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 88
│ │ └── 23efd7fa394844ef4af3c649823fa4aedefec5
│ ├── 91
│ │ └── 0fc16f5cc5a91e6712c33aed4aad2cfffccb73
│ ├── 9f
│ │ └── 4d96d5b00d98959ea9960f069585ce42b1349a
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
-
objects
目錄下多了兩個(gè)對(duì)象8823efd7fa394844ef4af3c649823fa4aedefec5
和910fc16f5cc5a91e6712c33aed4aad2cfffccb73
- 多了一個(gè)
log
目錄征椒,里邊存了好些東西娇哆,先不管它~~
.git/refs/heads
下多了一個(gè)master
文件,可以直接查看:
$ cat .git/refs/heads/master
910fc16f5cc5a91e6712c33aed4aad2cfffccb73
該文件是一個(gè)文本文件,里邊保存著一個(gè)對(duì)象的名稱碍讨。從上文可以看到治力,該對(duì)象是新增加的。查看一下它的類型和內(nèi)容:
$ git cat-file -t 910fc16f5cc5a91e6712c33aed4aad2cfffccb73
commit
$ git cat-file -p 910fc16f5cc5a91e6712c33aed4aad2cfffccb73
tree 8823efd7fa394844ef4af3c649823fa4aedefec5
author yjiyjige <475500230@qq.com> 1472309876 +0800
committer yjiyjige <475500230@qq.com> 1472309876 +0800
first commit
可以看到該對(duì)象的類型是commit
垄开,而它的內(nèi)容包含了另外的一個(gè)對(duì)象的引用(tree
對(duì)象)琴许,還有就是作者信息、提交者信息和提交的日志溉躲。
現(xiàn)在來看看8823efd7fa394844ef4af3c649823fa4aedefec5
這個(gè)對(duì)象:
git cat-file -t 8823efd7fa394844ef4af3c649823fa4aedefec5
tree
$ git cat-file -p 8823efd7fa394844ef4af3c649823fa4aedefec5
100644 blob 9f4d96d5b00d98959ea9960f069585ce42b1349a a.txt
該對(duì)象類型是tree
榜田,而該對(duì)象指向了我們一開始add
生成的那個(gè)blob
對(duì)象,并且保存著文件名锻梳。
接下來執(zhí)行如下操作:
$ mkdir temp
$ echo "Second file" > temp/b.txt
$ git add temp/b.txt
$ git commit -m "second commit"
然后看下.git
目錄的變化:
├── HEAD
├── branches
├── index
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 20
│ │ └── d5b672a347112783818b3fc8cc7cd66ade3008
│ ├── 80
│ │ └── 0910d78c39017816173b00d3a1074800854612
│ ├── 88
│ │ └── 23efd7fa394844ef4af3c649823fa4aedefec5
│ ├── 8e
│ │ └── 19c6677af0c3a80d5e2a3d1c1dffe9934431a5
│ ├── 91
│ │ └── 0fc16f5cc5a91e6712c33aed4aad2cfffccb73
│ ├── 9f
│ │ └── 4d96d5b00d98959ea9960f069585ce42b1349a
│ ├── e8
│ │ └── b5b9a992fe8b5d24b09ef55b97739f35221b1d
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
可以看到多了4個(gè)對(duì)象箭券,但我們先看下.git/refs/heads/master
文件:
$ cat .git/refs/heads/master
800910d78c39017816173b00d3a1074800854612
可以看到引用了一個(gè)新的對(duì)象,再看看這個(gè)對(duì)象是什么:
$ git cat-file -t 800910d78c39017816173b00d3a1074800854612
commit
$ git cat-file -p 800910d78c39017816173b00d3a1074800854612
tree 8e19c6677af0c3a80d5e2a3d1c1dffe9934431a5
parent 910fc16f5cc5a91e6712c33aed4aad2cfffccb73
author yjiyjige <475500230@qq.com> 1472311564 +0800
committer yjiyjige <475500230@qq.com> 1472311564 +0800
second commit
還是一個(gè)commit
對(duì)象疑枯,該對(duì)象又引用了一個(gè)新的tree
對(duì)象辩块,而且有一個(gè)parent
后面跟著的是我們上次提交的commit
對(duì)象【S溃看看所引用的tree
對(duì)象是怎樣的:
$ git cat-file -p 8e19c6677af0c3a80d5e2a3d1c1dffe9934431a5
100644 blob 9f4d96d5b00d98959ea9960f069585ce42b1349a a.txt
040000 tree e8b5b9a992fe8b5d24b09ef55b97739f35221b1d temp
該tree
對(duì)象包含了第一次add的生成的blob
對(duì)象(對(duì)應(yīng)于a.txt
文件)和另一個(gè)tree
對(duì)象废亭。幾乎可以想到,這個(gè)tree
對(duì)象中應(yīng)該包含了一個(gè)blob
對(duì)象具钥,對(duì)應(yīng)于b.txt
文件:
$ git cat-file -p e8b5b9a992fe8b5d24b09ef55b97739f35221b1d
100644 blob 20d5b672a347112783818b3fc8cc7cd66ade3008 b.txt
如果我們把這些對(duì)象的引用關(guān)系豆村,包括master
文件用圖畫出來,大概是這個(gè)樣子:
小結(jié):
-
tree
對(duì)象相當(dāng)于一個(gè)目錄(或者叫文件夾)骂删,其中包含blob
對(duì)象和其他tree
對(duì)象掌动。 - 每一次提交都會(huì)有一個(gè)
commit
對(duì)象,commit
對(duì)象中會(huì)有一個(gè)tree
對(duì)象和一個(gè)指和上一次提交的引用宁玫。 -
master
分支其實(shí)就是一個(gè)引用而已粗恢,指向某一個(gè)提交對(duì)象。
Q&A
怎么理解每次提交都是一個(gè)“快照”
從上文中我們可能看到欧瘪,每一個(gè)commit
對(duì)象所引用的tree
對(duì)象最終可以遞歸得出提交時(shí)的所有的文件眷射,并不是說會(huì)把所有的文件都重新備份一次。而Git在add文件時(shí)佛掖,確實(shí)會(huì)把文件完整地保存成一個(gè)新的blob
對(duì)象妖碉,我們可以驗(yàn)證:
$ echo "Third" > a.txt
$ git add a.txt
$ git commit -m "third commit"
會(huì)多幾個(gè)對(duì)象呢?
├── HEAD
├── branches
├── index
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 16
│ │ └── df5eafaccb32649a890005b3f693fed266fc3d
│ ├── 20
│ │ └── d5b672a347112783818b3fc8cc7cd66ade3008
│ ├── 56
│ │ └── 9f012efac9a65ee515e488e244b89cbe795d6e
│ ├── 80
│ │ └── 0910d78c39017816173b00d3a1074800854612
│ ├── 88
│ │ └── 23efd7fa394844ef4af3c649823fa4aedefec5
│ ├── 8e
│ │ └── 19c6677af0c3a80d5e2a3d1c1dffe9934431a5
│ ├── 91
│ │ └── 0fc16f5cc5a91e6712c33aed4aad2cfffccb73
│ ├── 9f
│ │ ├── 4d96d5b00d98959ea9960f069585ce42b1349a
│ │ └── 7da334be98d63c78ccf1e94414b0664e649e5f
│ ├── e8
│ │ └── b5b9a992fe8b5d24b09ef55b97739f35221b1d
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
多了三個(gè)對(duì)象苦囱,直接通過master
一步步看:
$ cat .git/refs/heads/master
569f012efac9a65ee515e488e244b89cbe795d6e
$ git cat-file -p 569f012efac9a65ee515e488e244b89cbe795d6e
tree 9f7da334be98d63c78ccf1e94414b0664e649e5f # 新的tree對(duì)象
parent 800910d78c39017816173b00d3a1074800854612
author yjiyjige <475500230@qq.com> 1472317420 +0800
committer yjiyjige <475500230@qq.com> 1472317420 +0800
third commit
$ git cat-file -p 9f7da334be98d63c78ccf1e94414b0664e649e5f
100644 blob 16df5eafaccb32649a890005b3f693fed266fc3d a.txt # 文件名一樣嗅绸,但blob對(duì)象已經(jīng)不一樣了
040000 tree e8b5b9a992fe8b5d24b09ef55b97739f35221b1d temp # 和上次的tree對(duì)象是一樣的
$ git cat-file -p 16df5eafaccb32649a890005b3f693fed266fc3d
Third
$ git cat-file -p 9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello Git
# 可以看到老blob對(duì)象還在
可以發(fā)現(xiàn)脾猛,新生成一個(gè)tree
對(duì)象撕彤,指向了一個(gè)新的blob
對(duì)象(還是對(duì)應(yīng)于a.txt
)只不過內(nèi)容變了。原來的temp
目錄對(duì)應(yīng)的tree
對(duì)象沒有變化,所以直接引用羹铅。
等等蚀狰,如果每次修改都保存一個(gè)完整的文件,那倉庫不是很快就變得巨大职员?
理論上來說麻蹋,每次修改只需要保存這個(gè)文件diff
就行了,但那樣就實(shí)現(xiàn)不了Git這么優(yōu)雅的設(shè)計(jì)了焊切。Git是通過“打包”來實(shí)現(xiàn)的扮授。我們調(diào)用git gc
,然后看下倉庫的文件:
├── HEAD
├── branches
├── index
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── info
│ │ └── packs
│ └── pack
│ ├── pack-b25e184d1a96e5f1bde09c941be14cbe2cdb1289.idx
│ └── pack-b25e184d1a96e5f1bde09c941be14cbe2cdb1289.pack
├── packed-refs
└── refs
├── heads
└── tags
WTFWǚ尽I膊!所有對(duì)象都不見了嚎尤!甚至master
都不見了荔仁!
莫方,我們看看packed-refs
是什么:
$ cat packed-refs
# pack-refs with: peeled fully-peeled
569f012efac9a65ee515e488e244b89cbe795d6e refs/heads/master
看來至少master
還是在的芽死。再通過git verify-pack -v
看看.idx
文件是什么東西:
$ git verify-pack -v objects/pack/pack-b25e184d1a96e5f1bde09c941be14cbe2cdb1289.idx
569f012efac9a65ee515e488e244b89cbe795d6e commit 215 147 12
800910d78c39017816173b00d3a1074800854612 commit 216 148 159
910fc16f5cc5a91e6712c33aed4aad2cfffccb73 commit 167 117 307
16df5eafaccb32649a890005b3f693fed266fc3d blob 6 15 424
20d5b672a347112783818b3fc8cc7cd66ade3008 blob 12 21 439
9f7da334be98d63c78ccf1e94414b0664e649e5f tree 64 75 460
e8b5b9a992fe8b5d24b09ef55b97739f35221b1d tree 33 44 535
8e19c6677af0c3a80d5e2a3d1c1dffe9934431a5 tree 64 75 579
9f4d96d5b00d98959ea9960f069585ce42b1349a blob 10 19 654
8823efd7fa394844ef4af3c649823fa4aedefec5 tree 33 44 673
non delta: 10 objects
objects/pack/pack-b25e184d1a96e5f1bde09c941be14cbe2cdb1289.pack: ok
原來.idx
文件記錄了之前的所有對(duì)象乏梁,而現(xiàn)在的數(shù)據(jù)保存在了.pack
文件中。通過.idx
文件記錄的起始值关贵、文件長度這些信息就可以把原有的對(duì)象提取出來了遇骑。如果文件相似,其實(shí)是會(huì)保留新版本坪哄,而老版本保留diff的形式存在质蕉!
回到“快照”這個(gè)概念,Git在底層做了臟活翩肌,只要通過當(dāng)時(shí)提交的文件對(duì)應(yīng)的blob
對(duì)象引用模暗,就可以還原出原始文件。所以念祭,從用戶角度兑宇,blob
文件相當(dāng)于原始文件。
$ git cat-file -p 9f4d96d5b00d98959ea9960f069585ce42b1349a
Hello Git
這部分不好理解粱坤,甚至很多書都會(huì)直接說“Git保留文件快照隶糕,而其他VCS是保存diff”。其實(shí)Git底層也會(huì)保存diff的站玄,只不過我們感覺不到diff的存在而已枚驻。
關(guān)于打包這部分,詳細(xì)請(qǐng)見Pro git株旷。
未完再登,可能會(huì)續(xù)~