Git 傳輸協(xié)議

2018-09-27 15:52 更新

Git 可以以兩種主要的方式跨越兩個(gè)倉庫傳輸數(shù)據(jù):基于HTTP協(xié)議之上,和 file://, ssh://, 和 git:// 等智能傳輸協(xié)議。這一節(jié)帶你快速瀏覽這兩種主要的協(xié)議操作過程。

啞協(xié)議

Git 基于HTTP之上傳輸通常被稱為啞協(xié)議,這是因?yàn)樗诜?wù)端不需要有針對(duì) Git 特有的代碼。這個(gè)獲取過程僅僅是一系列GET請(qǐng)求,客戶端可以假定服務(wù)端的Git倉庫中的布局。讓我們以 simplegit 庫來看看 http-fetch 的過程:

$ git clone http://github.com/schacon/simplegit-progit.git
它做的第1件事情就是獲取 info/refs 文件。這個(gè)文件是在服務(wù)端運(yùn)行了 update-server-info 所生成的,這也解釋了為什么在服務(wù)端要想使用HTTP傳輸,必須要開啟 post-receive 鉤子:

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

現(xiàn)在你有一個(gè)遠(yuǎn)端引用和SHA值的列表。下一步是尋找HEAD引用,這樣你就知道了在完成后,什么應(yīng)該被檢出到工作目錄:

=> GET HEAD
ref: refs/heads/master

這說明在完成獲取后,需要檢出 master 分支。 這時(shí),已經(jīng)可以開始漫游操作了。因?yàn)槟愕钠瘘c(diǎn)是在 info/refs 文件中所提到的 ca82a6 commit 對(duì)象,你的開始操作就是獲取它:

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)

然后你取回了這個(gè)對(duì)象 - 這在服務(wù)端是一個(gè)松散格式的對(duì)象,你使用的是靜態(tài)的 HTTP GET 請(qǐng)求獲取的??梢允褂?zlib 解壓縮它,去除其頭部,查看它的 commmit 內(nèi)容:

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700

changed the version number

這樣,就得到了兩個(gè)需要進(jìn)一步獲取的對(duì)象 - cfda3b 是這個(gè) commit 對(duì)象所對(duì)應(yīng)的 tree 對(duì)象,和 085bb3 是它的父對(duì)象;

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)

這樣就取得了這它的下一步 commit 對(duì)象,再抓取 tree 對(duì)象:

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)

Oops - 看起來這個(gè) tree 對(duì)象在服務(wù)端并不以松散格式對(duì)象存在,所以得到了404響應(yīng),代表在HTTP服務(wù)端沒有找到該對(duì)象。這有好幾個(gè)原因 - 這個(gè)對(duì)象可能在替代倉庫里面,或者在打包文件里面, Git 會(huì)首先檢查任何列出的替代倉庫:

=> GET objects/info/http-alternates
(empty file)

如果這返回了幾個(gè)替代倉庫列表,那么它會(huì)去那些地方檢查松散格式對(duì)象和文件 - 這是一種在軟件分叉之間共享對(duì)象以節(jié)省磁盤的好方法。然而,在這個(gè)例子中,沒有替代倉庫。所以你所需要的對(duì)象肯定在某個(gè)打包文件中。要檢查服務(wù)端有哪些打包格式文件,你需要獲取 objects/info/packs 文件,這里面包含有打包文件列表(是的,它也是被 update-server-info 所生成的);

=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

這里服務(wù)端只有一個(gè)打包文件,所以你要的對(duì)象顯然就在里面。但是你可以先檢查它的索引文件以確認(rèn)。這在服務(wù)端有多個(gè)打包文件時(shí)也很有用,因?yàn)檫@樣就可以先檢查你所需要的對(duì)象空間是在哪一個(gè)打包文件里面了:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)

現(xiàn)在你有了這個(gè)打包文件的索引,你可以看看你要的對(duì)象是否在里面 - 因?yàn)樗饕募谐隽诉@個(gè)打包文件所包含的所有對(duì)象的SHA值,和該對(duì)象存在于打包文件中的偏移量,所以你只需要簡單地獲取整個(gè)打包文件:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)

現(xiàn)在你也有了這個(gè) tree 對(duì)象,你可以繼續(xù)在 commit 對(duì)象上漫游。它們?nèi)慷荚谶@個(gè)你已經(jīng)下載到的打包文件里面,所以你不用繼續(xù)向服務(wù)端請(qǐng)求更多下載了。 在這完成之后,由于下載開始時(shí)已探明HEAD引用是指向 master 分支, Git 會(huì)將它檢出到工作目錄。

整個(gè)過程看起來就像這樣:

$ git clone http://github.com/schacon/simplegit-progit.git
Initialized empty Git repository in /private/tmp/simplegit-progit/.git/
got ca82a6dff817ec66f44342007202690a93763949
walk ca82a6dff817ec66f44342007202690a93763949
got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Getting alternates list for http://github.com/schacon/simplegit-progit.git
Getting pack list for http://github.com/schacon/simplegit-progit.git
Getting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835
Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835
 which contains cfda3bf379e4f8dba8717dee55aab78aef7f4daf
walk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
walk a11bef06a3f659402fe7563abf99ad00de2209e6

智能協(xié)議

這個(gè)HTTP方法是很簡單但效率不是很高。使用智能協(xié)議是傳送數(shù)據(jù)的更常用的方法。這些協(xié)議在遠(yuǎn)端都有Git智能型進(jìn)程在服務(wù) - 它可以讀出本地?cái)?shù)據(jù)并計(jì)算出客戶端所需要的,并生成合適的數(shù)據(jù)給它,這有兩類傳輸數(shù)據(jù)的進(jìn)程:一對(duì)用于上傳數(shù)據(jù)和一對(duì)用于下載。

上傳數(shù)據(jù)

為了上傳數(shù)據(jù)至遠(yuǎn)端, Git 使用 send-pack 和 receive-pack 進(jìn)程。這個(gè) send-pack 進(jìn)程運(yùn)行在客戶端上,它連接至遠(yuǎn)端運(yùn)行的 receive-pack 進(jìn)程。

舉例來說,你在你的項(xiàng)目上運(yùn)行了 git push origin master, 并且 origin 被定義為一個(gè)使用SSH協(xié)議的URL。 Git 會(huì)使用 send-pack 進(jìn)程,它會(huì)啟動(dòng)一個(gè)基于SSH的連接到服務(wù)器。它嘗試像這樣透過SSH在服務(wù)端運(yùn)行命令:

$ ssh -x git@github.com "git-receive-pack 'schacon/simplegit-progit.git'"
005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status delete-refs
003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic
0000

這里的 git-receive-pack 命令會(huì)立即對(duì)它所擁有的每一個(gè)引用響應(yīng)一行 - 在這個(gè)例子中,只有 master 分支和它的SHA值。這里第1行也包含了服務(wù)端的能力列表(這里是 report-status 和 delete-refs)。

每一行以4字節(jié)的十六進(jìn)制開始,用于指定整行的長度。你看到第1行以005b開始,這在十六進(jìn)制中表示91,意味著第1行有91字節(jié)長。下一行以003e起始,表示有62字節(jié)長,所以需要讀剩下的62字節(jié)。再下一行是0000開始,表示服務(wù)器已完成了引用列表過程。

現(xiàn)在它知道了服務(wù)端的狀態(tài),你的 send-pack 進(jìn)程會(huì)判斷哪些 commit 是它所擁有但服務(wù)端沒有的。針對(duì)每個(gè)引用,這次推送都會(huì)告訴對(duì)端的 receive-pack 這個(gè)信息。舉例說,如果你在更新 master 分支,并且增加 experiment 分支,這個(gè) send-pack 將會(huì)是像這樣:

0085ca82a6dff817ec66f44342007202690a93763949  15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status
00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment
0000

這里的全'0'的SHA-1值表示之前沒有過這個(gè)對(duì)象 - 因?yàn)槟闶窃谔砑有碌?experiment 引用。如果你在刪除一個(gè)引用,你會(huì)看到相反的: 就是右邊是全'0'。

Git 針對(duì)每個(gè)引用發(fā)送這樣一行信息,就是舊的SHA值,新的SHA值,和將要更新的引用的名稱。第1行還會(huì)包含有客戶端的能力。下一步,客戶端會(huì)發(fā)送一個(gè)所有那些服務(wù)端所沒有的對(duì)象的一個(gè)打包文件。最后,服務(wù)端以成功(或者失敗)來響應(yīng):

000Aunpack ok

下載數(shù)據(jù)

當(dāng)你在下載數(shù)據(jù)時(shí), fetch-pack 和 upload-pack 進(jìn)程就起作用了??蛻舳藛?dòng) fetch-pack 進(jìn)程,連接至遠(yuǎn)端的 upload-pack 進(jìn)程,以協(xié)商后續(xù)數(shù)據(jù)傳輸過程。

在遠(yuǎn)端倉庫有不同的方式啟動(dòng) upload-pack 進(jìn)程。你可以使用與 receive-pack 相同的透過SSH管道的方式,也可以通過 Git 后臺(tái)來啟動(dòng)這個(gè)進(jìn)程,它默認(rèn)監(jiān)聽在9418號(hào)端口上。這里 fetch-pack 進(jìn)程在連接后像這樣向后臺(tái)發(fā)送數(shù)據(jù):

003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0

它也是以4字節(jié)指定后續(xù)字節(jié)長度的方式開始,然后是要運(yùn)行的命令,和一個(gè)空字節(jié),然后是服務(wù)端的主機(jī)名,再跟隨一個(gè)最后的空字節(jié)。 Git 后臺(tái)進(jìn)程會(huì)檢查這個(gè)命令是否可以運(yùn)行,以及那個(gè)倉庫是否存在,以及是否具有公開權(quán)限。如果所有檢查都通過了,它會(huì)啟動(dòng)這個(gè) upload-pack 進(jìn)程并將客戶端的請(qǐng)求移交給它。

如果你透過SSH使用獲取功能, fetch-pack 會(huì)像這樣運(yùn)行:

$ ssh -x git@github.com "git-upload-pack 'schacon/simplegit-progit.git'"
不管哪種方式,在 fetch-pack 連接之后, upload-pack 都會(huì)以這種形式返回:

0088ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_ack thin-pack \
  side-band side-band-64k ofs-delta shallow no-progress include-tag
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
003e085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 refs/heads/topic
0000

這與 receive-pack 響應(yīng)很類似,但是這里指的能力是不同的。而且它還會(huì)指出HEAD引用,讓客戶端可以檢查是否是一份克隆。

在這里, fetch-pack 進(jìn)程檢查它自己所擁有的對(duì)象和所有它需要的對(duì)象,通過發(fā)送 "want" 和所需對(duì)象的SHA值,發(fā)送 "have" 和所有它已擁有的對(duì)象的SHA值。在列表完成時(shí),再發(fā)送 "done" 通知 upload-pack 進(jìn)程開始發(fā)送所需對(duì)象的打包文件。這個(gè)過程看起來像這樣:

0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0000
0009done

這是傳輸協(xié)議的一個(gè)很基礎(chǔ)的例子,在更復(fù)雜的例子中,客戶端可能會(huì)支持 multi_ack 或者 side-band 能力;但是這個(gè)例子中展示了智能協(xié)議的基本交互過程。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)