Git命令的背后

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 放置分支引用的目錄

其中descriptionconfighooks這些不在討論中嗜傅,后文會(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 Areaindex本身是一個(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ì)象8823efd7fa394844ef4af3c649823fa4aedefec5910fc16f5cc5a91e6712c33aed4aad2cfffccb73
  • 多了一個(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ù)~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尔邓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锉矢,更是在濱河造成了極大的恐慌梯嗽,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沽损,死亡現(xiàn)場(chǎng)離奇詭異灯节,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)绵估,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門炎疆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人国裳,你說我怎么就攤上這事磷雇。” “怎么了躏救?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵唯笙,是天一觀的道長。 經(jīng)常有香客問我盒使,道長崩掘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任少办,我火速辦了婚禮苞慢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘英妓。我一直安慰自己挽放,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布蔓纠。 她就那樣靜靜地躺著辑畦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腿倚。 梳的紋絲不亂的頭發(fā)上纯出,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音敷燎,去河邊找鬼暂筝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛硬贯,可吹牛的內(nèi)容都是我干的焕襟。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼饭豹,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼鸵赖!你這毒婦竟也來了畏吓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤卫漫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后肾砂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體列赎,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年镐确,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了包吝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡源葫,死狀恐怖诗越,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情息堂,我是刑警寧澤嚷狞,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站荣堰,受9級(jí)特大地震影響床未,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜振坚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一薇搁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渡八,春花似錦啃洋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逮壁,卻和暖如春绝编,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背貌踏。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來泰國打工十饥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祖乳。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓逗堵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眷昆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜒秤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 本片內(nèi)容轉(zhuǎn)自CSDN http://blog.csdn.net/ithomer/article/details/7...
    五娃兒閱讀 4,934評(píng)論 2 88
  • 職業(yè)選擇 批評(píng)家和導(dǎo)演不是一回事汁咏,絕大多數(shù)批評(píng)家都拍不出電影,也不能設(shè)計(jì)出美觀的界面或者好用的產(chǎn)品作媚。 分析能力有兩...
    Savian閱讀 252評(píng)論 0 0
  • M最近陷于關(guān)于人到中年的苦悶中攘滩,今天的他跟我分享的了他的瓶頸。在公司這樣的單位工作纸泡,壓力不算大漂问,待遇還可以,別人眼...
    默默然然閱讀 268評(píng)論 1 0
  • 走在沙漠里女揭,看不到希望和路蚤假,很容易迷茫。 走在岔路口吧兔,前頭有太多路磷仰,也容易迷茫。 走在岔路口境蔼,前頭的路不多灶平,但是向...
    Walter所羅貓閱讀 585評(píng)論 0 5