Git內部原理之深入解析傳輸協議
一、啞協議

如果正在架設一個基于 HTTP 協議的只讀版本庫,一般而言這種情況下在版本庫之間傳輸數據使用的就是啞協議,這個協議之所以被稱為“啞”協議,是因為在傳輸過程中,服務端不需要有針對 Git 特有的代碼;抓取過程是一系列 HTTP 的 GET 請求,這種情況下,客戶端可以推斷出服務端 Git 倉庫的布局。
現在已經很少使用啞協議了,使用啞協議的版本庫很難保證安全性和私有化,所以大多數 Git 服務器宿主(包括云端和本地)都會拒絕使用它,一般情況下都建議使用智能協議。
通過 simplegit 版本庫來看看 http-fetch 的過程:
$ git clone http://server/simplegit-progit.git
1
它做的第一件事就是拉取 info/refs 文件,這個文件是通過 update-server-info 命令生成的,這也解釋了在使用 HTTP 傳輸時,必須把它設置為 post-receive 鉤子的原因:
=> GET info/refs ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
1
2
現在得到了一個遠程引用和 SHA-1 值的列表。接下來,要確定 HEAD 引用是什么,這樣就知道在完成后應該被檢出到工作目錄的內容:
=> GET HEAD ref: refs/heads/master
1
2
這說明在完成抓取后,需要檢出 master 分支。這時,就可以開始遍歷處理了,因為是從 info/refs 文件中所提到的 ca82a6 提交對象開始的,所以首要操作是獲取它:
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949 (179 bytes of binary data)
1
2
取回了一個對象,這是一個在服務端以松散格式保存的對象,是通過使用靜態 HTTP GET 請求獲取的。可以使用 zlib 解壓縮它,去除其頭部,查看提交記錄的內容:
$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949 tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 author Scott Chacon
1
2
3
4
5
6
7
接下來,還要再獲取兩個對象,一個是樹對象 cfda3b,它包含有我們剛剛獲取的提交對象所指向的內容,另一個是它的父提交 085bb3:
=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 (179 bytes of data)
1
2
這樣就取得了下一個提交對象,再抓取樹對象:
=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf (404 - Not Found)
1
2
看起來這個樹對象在服務端并不以松散格式對象存在,所以得到了一個 404 響應,代表在 HTTP 服務端沒有找到該對象。這有好幾個可能的原因,這個對象可能在替代版本庫里面,或者在包文件里面。Git 會首先檢查所有列出的替代版本庫:
=> GET objects/info/http-alternates (empty file)
1
2
如果這返回了一個包含替代版本庫 URL 的列表,那么 Git 就會去那些地址檢查松散格式對象和文件,這是一種能讓派生項目共享對象以節省磁盤的好方法。然而,在這個例子中,沒有列出可用的替代版本庫,所以所需要的對象肯定在某個包文件中。要檢查服務端有哪些可用的包文件,需要獲取 objects/info/packs 文件,這里面有一個包文件列表(它也是通過執行 update-server-info 所生成的):
=> GET objects/info/packs P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
1
2
服務端只有一個包文件,所以需要的對象顯然就在里面。但是要先檢查它的索引文件以確認,即使服務端有多個包文件,這也是很有用的,因為這樣就可以知道你所需要的對象是在哪一個包文件里面:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx (4k of binary data)
1
2
現在有這個包文件的索引,可以查看需要的對象是否在里面,因為索引文件列出了這個包文件所包含的所有對象的 SHA-1 值,和該對象存在于包文件中的偏移量,需要的對象就在這里,接下來就是獲取整個包文件:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack (13k of binary data)
1
2
現在也有了樹對象,可以繼續在提交記錄上漫游,它們全部都在這個剛下載的包文件里面,所以不用繼續向服務端請求更多下載了。Git 會將開始時下載的 HEAD 引用所指向的 master 分支檢出到工作目錄。
二、智能協議
啞協議雖然很簡單但效率略低,且它不能從客戶端向服務端發送數據。智能協議是更常用的傳送數據的方法,但它需要在服務端運行一個進程,而這也是 Git 的智能之處,它可以讀取本地數據,理解客戶端有什么和需要什么,并為它生成合適的包文件,總共有兩組進程用于傳輸數據,它們分別負責上傳和下載數據。
為了上傳數據至遠端,Git 使用 send-pack 和 receive-pack 進程,運行在客戶端上的 send-pack 進程連接到遠端運行的 receive-pack 進程。
SSH:
舉例來說,在項目中使用命令 git push origin master 時,origin 是由基于 SSH 協議的 URL 所定義的。Git 會運行 send-pack 進程,它會通過 SSH 連接服務器,嘗試通過 SSH 在服務端執行命令,就像這樣:
$ 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
1
2
3
4
5
git-receive-pack 命令會立即為它所擁有的每一個引用發送一行響應,例子中就只有 master 分支和它的 SHA-1 值,第一行響應中也包含了一個服務端能力的列表(這里是 report-status、delete-refs 和一些其它的,包括客戶端的識別碼)。
每一行以一個四位的十六進制值開始,用于指明本行的長度,可以看到第一行以 00a5 開始,這在十六進制中表示 165,意味著第一行有 165 字節;下一行是 0000,表示服務端已完成了發送引用列表過程。
現在它知道了服務端的狀態,send-pack 進程會判斷哪些提交記錄是它所擁有但服務端沒有的。send-pack 會告知 receive-pack 這次推送將會更新的各個引用。舉個例子,如果正在更新 master 分支,并且增加 experiment 分支,這個 send-pack 的響應將會是像這樣:
0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \ refs/heads/master report-status 006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \ refs/heads/experiment 0000
1
2
3
4
5
第一行也包括了客戶端的能力,這里的全為 0 的 SHA-1 值表示之前沒有過這個引用,因為正要添加新的 experiment 引用;刪除引用時,將會看到相反的情況:右邊的 SHA-1 值全為 0。
然后,客戶端會發送一個包含了所有服務端上所沒有的對象的包文件,最終,服務端會響應一個成功(或失敗)的標識:
000eunpack ok
1
HTTP(S):
上傳過程在 HTTP 上幾乎是相同的,除了握手階段有一點小區別,連接是從下面這個請求開始的:
=> 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
1
2
3
4
5
6
這完成了客戶端和服務端的第一次數據交換。接下來客戶端發起另一個請求,這次是一個 POST 請求,這個請求中包含了 send-pack 提供的數據:
=> POST http://server/simplegit-progit.git/git-receive-pack
1
這個 POST 請求的內容是 send-pack 的輸出和相應的包文件,服務端在收到請求后相應地作出成功或失敗的 HTTP 響應。請牢記,HTTP 協議有可能會進一步用分塊傳輸編碼將數據包裹起來。
當下載數據時, fetch-pack 和 upload-pack 進程就起作用了,客戶端啟動 fetch-pack 進程,連接至遠端的 upload-pack 進程,以協商后續傳輸的數據。
SSH:
如果通過 SSH 使用抓取功能,fetch-pack 會像這樣運行:
$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"
1
在 fetch-pack 連接后,upload-pack 會返回類似下面的內容:
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
1
2
3
4
5
6
這與 receive-pack 的響應很相似,但是這里所包含的能力是不同的,而且它還包含 HEAD 引用所指向內容(symref=HEAD:refs/heads/master),這樣如果客戶端執行的是克隆,它就會知道要檢出什么。
這時候,fetch-pack 進程查看它自己所擁有的對象,并響應 “want” 和它需要的對象的 SHA-1 值,它還會發送“have”和所有它已擁有的對象的 SHA-1 值。在列表的最后,它還會發送“done”以通知 upload-pack 進程可以開始發送它所需對象的包文件:
003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta 0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 0009done 0000
1
2
3
4
HTTP(S):
抓取操作的握手需要兩個 HTTP 請求,第一個是向和啞協議中相同的端點發送 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
1
2
3
4
5
6
7
8
這和通過 SSH 使用 git-upload-pack 是非常相似的,但是第二個數據交換則是一個單獨的請求:
=> POST $GIT_URL/git-upload-pack HTTP/1.0 0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7 0032have 441b40d833fdfa93eb2908e52742248faf0ee993 0000
1
2
3
4
這個輸出格式還是和前面一樣的,這個請求的響應包含了所需要的包文件,并指明成功或失敗。
Git TCP/IP
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。