用 strace 命令淺析 git push 通過 SSH 工作的原理
來自: http://blog.jobbole.com/97913/
昨天,突如其來的好奇充斥著我的腦袋:究竟 git push 如何通過 SSH 工作呢?由于我越來越習慣使用 strace 來折騰這類問題,所以我又嘗試用它來練練手。如果我 利用 strace(跟蹤)git push 命令到這個網站的(資料)庫,會得到如下顯 示:
[pid 15943] execve("/usr/bin/ssh", ["ssh", "git@github.com", "git-receive-pack 'kamalmarhubi/w"...], [/* 51 vars */]) = 0
所以 git push 最終會調用 ssh git@github.com git-receive-pack <repo-path>。然后在我的終端(terminal)嘗試輸入以下命 令,得到了以下線索:
$ ssh git@github.com git-receive-pack kamalmarhubi/website00bb2979fec627d60938c4ed2086cc60bb1 refs/heads/gh-pagesreport-status delete-refs side-band-64k quiet atomic ofs-delta agent=git/2:2.4.8~upload-pack-wrapper-script-1211-gc27b061
003f04bfcb3e238e5660ae9e71a6ce99f472211fe85f refs/heads/master
0000</pre>
終端依然在等待我的輸入。SSH 用來解決身份驗證和遠 程控制的問題 ,驗證成功后,SSH 的另一端會運行一段命令來進行數據交換。而上面這幾行就是數據交換的開始。
在網上稍微搜索了一下,我知道了這個協議是由行組成的 ,而每一行的 4 位前置碼正是行長度的十六進制表示。后面跟著提交的 SHA-1 和 ref ,發送端以一行 “0000” 作為結束標識符。
上面的每一行對應(資料)庫里的每一個分支:第一行自帶了一條長長的小 尾巴,好像是發送程序的自我介紹和支持的相關功能。
我在研究這些代碼的時候,使用 xsel 命令把輸出結果復制到編輯器上面,不過令人困惑的是,我粘貼得到的竟然只有第一行而不是所有元數據。
00bb29793c39c8e4bfec627d60938c4ed2086cc60bb1 refs/heads/gh-pages通過 hexdump –C 查看完整的輸出后發現,原來在 refs/heads/gh-pages 后面有一個空字節,而且在末尾處還換行了(用星號 * 標記處):
00000000 30 30 62 62 32 39 37 39 33 63 33 39 63 38 65 34 |00bb29793c39c8e4|00000010 62 66 65 63 36 32 37 64 36 30 39 33 38 63 34 65 |bfec627d60938c4e|
00000020 64 32 30 38 36 63 63 36 30 62 62 31 20 72 65 66 |d2086cc60bb1 ref|
00000030 73 2f 68 65 61 64 73 2f 67 68 2d 70 61 67 65 73 |s/heads/gh-pages|
00000040 0072 65 70 6f 72 74 2d 73 74 61 74 75 73 20 64 |.report-status d|
00000050 65 6c 65 74 65 2d 72 65 66 73 20 73 69 64 65 2d |elete-refs side-|
00000060 62 61 6e 64 2d 36 34 6b 20 71 75 69 65 74 20 61 |band-64k quiet a|
00000070 74 6f 6d 69 63 20 6f 66 73 2d 64 65 6c 74 61 20 |tomic ofs-delta |
00000080 61 67 65 6e 74 3d 67 69 74 2f 32 3a 32 2e 34 2e |agent=git/2:2.4.|
00000090 38 7e 75 70 6c 6f 61 64 2d 70 61 63 6b 2d 77 72 |8~upload-pack-wr|
000000a0 61 70 70 65 72 2d 73 63 72 69 70 74 2d 31 32 31 |apper-script-121|
000000b0 31 2d 67 63 32 37 62 30 36 310a30 30 33 66 37 |1-gc27b061.003f7|
000000c0 39 32 66 34 39 36 65 37 35 33 64 62 39 33 33 30 |92f496e753db9330|
000000d0 66 30 61 34 65 38 32 39 30 62 38 61 36 63 62 61 |f0a4e8290b8a6cba|
000000e0 38 61 62 36 64 61 62 20 72 65 66 73 2f 68 65 61 |8ab6dab refs/hea|
000000f0 64 73 2f 6d 61 73 74 65 72 0a 30 30 30 30 |ds/master.0000|
000000fe</pre>
我在沒有仔細研究的情況下,大膽地做了一個猜想:那些在 github 上做開發的家伙們,定義了一個相當簡單的長度前綴+換行分隔協議(length-prefixed, newline-separated protocol),當他們有需要向協議里面加入一些元數據 的時候,可以保 持和老版本 git 的兼容性。這個解決方案巧妙地利用了 C 語言的 0 結尾字符串:把元數據放在空字節和新行之間。用這種方式讀取數據(讀取到新行之前位置)可以得到所有的元數據。元數據處理代碼在執行時會跳過空字節,但是現有的協議代碼卻只能看到到空字節之前的 數據,它們對這種改動完全無感!
所以我之前用 xsel 命令復制數據時,那些空字節后面的東東被完美地忽略了。
真相就只有一個,謎底就這樣被解開了!(其實wo真不是柯南哦)
</div>