##前言 在開發軟件的過程當中,每每是須要多我的參與,版本控制系統的協同工做的重要性不言而喻,除此以外, 版本控制軟件對整個開發流程的記錄對於缺陷追蹤也是很是重要的。版本控制系統也是軟件開發的基礎設施。html
筆者開始接觸版本控制系統是大學的時候,最開始安裝了 TortoiseSVN ,然而 TortoiseSVN 僅僅是佔據了硬盤空間而沒有發揮做用,不少開發者在接觸新事物的時候,並不必定會有極大的熱情去了解, 有的走了不少彎路後返回到了原地,只有當深刻了解之後,才以爲其中異常的精彩。當我在 Windows 下編譯 LLVM 的時候, Subversion 開始發揮做用,彼時,幾乎全部開源的大型軟件都是使用 Subversion 進行託管,固然還有部分 CVS。 GIT 遠遠沒有目前流行。後來參加工做後,就是代碼託管的工做,對 Subversion 和 Git 有了必定程度的瞭解, 逐漸有了本身的思考。前端
大多數人對版本控制系統的解讀都是站在使用者的角度,而本文是站在一個代碼託管的開發者立場。linux
##版本控制系統見聞 版本控制系統的歷史能夠追溯到20世紀70年代,這是一個軍方開發的 CCC (變動和配置控制)系統,名字叫作 CA Software Change Manager 隨後,版本控制系統開始發展起來。git
CVS 一度曾經是開源軟件的第一選擇,好比 GNOME、KDE、THE GIMP 和 Wine, 都曾使用過 CVS 來管理。這是一個集中式的版本控制系統,一樣是集中式的還有 Subversion, Visual SouceSafe Perforce,Team Foundation Server。web
因爲難以忍受 CVS,CollabNet 的開發者開發了著名的 Subversion(SVN) 來取代 CVS, Subversion 誕生於 2000 年, 時至今日,SVN 依然是最流行的集中式版本控制系統,GCC ,LLVM 等開源軟件都使用 SVN 管理,代碼託管網站方面, SourceForge 提供 SVN 的代碼託管。apache
Visual SouceSafe(VSS)是微軟開發的版本控制系統,到了 2008年,被 Team Foundation Server(TFS) 取代, TFS 並非傳統意義的版本控制系統,而是雲開發協做平臺,支持 Team Foundation Version Control 和 Git, 像微軟這樣的企業,不管是 Windows 仍是 Office 仍是 其餘軟件,代碼量都很是巨大,只有像 TFS 這樣量身定作的系統才合適。編程
Perforce 是一個商業的版本控制系統,在其官網 www.perfoce.com 介紹, 有着超過10000個用戶使用他們的服務,有 NVIDIA ,Sumsung,vmware,adidas 等著名企業,而我對他的印象在是 OpenWATCOM C/C++ 編譯器以及 p4merge 工具。p4merge 是 Perforce 提供的一個基於 Qt 開發的跨平臺比較工具。windows
與集中式版本控制系統對應的是分佈式版本控制系統 (Distribution Version Control System) 比較流行的有 git 和 Mercurial, 兩者均誕生於 2005 年。後端
Git 由 Linux 之父, Linus Torvalds 爲了替代 BitKeeper 而開發的,關於 Git 的誕生,能夠看對 Linus 本人的採訪: 10 Years of Git: An Interview with Git Creator Linus Torvalds Git 很是流行, Linux, FreeBSD, .NET Core CLR, .NET Core Fx, Minix, Android 等項目都使用 Git 來管理, Git 的社區很是成熟,有不少代碼託管網站提供託管服務,如 Github, Bitbucket, 國內有 OSC@GIT,coding,gitcafe, CSDN code, jd code 等等。瀏覽器
技術上一樣優秀的版本控制系統 Mercurial 的使用者少不少,也有著名的瀏覽器 Mozilla Firefox,服務器 Nginx,以及編程語言 Python。 Mercurial 使用 Python 實現,或許這一點也限制了 Mercurial 的發展。
在維基百科中有一個 VCS 列表: Template:Version control software 記錄了多種版本控制系統,誕生時間,分類。
大多數時候,開發者須要學習的版本控制系統爲 Subversion 或者是 GIT。這兩者已然是兩個版本控制流派的表明。
##Git 技術內幕 本節主要介紹 Git 的存儲和傳輸
###Git 存儲
git 倉庫在磁盤上能夠表現爲兩種形式,帶有工做目錄的普通倉庫和不帶工做目錄的裸倉庫。
咱們能夠建立一個標準倉庫:
mkdir gitrepo &&cd gitrepo &&git --init &&tree -a
目錄結構以下
. ├── .git │ ├── branches │ ├── COMMIT_EDITMSG │ ├── config │ ├── description │ ├── HEAD │ ├── hooks │ │ ├── applypatch-msg.sample │ │ ├── commit-msg.sample │ │ ├── post-update.sample │ │ ├── pre-applypatch.sample │ │ ├── pre-commit.sample │ │ ├── prepare-commit-msg.sample │ │ ├── pre-push.sample │ │ ├── pre-rebase.sample │ │ └── update.sample │ ├── index │ ├── info │ │ └── exclude │ ├── logs │ │ ├── HEAD │ │ └── refs │ │ └── heads │ │ └── master │ ├── objects │ │ ├── 89 │ │ │ └── 50b8b1af3c4cc712edb5a995c83a53eb03e6be │ │ ├── d0 │ │ │ └── 2d9281b58703d020c3afe3e2ace204d6d462ae │ │ ├── e6 │ │ │ └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391 │ │ ├── info │ │ └── pack │ └── refs │ ├── heads │ │ └── master │ └── tags └── helloworld
實際上咱們建立一個裸倉庫會發現和普通倉庫的 .git 目錄結構是一致的。
mkdir gitbare.git &&cd gitbare.git &&git init --bare &&tree -a
目錄結構:
. ├── branches ├── config ├── description ├── HEAD ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── prepare-commit-msg.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ └── update.sample ├── info │ └── exclude ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags 9 directories, 13 files
當咱們建立一個倉庫時,默認狀況下會建立工做目錄,在工做目錄下有個 .git 的子目錄,這纔是存儲庫的目錄。 而咱們一般修改代碼的目錄稱之爲工做目錄。
衆所周知,git 是分佈式版本控制系統,這就意味着,只要得到了 .git 目錄的完整數據,就能夠在任意位置恢復成一個帶有工做目錄的倉庫。而 GIT 克隆一個存儲庫也僅僅是將 .git/objects 目錄下的 object 和 .git/refs (.git/packed-refs|.git/info/refs) 所存儲的引用列表傳輸到本地,並應用。
對於 Subversion 同樣的集中式版本控制系統,就至關於 .git 目錄被託管在中央服務器上,而本地的 .svn 只是工做目錄的元數據。
兩者不一樣的機制帶來的直接差異就是一旦中央服務器宕機,git 能夠迅速的遷移到其餘服務器,而且數據的丟失的可能性很小,而 Subversion 服務器就沒有這麼好的運氣了。
每一次提交,git 都會把修改的文件快照,還有更新的目錄結構,以及提交信息,打包成一個個 object,這些 object 被loose object, 因此 git 的 object 多是 blob tree commit 等。打包的過程會使用 zip 壓縮,這種被普遍運用的壓縮格式實上壓縮率較低,壓縮速度也慢,但好處有普遍的支持,專利上比較友好。
若是調用 git gc 命令後,git-gc 會將這些 object 打包成 pack 文件,這些內容在 proGit 都有詳細說明。
###Git 傳輸協議 Git 支持多種協議 http, git , ssh, file ,之內部機制區分爲啞協議和智能協議,啞協議很是簡單,簡單的說, 客戶端經過 URL 直接拿取服務端的文件。
Git 智能協議實現了兩類 RPC 調用,一個是 fetch-pack<->upload-pack, 另外一個是 send-pack<->receive-pack。
任何 Git 遠程操做都須要得到遠程倉庫的引用列表,與自身的引用列表進行比對
這裏以 HTTP 爲例
1 Fetch-Upload
Step 1:
Request
C: GET $GIT_URL/info/refs?service=git-upload-pack HTTP/1.0
Response
S: 200 OK S: Content-Type: application/x-git-upload-pack-advertisement S: Cache-Control: no-cache S: S: 001e# service=git-upload-pack\n S: 004895dcfa3633004da0049d3d0fa03f80589cbcaf31 refs/heads/maint\0multi_ack\n S: 0042d049f6c27a2244e12041955e262a404c7faba355 refs/heads/master\n S: 003c2cb58b79488a98d2721cea644875a8dd0026b115 refs/tags/v1.0\n S: 003fa3c2e2402b99163d1d59756e5f207ae21cccba4c refs/tags/v1.0^{}\n
Step 2:
Request
C: POST $GIT_URL/git-upload-pack HTTP/1.0 C: Content-Type: application/x-git-upload-pack-request C: C: 0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7\n C: 0032have 441b40d833fdfa93eb2908e52742248faf0ee993\n C: 0000
Response
S: 200 OK S: Content-Type: application/x-git-upload-pack-result S: Cache-Control: no-cache S: S: ....ACK %s, continue S: ....NAK
2 Send-Receive 實際上 push 的過程也是 GET 和 POST, 只不過,git-upload-pack 要變成 git-receive-pack ,POST 時,後者請求體中包含有 差別 package。
對於 git HTTP 來講,權限驗證一般是 HTTP 的一套,也就是 WWW-Authenticate, 絕大多數的 HTTP 服務器也就支持 Basic。
即:
user:password ->Base64 encode -->dXNlcjpwYXNzd29yZA==
因此從安全上來講,若是使用 HTTP 而不是 HTTPS , 對 GIT 遠程倉庫進行寫操做簡直就是在裸奔。
git HTTP 支持的 HTTP 返回碼並很少,這些是返回碼是支持的: 200 30x 304 403 404 410
關於 HTTP 的更多文檔細節能夠去這個地址查看: HTTP Protocol
基於 HTTP 的智能協議和基於 SSH,Git 協議本質上並沒有太大的不一樣,都是經過這兩類 RPC 調用,實現本地倉庫和遠程倉庫的數據交換。
HTTP 協議是經過 http smart server 運行 git-xxx-pack,對其輸入數據,而後讀取 git-xxx-pack 輸出。 SSH 則是經過 ssh 服務器在遠程機器上運行 git-xxx-pack ,數據傳輸的過程使用 SSH 加密。 而 GIT 協議 (git://) 協議則是 經過遠程服務器 git-daemon 運行 git-xxx-pack 實現數據的交互。一般來講 git:// 沒法實現差別化的權限管理, 也就是要麼所有隻讀,所有可寫。
查看 git daemon 程序幫助:
git help daemon
一些更多的技術內幕能夠參考 社區大做 《Pro Git》
##Git 代碼託管平臺的開發演進
雖然 GIT 是分佈式版本控制,可是對於代碼託管平臺來講又是一回事了。對於 HTTP 協議來講,像 NGINX 同樣的服務器只須要實現動態 IP, 而後經過 proxy 或者是 upstream 的方式實現 GIT 代碼託管平臺的 分佈式就能夠了。可是對於 SSH 來講比較麻煩。
###基於 RPC 的 GIT 分佈式設計
客戶端訪問倉庫時,路由智能到達 DNS 所記錄的機器或者是無差異代理的機器(前端機器),每每不能到達特定的存儲機器, 開發者使用分佈式文件系統或者 分佈式 RPC 或者代理等多種方案實現 前端到存儲的關鍵一步。這裏主要說分佈式 RPC 與 GIT smart 的應用。
分佈式 RPC 框架不少,其中著名的有 Apache Thrift ,此項目是 Facebook 開源並貢獻給 Apache 基金會的,支持多種語言。
對於 GIT 操做,只須要實現 4個函數。一下是 Thrift 接口文件的一部分:
service GitSmartService{ i32 Checksum(1:i32 client); string FetchRemoteReferences(1:string repositoryPath); binary FetchRemoteDiffPackage(1:string repositoryPath, 2:string clientReferences) string PushRemoteRefereces(1:string repositoryPath); string PushRemoteDiffPackage(1:string repositoryPath, 2:binary clientPackage); }
而後存儲服務器經過 pipe 讀取存儲機器上的 git-upload-pack /git-receive-pack 的輸入輸出。 在 Linux 上經過管道讀取 git upload-pack 的輸出:
int FetchRemoteReferencesCli(std::string &result,const std::string &path){ result.clear(); int pid,fd[2]; if(pipe(fd)<0){ printf("oops\n"); } if((pid=fork())<0){ printf("fork failed \n"); return -1; }else if(pid==0){ if(fd[1]!=STDOUT_FILENO){ if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO){ return -1; } close(fd[1]); } if(execlp("git","git","upload-pack","--stateless-rpc","--advertise-refs",path.c_str(),NULL)==-1){ printf("execlp failed \n"); exit(0); } }else{ char buffer[4096]={0}; close(fd[1]); int n=0; while((n=read(fd[0],buffer,4096))){ result.append(buffer,n); } close(fd[0]); } return 0; }
前端服務器上,編寫 模擬 git-upload-pack 或者是 git-receive-pack 的程序。用戶經過 ssh 訪問遠程倉庫時執行的 git 工具變成了模擬後的 git-upload-pack /git-receive-pack, 當使用 HTTP 訪問時,能夠整合成 RPC 客戶端整合直接整合進 HTTP 服務器,好比 NGINX 模塊, 或者也可 使用 傳統的 Git Smart HTTP 庫的方式,總的來講 Thrift 有多種語言支持,Git Smart HTTP 整合 Thrift RPC 並不成問題。
這個惟一的問題是實現異步比較麻煩,二者都須要實現異步模式,git 倉庫可能很是大,一次性克隆傳輸數據幾百 MB 或者上 GB, 這個時候 4nK 發送很是必要。
###基於 libgit2 的 smart 協議實現
GIT 除了 Linus 本人實現,kernel.org 託管的官方版本外,還有 jgit,libgit2 等,git 是一系列命令組成,幾乎沒有剝離出共享庫的能力, 這樣的後果致使其餘語言使用 git 時,不得不使用管道等進程間通信的模式與 git 工具交互。而 jgit 使用 Java 實現,基本上沒有其餘流行語言的綁定能力。
libgit2 是一個 GIT 的兼容實現,基於 C89 開發,支持絕大多數 git 特性。開發很是活躍,有多種語言綁定,如 C# Ruby 等, 其中 C# 綁定 Libgit2Sharp 被 VisualStudio, Github for Windows 等使用,而 Ruby 綁定 Rugged ,被 Github, GIT@OSC 等代碼託管平臺使用。
libgit2 並無合適的 GIT smart 服務器後端實現,多數狀況下,libgit2 主要面向的是客戶端,因爲 git 是分佈式的,對於倉庫的讀寫也就客戶端 和服務器的行爲也是相似的。
##Subversion 內幕 此部分中 SVN 協議 指 Apache Subversion 程序 svn(以及兼容的客戶端) 與遠程服務器上的 Apache Subversion svnserve (以及兼容的服務器) 進程通信的協議, 即 Subversion protocol,協議默認端口是 3690,基於 TCP, 傳輸數據使用 ABNF 範式。
在這裏指出,與 Git 徹底不一樣的是,svn 的倉庫存儲在遠程中央服務器上,開發者檢出的代碼只是特定版本,特定目錄的代碼,本地爲工做拷貝。
###Subversion HTTP 協議實現 Subversion HTTP 協議是一種 基於 WebDAV/DeltaV 的協議,WebDAV 在 HTTP 1.1 的基礎上擴展了多個 Method, 絕大多數的服務器並不支持 WebDAV, 這樣的後果就是,除了 Apache 可使用 mod_dav_svn 插件,基本上再也沒有其餘的服務器能快速的支持 Subversion 的 HTTP 協議了。代理仍是能夠的。
WebDAV 協議在 HTTP 1.1 的基礎上 使用 XML 的方式呈現數據,對於 Subversion 這種集中式版本控制系統來講,絕大多數操做都是在線的, WebDAV 包裹這些操做就變得很繁瑣。
好比一個 update-report 請求:
<S:update-report send-all="true" xmlns:S="svn:"> <S:src-path>http://localhost:8080/repos/test/httpd/support</S:src-path> <S:target-revision>2</S:target-revision> <S:entry rev="2" start-empty="true"></S:entry> </S:update-report>
而後服務器返回:
<S:update-report xmlns:S="svn:" xmlns:V="..." xmlns:D="DAV:" send-all="true"> <S:target-revision rev="2"/> <S:open-directory rev="2"> <D:checked-in> <D:href>/repos/test/!svn/ver/2/httpd/support</D:href> </D:checked-in> <S:set-prop name="svn:entry:committed-rev">2</S:set-prop> ... more set props ... <S:add-file name="ab.c"> <D:checked-in> <D:href>/repos/test/!svn/ver/2/httpd/support/ab.c</D:href> </D:checked-in> <S:set-prop name="svn:entry:committed-rev">2</S:set-prop> ... more set props for the file ... <S:txdelta>...base64-encoded file content...</S:txdelta> </S:add-file> <S:add-directory name="os" bc-url="/repos/test/!svn/bc/2/httpd/os"> <D:checked-in> <D:href>/repos/test/!svn/ver/2/httpd/os</D:href> </D:checked-in> ...directory contents... </S:add-directory> </S:open-directory> </S:update-report>
不一樣的請求,xml 的內容也徹底不一樣,Subversion HTTP 協議的複雜也讓不少開發者望而卻步。
在 Subversion 的路線圖中,基於 WebDAV/DeltaV 的 HTTP 接入將被 基於 HTTP v2 的實現取代。
A Streamlined HTTP Protocol for Subversion
###Subversion SVN 協議實現 與 HTTP 不一樣的是,一個完整的基於 SVN 協議的鏈接中,倉庫的操做是上下文相關的。
當客戶端的鏈接過來時,服務器,一般說的 svnservice 將發送一段信息給客戶端,告知服務器的能力。
S: ( minver:number maxver:number mechs:list ( cap:word ... ) )
Example:
( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries depth inherited-props log-revprops ) ) )
這個時候客戶端獲知了這些數據,若是沒法兼容,服務器,那麼將斷開與服務器的鏈接,不然,將發送請求數據給服務器,格式以下:
C: response: ( version:number ( cap:word ... ) url:string ? ra-client:string ( ? client:string ) )
Example:
( 2 ( edit-pipeline svndiff1 absent-entries depth mergeinfo log-revprops ) 36:svn://subversion.io/subversion/trunk 53:SVN/1.8.13-SlikSvn-1.8.13-X64 (x64-microsoft-windows) ( ) )
與 GIT 數據包相似的地方有一點,git 每一行數據前 4 個16進制字符表明本行的長度,而 這裏的 10 進制字符表明 字符的長度,好比 URL 長度36,UA 53。
服務器此時的行爲就得經過解析 URL 得到中央倉庫的位置,判斷協議是否兼容,而 UA 有可能爲空,格式並非很是標準,因此這是值得注意的地方。
服務器將決定使用那種受權方式,MD5 通常是 Subversion 客戶端默認的,沒法第三方庫支持,而 PLAIN 和 ANONYMOUS 須要 SASL 模塊的支持, 在 Ubuntu 上編譯 svn,先安裝 libsasl2-dev。
S: ( ( mech:word ... ) realm:string )
客戶端不支持此受權方式時,會輸出錯誤信息,「沒法協商驗證方式」
這裏的 Realm 是 subversion 客戶端存儲用戶帳戶用戶名和密碼信息的一個 key,只要 realm 一致,就會取相同的 用戶名和密碼。 realm RFC2617
Example:
( success ( ( PLAIN ) 36:e967876f-5ea0-4ff2-9c55-ea2d1703221e ) )
若是是 MD5 ,驗證協商以下:
S: ( mech:word [ token:string ] )
這個 Token 是隨機生成的 UUID, C++ 可使用 boost 生成,也可使用平臺的 API 生成。
若是是 PLAIN 受權機制,這裏就是用戶名和密碼經 Base64 編碼了, 用 NUL(0) 分隔
usernameNULpassword --> Base64 Encoded
Example:
( PLAIN ( 44:YWRtaW5Ac3VidmVyc2lvbi5pbyU1QzBwYXNzd29yZA== ) )
對於純 svn 協議來講,使用 PLAIN 並不安全,且當 Subversion 只做爲 GIT 代碼託管平臺的一個服務來講, 使用 CRAM-MD5 並不利於服務整合,這也是一個缺陷了。
這是服務器的下一步驟:
S: challenge: ( step ( token:string ) ) S: | ( failure ( message:string ) ) S: | ( success [ token:string ] )
Incorrect credentials:
( failure ( 21:incorrect credentials ) )
Success
( success ( ) )
隨後服務器再發送存儲庫 UUID, capabilities 給客戶端
S: ( uuid:string repos-url:string ( cap:word ... ) )
Example:
( ( 36:0f475597-c342-45b4-88c5-7dc0857b8ba4 36:svn://subversion.io/subversion/trunk ( edit-pipeline svndiff1 absent-entries depth inherited-props log-revprops ))
若是是 svn up/commit 或者其餘的操做,這個時候會檢查 uuid 是否匹配,固然也會檢查 URL 是否匹配。
若是客戶端以爲一切都 OK 啦,那麼就會開始下一階段的操做,command 模式,這些規則能夠從 Subversion 官方存儲庫查看 Subversion Protocol
與 GIT 或者 SVN HTTP 不一樣的是,一個完整的 基於 svn 協議的 SVN 操做,只須要創建一次 socket,Subversion 客戶端此時是阻塞的,而且屏蔽了 Ctrl+C 等 信號, 倉庫體積巨大時,這種對鏈接資源的佔用很是突出,由於有數據讀取, socket 並不會超時。這樣的機制使得 svn 服務器的併發受到了限制。
###Subversion 兼容實現 Github 基於 HTTP 協議的方式實現了對 Subversion 的兼容,而 GIT@OSC 基於 svn 協議方式實現了對 Subversion 的不徹底兼容。
基於 HTTP 協議實現的 Subversion 兼容服務和 基於 SVN 協議的 Subversion 兼容服務兩者並不能說誰就必定好,HTTP 協議很容易致使網關超時, 多大數狀況下,一次完整的操做時成千上萬的 HTTP 請求構成,HTTP 協議支持須要 HTTP 服務器可以支持 WebDAV, XML 解析過程比較麻煩, Subversion 官方也計劃使用 HTTP v2 取代 WebDAV,但 HTTP 協議的好處仍是有的,好比不少企業並不必定開放 SVN 端口 3690, 能夠和 gitlab 之類的服務整合。
而 SVN 協議也有很差的地方,好比鏈接時間過長,服務器併發上不去,容易阻塞,與 HTTP 服務整合不便,但同時 SVN 協議可以支持較大存儲庫。
實際上兼容實現 SVN 接入每每沒有原生的 SVN 服務好,這點事毋庸置疑的。
###Subversion 協議代理服務器的實現 前面並不徹底的分析了 SVN 協議,可是那些協議內容足夠實現一個 SVN 協議動態代理服務器了。
在客戶端 C 和代理服務器 S 創建鏈接後, S 向 C 發送一個數據包:
#S to C ( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries depth inherited-props log-revprops ) ) )
C 接收到 S 的數據後,必須作出選擇,併發送第一個請求給 S。
#C to S ( 2 ( edit-pipeline svndiff1 absent-entries depth mergeinfo log-revprops ) 43:svn://subversion.io/apache/subversion/trunk 53:SVN/1.8.13-SlikSvn-1.8.13-X64 (x64-microsoft-windows) ( ) )
S 接收到 C 的請求後,解析 數據包,提取到 URL 爲 svn://subversion.io/apache/subversion/trunk , 而 Gitlab 的規則是 host/user/repo, 若是不一樣用戶的存儲庫放在不一樣機器上,這個時候提取到用戶爲 apache, 交由路由選擇模塊去處理獲得後端的地址,也就是真實 svnserve 的 IP 和端口。
創建與後端服務器 B 的鏈接。這個時候 S 讀取 B 的數據包,也就是前面的服務器頭,接收完畢直接丟棄便可,而後將客戶端 C 的頭請求轉發給後端服務器。
#S to B ( 2 ( edit-pipeline svndiff1 absent-entries depth mergeinfo log-revprops ) 43:svn://subversion.io/apache/subversion/trunk 53:SVN/1.8.13-SlikSvn-1.8.13-X64 (x64-microsoft-windows) ( ) )
這裏值得注意的是 svnkit,Subversion Javahl 並無添加 UA 字符串,因此解析時略過便可。
至此,代理服務器的後面就沒必要關係細節了,GIT@OSC 使用 Boost.ASIO 異步框架,
Client <---> Proxy Server <---> Backend Subversion Server
一個基本的 SVN 協議動態代理服務器就實現了。
##結尾 若是你不是專業的 Git 或者 Subversion 開發者,你可能會以爲上面的內容沒什麼用處,實際上也沒什麼技術難度。