這里我們要看一下:Git 的客戶端和服務(wù)器如何交互傳輸數(shù)據(jù)。
通過 http 協(xié)議的 url 進(jìn)行的 git 數(shù)據(jù)抓取,使用了一個(gè)比較傻瓜化(dumber)的協(xié)議。
使用 http 協(xié)議,所有的邏輯計(jì)算(logic)都是在客戶端進(jìn)行。服務(wù)器不需要特別的設(shè)置,你只要把 git 目錄放到一個(gè)可以訪問的 web 目錄即可。
為了能通過 http 訪問,當(dāng)你的倉庫有任何更新時(shí),需要運(yùn)行一個(gè)命令:git update-server-info。因?yàn)?web 服務(wù)器一般不允許執(zhí)行列出目錄中文件的操作,所以 git update-server-info 命令把可用的打包文件(packfile)和引用(refs)列表更新到‘objects/info/packs’,info/refs 這個(gè)兩個(gè)文件中。當(dāng) git update-server-info 執(zhí)行后,"objects/info/packs"文件看起來就會(huì)像下面一樣:
P pack-ce2bd34abc3d8ebc5922dc81b2e1f30bf17c10cc.pack
P pack-7ad5f5d05f5e20025898c95296fe4b9c861246d8.pack
如果在通過 http 協(xié)議拉取數(shù)據(jù)的過程中找不到松散文件(loose file),git 就會(huì)去嘗試查找打包文件(packfiles)。 "info/refs" 文件的內(nèi)容看起來就下面這樣:
184063c9b594f8968d61a686b2f6052779551613 refs/heads/development
32aae7aef7a412d62192f710f2130302997ec883 refs/heads/master
當(dāng)你從這個(gè)倉庫開始抓取(fetch)數(shù)據(jù)時(shí),git 就會(huì)從這些引用(refs)開始遍歷查找所有的提交對(duì)象(commit objects),直到客戶端得到了它所有需要的所有對(duì)象為止。
例如,你要抓取到(fetch)服務(wù)器上的"master"分支;git 看到服務(wù)器上的"master"分支指向 32aae7ae,而你當(dāng)前的"master"分支是指向 ab04d88。那么很明顯,你需要得到 32aae7ae 這個(gè)對(duì)象。
下面就是抓取時(shí)的交互過程(http協(xié)議層):
CONNECT http://myserver.com
GET /git/myproject.git/objects/32/aae7aef7a412d62192f710f2130302997ec883 - 200
然后返回信息看起來就像下面這樣:
tree aa176fb83a47d00386be237b450fb9dfb5be251a
parent bd71cad2d597d0f1827d4a3f67bb96a646f02889
author Scott Chacon <schacon@gmail.com> 1220463037 -0700
committer Scott Chacon <schacon@gmail.com> 1220463037 -0700
added chapters on private repo setup, scm migration, raw git
好的那么現(xiàn)在它就是開始抓取樹對(duì)象(tree) aa176fb8:譯者注:32aae7ae 提交對(duì)象(commit object)指向的樹對(duì)象(tree)是:aa176fb8。
GET /git/myproject.git/objects/aa/176fb83a47d00386be237b450fb9dfb5be251a - 200
下面這些是返回的樹對(duì)象(tree)信息:
100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3 COPYING
100644 blob 97b51a6d3685b093cfb345c9e79516e5099a13fb README
100644 blob 9d1b23b8660817e4a74006f15fae86e2a508c573 Rakefile
很明顯,樹對(duì)象(tree)里有 3 個(gè)文件(blob),我們就把它們抓下來吧:
GET /git/myproject.git/objects/6f/f87c4664981e4397625791c8ea3bbb5f2279a3 - 200
GET /git/myproject.git/objects/97/b51a6d3685b093cfb345c9e79516e5099a13fb - 200
GET /git/myproject.git/objects/9d/1b23b8660817e4a74006f15fae86e2a508c573 - 200
這些 http 下載操作實(shí)際上是由 curl 來完成的,我們可以開多個(gè)并行的線程來加快下載速度。Git 遍歷完提交對(duì)象(commit)所指向的樹對(duì)象(tree)后,就會(huì)開始抓取提交對(duì)象(commit)的父對(duì)象(next parent)。
GET /git/myproject.git/objects/bd/71cad2d597d0f1827d4a3f67bb96a646f02889 - 200
返回的父對(duì)象(parent commit object)信息就如下面所示:
tree b4cc00cf8546edd4fcf29defc3aec14de53e6cf8
parent ab04d884140f7b0cf8bbf86d6883869f16a46f65
author Scott Chacon <schacon@gmail.com> 1220421161 -0700
committer Scott Chacon <schacon@gmail.com> 1220421161 -0700
added chapters on the packfile and how git stores objects
我們現(xiàn)在可以看到 ab04d88 是返回的對(duì)象(commit)的父對(duì)象,而 ab04d88(commit)就是我們當(dāng)前的‘master’分支。那么我們只需要得到樹對(duì)象(tree):b4cc00c 就可以了,因?yàn)橹暗乃缘奶峤?commit)我們都有了。為了保險(xiǎn)起見,你也可以加上--recover
參數(shù),強(qiáng)制 git 反復(fù)檢查我們是否擁有所有的對(duì)象。你可以點(diǎn)這里:git http-fetch 查看更多信息:
如果有一個(gè)松散對(duì)象(loose object)下載失敗了, git會(huì)下載打包文件索引(packfile indexes), 通過它來查找對(duì)應(yīng)的 sha 串值,然后再下載對(duì)應(yīng)的打包文件(packfile)。
你一定要在 git 服務(wù)器的倉庫里添一個(gè)“post-receive”鉤子(hook), 這個(gè)鉤子(hook)會(huì)在倉庫更新后執(zhí)行git update-server-info
; 否則倉庫的相關(guān)信息就得不到更新。
對(duì)于一個(gè)聰明的協(xié)議,抓取對(duì)象的過程(fetching objects)應(yīng)當(dāng)更加高效。不管是用通過 ssh 協(xié)議還是 git 協(xié)議(git:// 協(xié)議,在 9418 端口上運(yùn)行), 當(dāng)客戶端和服務(wù)器建立了一個(gè) socket 連接后,客戶端開始運(yùn)行:git fetch-pack
命令,和服務(wù)器創(chuàng)建(fork)的linkgit:git update-pack
進(jìn)行通訊。
服務(wù)器會(huì)告訴客戶端它每個(gè)引用(ref)所有擁有的 SHA 串值,而客戶端會(huì)以它所需要的和所擁有 SHA 串值作為回應(yīng)。
這里,服務(wù)器會(huì)把客戶端需要的所有對(duì)象打一個(gè)包(packfile),然后再傳送給客戶端。
讓我們來看一個(gè)例子。
客戶端連接并且發(fā)送請(qǐng)求頭(request header)。例如,克隆命令:
$ git clone git://myserver.com/project.git
上面的命令會(huì)產(chǎn)生下面的請(qǐng)求:
0032git-upload-pack /project.git\000host=myserver.com\000
每行的最前面的 4 個(gè)字節(jié)表示此行的 16 進(jìn)行制長(zhǎng)度(hex length) (包括這個(gè) 4 個(gè)字節(jié),但不包括換行符)。 下面接著的是命令和參數(shù),這之后是一個(gè) null 字節(jié)(#body00
)和主機(jī)信息。請(qǐng)求的結(jié)尾是以 null 字節(jié)(\000
)結(jié)束的。
這個(gè)請(qǐng)求被服務(wù)器接收并且轉(zhuǎn)換成對(duì)"git-upload-pack"的命令調(diào)用。
$ git-upload-pack /path/to/repos/project.git
這條命令會(huì)馬上返回倉庫的信息:
007c74730d410fcb6603ace96f1dc55ea6196122532d HEAD\000multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress
003e7d1665144a3a975c05f1f43902ddaf084e784dbe refs/heads/debug
003d5a3f6be755bbb7deae50065988cbfa1ffa9ab68a refs/heads/dist
003e7e47fe2bd8d01d481f44d7af0531bd93d3b21c01 refs/heads/local
003f74730d410fcb6603ace96f1dc55ea6196122532d refs/heads/master
0000
每一行開始的頭 4 個(gè)字節(jié)表示此行的長(zhǎng)度(以 16 進(jìn)制表示)。這塊(section)信息以一行“0000”為結(jié)束標(biāo)識(shí)符。
上面這些服務(wù)器產(chǎn)生的數(shù)據(jù)被發(fā)送回客戶端。然后客戶端用另外一個(gè)請(qǐng)求做為響應(yīng):
0054want 74730d410fcb6603ace96f1dc55ea6196122532d multi_ack side-band-64k ofs-delta
p 0032want 7d1665144a3a975c05f1f43902ddaf084e784dbe
0032want 5a3f6be755bbb7deae50065988cbfa1ffa9ab68a
0032want 7e47fe2bd8d01d481f44d7af0531bd93d3b21c01
0032want 74730d410fcb6603ace96f1dc55ea6196122532d
00000009done
上面這些客戶端的請(qǐng)求會(huì)被發(fā)送到的git-upload-pack
進(jìn)程,這個(gè)進(jìn)程會(huì)返回(streams out)最終的結(jié)果(final response):
"0008NAK\n"
"0023\002Counting objects: 2797, done.\n"
"002b\002Compressing objects: 0% (1/1177) \r"
"002c\002Compressing objects: 1% (12/1177) \r"
"002c\002Compressing objects: 2% (24/1177) \r"
"002c\002Compressing objects: 3% (36/1177) \r"
"002c\002Compressing objects: 4% (48/1177) \r"
"002c\002Compressing objects: 5% (59/1177) \r"
"002c\002Compressing objects: 6% (71/1177) \r"
"0053\002Compressing objects: 7% (83/1177) \rCompressing objects: 8% (95/1177) \r"
...
"005b\002Compressing objects: 100% (1177/1177) \rCompressing objects: 100% (1177/1177), done.\n"
"2004\001PACK\000\000\000\002\000\000\n\355\225\017x\234\235\216K\n\302"...
"2005\001\360\204{\225\376\330\345]z2673"...
...
"0037\002Total 2797 (delta 1799), reused 2360 (delta 1529)\n"
...
"<\276\255L\273s\005\001w0006\001[0000"
你可以查看“打包文件”這一章,了解響應(yīng)內(nèi)容中的打包文件(packfile)的格式。
通過 git 和 ssh 協(xié)議推送數(shù)據(jù)(pushing data)是相似的,但是更簡(jiǎn)單?;旧鲜?,客戶端發(fā)出一個(gè)“receive-pack”的請(qǐng)求,如果客戶端有訪問權(quán)限,那么服務(wù)器就返回所有引用“頭”的 SHA 串值(all ref head shas)??蛻舳耸盏巾憫?yīng)后,計(jì)算出服務(wù)器需要的所有數(shù)據(jù)或?qū)ο?,再做成一個(gè)打包文件(packfile)傳送給服務(wù)器。服務(wù)器收到后要么就把它們存儲(chǔ)到硬盤上再建立索引,要么只把它解壓(如果里面的對(duì)象不多的話)。
在這整個(gè)推送數(shù)據(jù)的過程中,客戶端通過 git push 命令調(diào)用:git sendpack 命令,服務(wù)器端通過“ssh 連接進(jìn)程”或是“git 服務(wù)器”來調(diào)用:linkgit:git-receive-pack
命令來完成整個(gè)操作。