亂談版本控制軟件 頂 原

本控制(Revision control)是維護工程藍圖的標準作法,能追蹤工程藍圖從誕生一直到定案的過程。此外,版本控制也是一種軟件工程技巧,藉此能在軟件開發的過程中,確保由不同人所編輯的同一程式檔案都得到同步。

如果沒有版本控制軟件,團隊化的軟件開發將是個巨大的災難,而解決這一切的是版本控制系統,版本控制系統的意義在於使代碼的變更變得可控。

版本控制軟件的歷史可以追溯到上世紀70年代,最早是CA Software Change Manager( Harvest/CCC) ,這一版本系統是Santa Barbara CA爲軍方開發的。有趣的是很多軟件都是從軍方流出來的。
從此開始,版本控制系統的種類越來越多:

版本控制年譜

<table> <caption><em>版本控制軟件</em></caption> <tr> <td></td> <td>開源/免費</td> <td>私有軟件</td> <td>主流</td> </tr> <tr> <td>本地</td> <td>SCCS (1972) RCS (1982) </td> <td>PVCS (1985) QVCS (1991)</td> <td>RCS</td> </tr> <tr> <td>集中式</td> <td>CVS (1986, 1990 in C) CVSNT (1998) QVCS Enterprise (1998) Subversion (2000)</td> <td>Software Change Manager (1970s) Panvalet (1970s) Endevor (1980s) DSEE (1984) Synergy (1990) ClearCase (1992) CMVC (1994) Visual SourceSafe (1994) Perforce (1995) StarTeam (1995) Integrity (2001) Surround SCM (2002) AccuRev SCM (2002) SourceAnywhere (2003) Vault (2003) Team Foundation Server (2005) Team Concert (2008) </td> <td>Subversion</td> </tr> <tr> <td>分佈式</td> <td>GNU arch (2001) Darcs (2002) DCVS (2002) ArX (2003) Monotone (2003) SVK (2003) Codeville (2005) Bazaar (2005) Git (2005) Mercurial (2005) Fossil (2007) Veracity (2010) </td> <td>TeamWare (1990s?) Code Co-op (1997) BitKeeper (1998) Plastic SCM (2006)</td> <td>Git</td> </tr> </table>

版本控制系統種類繁多,流行版本控制系統一般是如下幾種:

  • BitKeeper
  • 協作版本系統|CVS] (Concurrent Versions System)
  • Micorosoft Visual SourceSafe/Team Foundation Server/Visual Studio Online
  • Perforce
  • Rational ClearCase
  • RCS(GNU Revision Control System)
  • Serena Dimention
  • Subversion
  • SVK
  • Git
  • Monotone
  • Bazaar (software)
  • Mercurial
  • SourceGear Vault

歷史往往會留下蛛絲馬跡,或許我們能從中找到答案,集中式版本控制系統的迭代週期較長,而分佈式的版本控制系統卻更加活躍,企業需要穩定的保守的策略,而社區需要冒險精神。

很有趣的是2005年誕生了多款版本控制軟件,如果我們翻閱歷史可以發現,Linux和BitKeeper的決裂正是那個時候,有興趣的可以看一看。
開源中國翻譯: Git 10 週年之際,創始人 Linus Torvalds 訪談

支持分佈式系統的人認爲:

在 DVCS 和集中式版本控制系統之間有三個關鍵差異。第一個差異是,DVCS 通過本地提交支持離線工作,這是由 DVCS 的操作方式決定的。這與集中式版本控制完全不同,集中式版本控制要求通過到中心服務器的連接執行所有操作。這種靈活性讓開發人員在飛機上也能夠像在辦公室中一樣輕鬆地工作,可以一次又一次地進行提交。 第二個差異是 DVCS 比集中式系統更靈活,因爲 DVCS 支持許多不同類型的工作流,從傳統的集中式工作流到純粹的特殊工作流,再到特殊工作流和集中式工作流的組合。這種靈活性允許通過電子郵件、對等網絡和開發團隊喜歡的任何方式進行開發。 第三個差異是 DVCS 比集中式版本控制系統快得多,因爲大多數操作在客戶機上進行,速度非常快。另外,在需要進行推(push )操作(與另一個節點通信)時,速度也更快,因爲兩個客戶機機器上都有完整的元數據。速度差異相當顯著,根據使用本地存儲庫還是網絡存儲庫,DVCS 比 Subversion 快大約 3-10 倍。

IBM developerWorks: 分佈式版本控制系統入門

當然,從另一方面講,集中式版本控制對於商業軟件來說,代碼是可控的,完整的倉庫放在服務器上,並不是每個人都有完整的權限可以將所有代碼拉取下來,修改或者是合併都顯得小心翼翼。企業對此很樂意看到。
人們很容易覺得集中式版本控制具有更強的掌控能力。 集中式版本控制系統大成者非Subversion莫屬,而分佈式版本控制系統目前幾乎由git掌控天下。
到目前爲止,很難再出現一個集中式版本控制系統超越Subversion的,而Subversion內部實現卻可以。

###Subversion實現 Subversion實際上是循規蹈矩的,所作的一切努力全部是爲了取代CVS,在協議的設計上基本上是按照對應的IETF制定的標準。
Subversion主要的傳輸協議有3類,本地的file 以及svn(svn+ssh)還有http(https)。 Subversion的SVN協議,遵循ABNF標準,ABNF 即擴充巴科斯-瑙爾範式

擴充巴科斯-瑙爾範式(ABNF)是一種基於巴科斯-瑙爾範式(BNF)的元語言,但它有自己的語法和派生規則。ABNF的原動原則是描述一種作爲雙向通信協議的語言的形式系統。它是由第68號互聯網標準("STD 68",大小寫樣式按照原文)定義的,也就是RFC 5234,經常用於互聯網工程任務組(IETF)通信協議的定義語言。——維基百科

使用SVN協議一般是運行svnserver服務,svnserver監聽3690端口,svn客戶端將URL等信息依照ABNF規則將數據封裝發送給服務器,服務器將數據解析,解析依照命令讀取倉庫信息,按照ABNF格式返回數據給客戶端。 svn傳輸主要的命令有

  • commit
  • diff
  • get-locations
  • check-path
  • update
  • other.......

服務端根據不同的命令執行相應的動作,實際上和http協議的流程是一樣的。
這些命令有的只是詳細的列出倉庫指定的信息,或者獲得倉庫文件,有的會對倉庫的修改,在設計svn服務器的時候可以據此對用戶接入權限控制進行設定。

更多的命令和技術細節可以參看svn協議文檔:
http://svn.apache.org/repos/asf/subversion/trunk/subversion/libsvn_ra_svn/protocol

Subversion的HTTP協議使用WebDAV。

  • RFC 2518 (WebDAV)
  • RFC 3253 (DeltaV)

WebDAV是HTTP 1.1的擴展協議,但它一種複雜的難以解析的協議,所以完全支持WebDAV的服務器並不多,更遑論DeltaV。支持WebDAV的有IIS,Apache httpd。Nginx只部分支持WebDAV,編譯參數--with-http_dav_module 而Github上有nginx模塊,支持WebDAV的PROPFIND和OPTIONS方法:nginx-dav-ext-module

WebDAV與Subversion結合使得問題變得較爲複雜,使用其他服務器,比如Nginx,WebDAV的方法要額外實現。有人在Nginx右鍵列表曾經諮詢過,支持SVN需要

full WebDAV support
DeltaV support
SVN repo format support

不過以Igor Sysoev的尿性,堅決不鳥。 SVN的URL或許帶有特殊的關鍵字,這些對應特定的操作,關鍵字如下:

!svn/rvr
!svn/txr
!svn/me
!svn/bln
!svn/blah
!svn/vcc
!svn/bc
!svn/act
!svn/bc
!svn/wrk

對應的細節:

Version Controlled Resource (VCC): !svn/vcc
Baseline resource: !svn/bln
Working baseline resource: !svn/wbl
Baseline collection resource: !svn/bc/REV/
Activity collection: !svn/act/ACTIVITY-UUID/
Versioned resource: !svn/ver/REV/path
Working resource: !svn/wrk/ACTIVITY-UUID/path

比如!svn/rev就可以用來diff比較差異,比較差異後,服務端將diff添加到HTTP包體中,客戶端解析xml文件後,再解析diff。

Example
+++++++

Request:

GET /repos/test/!svn/ver/3/httpd/configure HTTP/1.1
X-SVN-VR-Base: /repos/test/!svn/ver/2/httpd/configure
Accept-Encoding: svndiff1;q=0.9,svndiff;q=0.8

Response:

HTTP/1.1 200 OK
ETag: "3//httpd/configure"
Vary: Accept-Encoding
Content-Type: application/vnd.svn-svndiff

Subversion的HTTP協議使用WebDAV,一切都離不開xml,按照現在流行JSON的說法,這種老掉牙的XML解析起來非常麻煩,而且還浪費帶寬,比如svn log:

log-report

Purpose: Retrieve the log for a portion of the repository.

Target URL: Current baseline collection for a directory plus relative paths. Example: REPORT /repos/test/!svn/bc/5/httpd/support

Request:

<S:log-report xmlns:S="svn:"> <S:start-revision>2</S:start-revision> <S:end-revision>2</S:end-revision> <S:limit>1</S:limit> (optional) <S:discover-changed-paths/> (optional) <S:strict-node-history/> (optional) <S:include-merged-revisions/> (optional) <S:encode-binary-props> (optional) <S:revprop>REVPROP</S:revprop>... | <S:all-revprops/> | <S:no-revprops/> ('revprop', 'all-revprops', and 'no-revprops' are all optional) <S:path></S:path>... (optional) </S:log-report>

Response:

<?xml version="1.0" encoding="utf-8"?> <S:log-report xmlns:S="svn:" xmlns:D="DAV:"> <S:log-item> <D:version-name>2</D:version-name> <S:creator-displayname>bob</S:creator-displayname> <S:date>2006-02-27T18:44:26.149336Z</S:date> <D:comment>Add doo-hickey</D:comment> <S:revprop name="REVPROP">value</S:revprop>... (optional) <S:revprop name="REVPROP" encoding="base64">encoded value</S:revprop>... (optional) <S:has-children/> (optional) <S:added-path( copyfrom-path="PATH" copyfrom-rev="REVNUM">PATH</S:added-path>... (optional) <S:replaced-path( copyfrom-path="PATH" copyfrom-rev="REVNUM">PATH</S:replaced-path>... (optional) <S:deleted-path>PATH</S:deleted-path>... (optional) <S:modified-path>PATH</S:modified-path>... (optional) </S:log-item> ...multiple log-items for each returned revision... </S:log-report>

XML文檔需要大量的標籤,這在傳輸過程中需要佔用大量的帶寬,而且XML的解析並不方便,大多數解析器依然需要大量的代碼去解析對應的業務和邏輯,開發者一般覺得JSON更容易解析。目前很多網絡服務都開始由XML轉向到JSON傳輸。我倒覺得下一代Subversion可以使用JSON去重構HTTP業務。

Subversion具體的WebDAV協議內容地址如下: http://svn.apache.org/repos/asf/subversion/trunk/notes/http-and-webdav/webdav-protocol

隨着HTTP 2.0的發佈,SVN開發者也開始思考移除WebDAV,使用HTTP 2.0完全取代WebDAV. HTTP 2.0路線計劃:
http://svn.apache.org/repos/asf/subversion/trunk/notes/http-and-webdav/http-protocol-v2.txt

Subversion的佈局 完整的檢出一個Subversion倉庫,在工作目錄下執行tree命令,得到如下佈局:

SubversionLayout

正常情況下,svn總是這個樣子,trunk-branches-tags, 所以,我們常常可以得到一些URL,URL的規則和Subversion重要的特性,也就是部分檢出,有密切的關係,如果我們使用 “svn co http://svn.apache.org/repos/asf/subversion subversion ” 這個命令將檢出所有的分支與標籤,也就是Subversion目錄下的所有文件。如果用下面的命令就是指定分支或者標籤。

http://svn.apache.org/repos/asf/subversion/trunk
http://svn.apache.org/repos/asf/subversion/1.9.x
http://svn.apache.org/repos/asf/subversion/1.8.13

然後Subversion的分支管理是薄弱的。我們使用“svn list http://svn.apache.org/repos/asf” 發現很有意思的一點,Apache的大部分項目存在一個巨大的Subversion倉庫中。
我們可以簡單的認爲,Subversion是一個有版本記錄的有權限控制的文件系統,比如http://svn.apache.org/repos/asf mount 到 / 。

基於SVN開發的一般流程:
SubversionLayout2

SVN文件改變對應的標識

[url=]  
L      abc.c               # svn已經在.svn目錄鎖定了abc.c                
M      bar.c               # bar.c的內容已經在本地修改過了               
M      baz.c               # baz.c屬性有修改,但沒有內容修改            
X      3rd_party           # 這個目錄是外部定義的一部分           
?      foo.o               # svn並沒有管理foo.o              
!      some_dir            # svn管理這個,但它可能丟失或者不完整          
~      qux                 # 作爲file/dir/link進行了版本控制,但類型已經改變             
I      .screenrc           # svn不管理這個,配置確定要忽略它           
A  +   moved_dir           # 包含歷史的添加,歷史記錄了它的來歷            
M  +   moved_dir/README    # 包含歷史的添加,並有了本地修改            
D      stuff/fish.c        # 這個文件預定要刪除            
A      stuff/loot/bloo.h   # 這個文件預定要添加            
C      stuff/loot/lump.c   # 這個文件在更新時發生衝突            
R      xyz.c               # 這個文件預定要被替換           
S      stuff/squawk        # 這個文件已經跳轉到了分支              

Subversion 路線圖:
svnroadmap

###Git

實際上git-scm網站已經把git的實現講的清清楚楚,如果再多的去描述git如何如何,那樣意義也不大,換個角度,研究git與svn差異已經兼容性實現倒可以扯幾句。
Git對文件修改更加較敏感,存在工作目錄的文件都會被Git及時的發現,並且要求用戶添加到暫存區。 而對subversion來說,這一切是惰性的,如果目錄中有一個文件沒有被納入版本控制也沒有被排除,svn會消極的對待它。 當文件已經被納入版本控制,文件被修改後提交,svn可以直接commit,而git每次都需要添加修改到暫存區。然後再修改。

Git版本控制中目錄不作爲一個單獨的對象被添加到倉庫,工作目錄下的目錄至少得找到一個文件纔會被納入版本控制。

Git不像Subversion,git每一次修改不是保存的差異,而是文件本身,正是這個特性,當一個項目的提交數目非常大時,版本庫的體積也變得巨大了。比如有些用戶使用PhotoShop產生的psd文件,若干次修改後,倉庫的體積 就變得無法忍受的。

由於Git的特性,直接支持大文件是不切實際的,但辦法不是沒有,目前Github已經開源了一個git擴展 git-lfs 實現Git的大文件託管。 使用git的svn轉換工具轉換svn倉庫時,這個問題就變得明顯了。

然而,Git的特性帶來的好處顯而易見,倉庫解析速度快,比如git checkout branchA之類基本不耗很長時間。

####從libgit2 看Git實現 如果要從源碼去研究GIT是如何實現的,沒有久經考驗之前,我會選擇libgit2,開源中國軟件收錄了libgit2: http://www.oschina.net/p/libgit2/

https://libgit2.github.com/libgit2/ 有libgit2的API文檔,如果有需要的可以查看。我們可以從一些枚舉類型中找到一些git的內幕。

Git Object 類型

typedef enum {
GIT_OBJ_ANY = -2, GIT_OBJ_BAD = -1, GIT_OBJ__EXT1 = 0,
GIT_OBJ_COMMIT = 1,
GIT_OBJ_TREE = 2,
GIT_OBJ_BLOB = 3,
GIT_OBJ_TAG = 4, GIT_OBJ__EXT2 = 5, GIT_OBJ_OFS_DELTA = 6,
GIT_OBJ_REF_DELTA = 7,
} git_otype;

Git 引用類型

typedef enum {
GIT_REF_INVALID = 0,
GIT_REF_OID = 1,
GIT_REF_SYMBOLIC = 2,
GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC,
} git_ref_t;

Git 分支類型

typedef enum {
GIT_BRANCH_LOCAL = 1,
GIT_BRANCH_REMOTE = 2,
GIT_BRANCH_ALL = GIT_BRANCH_LOCAL|GIT_BRANCH_REMOTE,
} git_branch_t;

比如說git_filemode_t ,這些都呵Unix的文件屬性有關係。比如GIT_FILEMODE_BLOB_EXECUTABLE的值就是0100755,比如我們使用chmod命令可以將文件輕鬆的賦予可執行權限,這個值正好是755。

$>chmod 755 hello.sh
等價 chmod +x hello.sh

從這些信息我們可以知道一些問題是如何出現的。

無論是Github的Subversion 還是OSC@GIT的Subversion都無法避免的一個問題,無法提交空目錄,在Git的概念裏,目錄是一個tree,如果這個tree無法向下遍歷到任何一個文件,也就是blob,commit,link,那麼這個目錄就不會被納入到版本控制裏面去。這個問題產生 的原因是:目前設計的 Git 索引 (臨時區域) 只允許文件列出,並沒有足夠的能力做出改變,以便允許空的目錄有照料不夠了解這種情況進行補救。

這可以看成是一種失誤吧:https://git.wiki.kernel.org/index.php/GitFaq#Can_I_add_empty_directories.3F

Git 文件類型

typedef enum {
GIT_FILEMODE_UNREADABLE = 0000000,
GIT_FILEMODE_TREE = 0040000,
GIT_FILEMODE_BLOB = 0100644,
GIT_FILEMODE_BLOB_EXECUTABLE = 0100755,
GIT_FILEMODE_LINK = 0120000,
GIT_FILEMODE_COMMIT = 0160000,
} git_filemode_t;

git中有submodule的概念,其中對應的文件類型正是GIT_FILEMODE_COMMIT。

目前libgit2支持的安全協議有x509與ssh v2。

typedef enum git_cert_t {
GIT_CERT_X509,
GIT_CERT_HOSTKEY_LIBSSH2,
} git_cert_t;

Git 子模塊

typedef enum {
GIT_SUBMODULE_UPDATE_RESET = -1,
GIT_SUBMODULE_UPDATE_CHECKOUT = 1,
GIT_SUBMODULE_UPDATE_REBASE = 2,
GIT_SUBMODULE_UPDATE_MERGE = 3,
GIT_SUBMODULE_UPDATE_NONE = 4,
GIT_SUBMODULE_UPDATE_DEFAULT = 0
} git_submodule_update_t;

如果你要從一次commit拿到一個特定的文件,我會選擇,先拿到commit id,然後找到root tree-entry的object id,從這個tree entry拿到指定path的tree-entry id,查看文件類型git_tree_entry_filemode_raw,如果是個blob,就使用git_blob_rawcontent查看文件內容。

事實上,也不要對libgit2抱有太高的期望,畢竟libgit2 1.0遙遙無期,而官方的git還在快速迭代,API也在不斷的調整。

關於libgit2的問題,可以在 Stackoverflow 問答網站找到更多的解答: http://stackoverflow.com/questions/tagged/libgit2

git的大火離不開github,github的成功git功不可沒,git的基礎設施,如git,msysgit,libgit2,都有github員工踊躍的參與,其中libgit2 msysgit基本上還是github員工貢獻的代碼。

###其他 說不定,下一代版本控制系統悄然誕生。

1.其他的版本控制系統可查看維基百科:
http://en.wikipedia.org/wiki/List_of_revision_control_software

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章