10.6 傳輸協(xié)議

2018-02-24 15:22 更新

傳輸協(xié)議

Git 可以通過兩種主要的方式在版本庫之間傳輸數(shù)據(jù):“啞(dumb)”協(xié)議和“智能(smart)”協(xié)議。 本節(jié)將會(huì)帶你快速瀏覽這兩種協(xié)議的運(yùn)作方式。

啞協(xié)議

如果你正在架設(shè)一個(gè)基于 HTTP 協(xié)議的只讀版本庫,一般而言這種情況下使用的就是啞協(xié)議。 這個(gè)協(xié)議之所以被稱為“啞”協(xié)議,是因?yàn)樵趥鬏斶^程中,服務(wù)端不需要有針對 Git 特有的代碼;抓取過程是一系列 HTTP 的?GET?請求,這種情況下,客戶端可以推斷出服務(wù)端 Git 倉庫的布局。

NOTE

現(xiàn)在已經(jīng)很少使用啞協(xié)議了。 使用啞協(xié)議的版本庫很難保證安全性和私有化,所以大多數(shù) Git 服務(wù)器宿主(包括云端和本地)都會(huì)拒絕使用它。 一般情況下都建議使用智能協(xié)議,我們會(huì)在后面進(jìn)行介紹。

讓我們通過 simplegit 版本庫來看看?http-fetch?的過程:

$ git clone http://server/simplegit-progit.git

它做的第一件事就是拉取?info/refs?文件。 這個(gè)文件是通過?update-server-info?命令生成的,這也解釋了在使用HTTP傳輸時(shí),必須把它設(shè)置為?post-receive?鉤子的原因:

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

現(xiàn)在,你得到了一個(gè)遠(yuǎn)程引用和 SHA-1 值的列表。 接下來,你要確定 HEAD 引用是什么,這樣你就知道在完成后應(yīng)該被檢出到工作目錄的內(nèi)容:

=> GET HEAD
ref: refs/heads/master

這說明在完成抓取后,你需要檢出?master?分支。 這時(shí),你就可以開始遍歷處理了。 因?yàn)槟闶菑?code>info/refs?文件中所提到的?ca82a6?提交對象開始的,所以你的首要操作是獲取它:

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

你取回了一個(gè)對象——這是一個(gè)在服務(wù)端以松散格式保存的對象,是你通過使用靜態(tài) HTTP GET 請求獲取的。 你可以使用 zlib 解壓縮它,去除其頭部,查看提交記錄的內(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è)對象,一個(gè)是樹對象?cfda3b,它包含有我們剛剛獲取的提交對象所指向的內(nèi)容,另一個(gè)是它的父提交?085bb3

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

這樣就取得了你的下一個(gè)提交對象。 再抓取樹對象:

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

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

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

如果這返回了一個(gè)包含替代版本庫 URL 的列表,那么 Git 就會(huì)去那些地址檢查松散格式對象和文件——這是一種能讓派生項(xiàng)目共享對象以節(jié)省磁盤的好方法。 然而,在這個(gè)例子中,沒有列出可用的替代版本庫。所以你所需要的對象肯定在某個(gè)包文件中。 要檢查服務(wù)端有哪些可用的包文件,你需要獲取?objects/info/packs?文件,這里面有一個(gè)包文件列表(它也是通過執(zhí)行?update-server-info?所生成的):

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

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

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

現(xiàn)在你有這個(gè)包文件的索引,你可以查看你要的對象是否在里面——因?yàn)樗饕募谐隽诉@個(gè)包文件所包含的所有對象的 SHA-1 值,和該對象存在于包文件中的偏移量。 你的對象就在這里,接下來就是獲取整個(gè)包文件:

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

現(xiàn)在你也有了你的樹對象,你可以繼續(xù)在提交記錄上漫游。 它們?nèi)慷荚谶@個(gè)你剛下載的包文件里面,所以你不用繼續(xù)向服務(wù)端請求更多下載了。 Git 會(huì)將開始時(shí)下載的 HEAD 引用所指向的master?分支檢出到工作目錄。

智能協(xié)議

啞協(xié)議雖然很簡單但效率略低,且它不能從客戶端向服務(wù)端發(fā)送數(shù)據(jù)。 智能協(xié)議是更常用的傳送數(shù)據(jù)的方法,但它需要在服務(wù)端運(yùn)行一個(gè)進(jìn)程,而這也是 Git 的智能之處——它可以讀取本地?cái)?shù)據(jù),理解客戶端有什么和需要什么,并為它生成合適的包文件。 總共有兩組進(jìn)程用于傳輸數(shù)據(jù),它們分別負(fù)責(zé)上傳和下載數(shù)據(jù)。

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

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

SSH

舉例來說,在項(xiàng)目中使用命令?git push origin master?時(shí),?origin?是由基于 SSH 協(xié)議的 URL 所定義的。 Git 會(huì)運(yùn)行?send-pack?進(jìn)程,它會(huì)通過 SSH 連接你的服務(wù)器。 它會(huì)嘗試通過 SSH 在服務(wù)端執(zhí)行命令,就像這樣:

$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status \
    delete-refs side-band-64k quiet ofs-delta \
    agent=git/2:2.1.1+github-607-gfba4028 delete-refs
0000

git-receive-pack?命令會(huì)立即為它所擁有的每一個(gè)引用發(fā)送一行響應(yīng)——在這個(gè)例子中,就只有master?分支和它的 SHA-1 值。 第一行響應(yīng)中也包含了一個(gè)服務(wù)端能力的列表(這里是?report-status、delete-refs?和一些其它的,包括客戶端的識別碼)。

每一行以一個(gè)四位的十六進(jìn)制值開始,用于指明本行的長度。 你看到第一行以 005b 開始,這在十六進(jìn)制中表示 91,意味著第一行有 91 字節(jié)。 下一行以 003e 起始,也就是 62,所以下面需要讀取 62 字節(jié)。 再下一行是 0000,表示服務(wù)端已完成了發(fā)送引用列表過程。

現(xiàn)在它知道了服務(wù)端的狀態(tài),你的?send-pack?進(jìn)程會(huì)判斷哪些提交記錄是它所擁有但服務(wù)端沒有的。?send-pack?會(huì)告知?receive-pack?這次推送將會(huì)更新的各個(gè)引用。 舉個(gè)例子,如果你正在更新?master?分支,并且增加?experiment?分支,這個(gè)?send-pack?的響應(yīng)將會(huì)是像這樣:

0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \
    refs/heads/master report-status
006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
    refs/heads/experiment
0000

Git 會(huì)為每一個(gè)將要更新的引用發(fā)送一行數(shù)據(jù),包括該行長度,舊 SHA-1 值,新 SHA-1 值和將要更新的引用。 第一行也包括了客戶端的能力。 這里的全為?0?的 SHA-1 值表示之前沒有過這個(gè)引用——因?yàn)槟阏砑有碌?experiment 引用。 刪除引用時(shí),將會(huì)看到相反的情況:右邊的 SHA-1 值全為?0

接下來,客戶端會(huì)發(fā)送一個(gè)包文件,它包含了所有服務(wù)端還沒有的對象。 最后,服務(wù)端會(huì)以成功(或失?。╉憫?yīng):

000eunpack ok

HTTP(S)

HTTPS 與 HTTP 相比較,除了在“握手”過程略有不同外,其他基本相似。 連接是從下面這個(gè)請求開始的:

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master report-status \
    delete-refs side-band-64k quiet ofs-delta \
    agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000

這完成了客戶端和服務(wù)端的第一次數(shù)據(jù)交換。 接下來客戶端發(fā)起另一個(gè)請求,這次是一個(gè)?POST?請求,這個(gè)請求中包含了?git-upload-pack?提供的數(shù)據(jù)。

=> POST http://server/simplegit-progit.git/git-receive-pack

這個(gè)?POST?請求的內(nèi)容是?send-pack?的輸出和相應(yīng)的包文件。 服務(wù)端在收到請求后相應(yīng)地作出成功或失敗的 HTTP 響應(yīng)。

下載數(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ù)臄?shù)據(jù)。

SSH

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

$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"

在?fetch-pack?連接后,upload-pack?會(huì)返回類似下面的內(nèi)容:

00dfca82a6dff817ec66f44342007202690a93763949 HEAD multi_ack thin-pack \
    side-band side-band-64k ofs-delta shallow no-progress include-tag \
    multi_ack_detailed symref=HEAD:refs/heads/master \
    agent=git/2:2.1.1+github-607-gfba4028
003fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master
0000

這與?receive-pack?的響應(yīng)很相似,但是這里所包含的能力是不同的。 而且它還包含 HEAD 引用所指向內(nèi)容(symref=HEAD:refs/heads/master),這樣如果客戶端執(zhí)行的是克隆,它就會(huì)知道要檢出什么。

這時(shí)候,fetch-pack?進(jìn)程查看它自己所擁有的對象,并響應(yīng) “want” 和它需要的對象的 SHA-1 值。 它還會(huì)發(fā)送“have”和所有它已擁有的對象的 SHA-1 值。 在列表的最后,它還會(huì)發(fā)送“done”以通知?upload-pack?進(jìn)程可以開始發(fā)送它所需對象的包文件:

003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000

HTTP(S)

抓取操作的握手需要兩個(gè) HTTP 請求。 第一個(gè)是向和啞協(xié)議中相同的端點(diǎn)發(fā)送?GET?請求:

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD multi_ack thin-pack \
    side-band side-band-64k ofs-delta shallow no-progress include-tag \
    multi_ack_detailed no-done symref=HEAD:refs/heads/master \
    agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

這和通過 SSH 使用?git-upload-pack?是非常相似的,但是第二個(gè)數(shù)據(jù)交換則是一個(gè)單獨(dú)的請求:

=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000

這個(gè)輸出格式還是和前面一樣的。 這個(gè)請求的響應(yīng)包含了所需要的包文件,并指明成功或失敗。

協(xié)議總結(jié)

這一章節(jié)是傳輸協(xié)議的一個(gè)概貌。 傳輸協(xié)議還有很多其它的特性,像是?multi_ack?或?side-band,但是這些內(nèi)容已經(jīng)超出了本書的范圍。 我們希望能給你展示客戶端和服務(wù)端之間的基本交互過程;如果你需要更多的相關(guān)知識,你可以參閱 Git 的源代碼。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號