現象,近年來,我們對版本控制工具的關注點似乎正在改變.起初,我們主要也是唯一的目的就是對代碼進行監控,使我們能夠安全的返回到舊的版本,以便我們能夠診斷代碼中的問題.後來,我們的關注點更側重於如何使人與人之間的合作更為順暢. 這個關注點並不是要取代對代碼的監控,而是以代碼監控為基礎,並建立於其上的.現在,我們又越來越關注使用這些工具來描述代碼的變更,因此就出現了對於重寫代碼曆史命令(history rewriting command)的需求.當然,對代碼變更的描述也同樣需要建立在前兩種關注點之上. 我們可以把版本控制工具的應用分為六個層次: 0. 沒有版本控制 完全沒有版本控制的解決方案,或者就使用一個共用的檔案系統,並對其做週期性備份.一個開發人員,或者最多幾個開發人員在沒有工具的情況下共用代碼,其面臨的風險可想而知:
- 在任何時刻,代碼可能都是不相容的.
- 代碼可能會由於開發人員的錯誤面丟失.
- 如果開發人員想要改寫覆蓋他人所做的修改,那再容易不過了.
1. 初步的探索
- 開發人員擁有了網路上的工作空間,他們無法線上下工作.
- 運行一次代碼構建也許就意味著有時間可以去好好吃一頓了.
- 重構即使能夠進行,也慢的要死.
- checkout代碼可能需要一整夜.
- checkin代碼也很慢.
- 沒有原子提交.
- Branching和Tagging操作的代價昂貴.
- 個人或是本地建立branch就意味著再次的checkout.
- 集中式的,而不是公布式的.
- 合并點的跟蹤很慢或是根本無法使用.
- 這時的工具還沒有辦法理解重新命名的合并.
- 程式碼程式庫有時會崩潰,需要較高的專家/開發人員比例,如1:10.
這時的工具有了基本的版本控制功能,如checkout,版本記錄和鎖檔案.通常這就意味著開發人員在同一份代碼上工作,而代碼的同步就會依賴於每個人代碼檔案的鎖狀態.這種工具在擴充和長期工作上都會有問題.對資源的重新命名難到幾乎不可能完成.Branching和Tagging操作則會需要同時操作三份代碼的許可權,而且可能還需要一個宰好的或者兩柱香.如, VSS. 2. 笨拙
- 開發人員有了本地的拷貝,並且可以線上下工作.
- 本地的檔案系統意味著構建的速度大大提升.
- 重構的時間只夠喝杯茶了.
- checkout的速度已經非常快了.
- checkin也許還是慢一點.
- 仍然沒有原子提交.
- Branching和Tagging代價仍然昂貴.
- 集中式的,不是分布式的.
- 合并點的跟蹤很慢或是根本無法使用.
- 沒有辦法合并重新命名檔案,需要在提交前使用一些擴充的跟進衝突解決機制.
- 程式碼程式庫有時會崩潰,專家/開發人員比例已經得到最佳化,如1:20.
如CVS和TFS. 3. 基本成型
- 開發人員擁有本地拷貝並且可以線上下工作.
- 在本地檔案系統上可以進行快速的構建.
- 可以快速的重構.
- checkout和checkin的速度都會非常的快.
- 終於有了原子提交.
- 輕量級的Branching和Tagging操作.
- 基本的合併作業.
- 個人/本地的branching操作仍然需要再次checkout.
- 因為仍然是集中式的,而不是分布式的.
- 基本的合并點追蹤.
- 沒有辦法合并重新命名檔案,需要在提交前使用一些擴充的跟進衝突解決機制.
- 程式碼程式庫有時會崩潰,專家/開發人員比例已經很低,如1:100.
如Subversion. 4. 有效並且可靠
- 開發人員擁有本地拷貝並且可以線上下工作.
- 在本地檔案系統上可以進行快速的構建.
- 可以快速的重構.
- checkout和checkin的速度都會非常的快.
- 無操作的代碼同步和更新非常的快速.
- 終於有了原子提交.
- 輕量級的Branching和Tagging操作.
- 進階的Branching和合併作業.
- 個人/本地的branching操作仍然需要再次checkout
- 因為仍然是集中式的,而不是分布式的.
- 完善的合并點追蹤.
- 合并重新命名檔案只能通過配置好的branch映射來實現,否則就需要在提交前進行修訂.
- 程式碼程式庫很少會崩潰,專家/開發人員比例非常低,如1:1000.
如Perforce. 5. 高速,無形,高度可用
- 開發人員擁有本地拷貝並且可以線上下工作.
- 在本地檔案系統上可以進行快速的構建.
- 可以快速的重構.
- checkout和checkin的速度都會非常的快.
- 無操作的代碼同步和更新非常的快速.
- 終於有了原子提交.
- 輕量級的Branching和Tagging操作.
- 進階的Branching和合併作業.
- 非常高效的個人/本地branching操作.
- 分布式的,而不是集中式的.
- 完善的合并點追蹤.
- 無縫合并重新命名檔案,無需任何配置.
- 程式碼程式庫很少會崩潰,專家/開發人員比例幾乎為零,如1:10000.
如Git和Mercurial. 通過前面版本控制工具的演化過程,我們基本上可以看到分布式工具的特點和優勢了.相對於以往的客戶-伺服器端的集中式系統,它所採用的是一種P2P的方式.用戶端不再需要從一個單一的中央程式碼程式庫同步代碼,每一個端點的代碼的拷貝都是真正的程式碼程式庫.分布式的版本控制系統是通過端點之間交換補丁(patch)的方式來同步代碼的,而這種方式就決定了分布式系統與集中式系統的幾個重要的區別:
- 預設情況下,沒有標準的程式碼程式庫參照;只有工作代碼的拷貝.
- 由於不需要與中央伺服器進行通訊,因此一般的操作(如提交,查看曆史和還原修改等)的執行速度非常快.只有在向其它端點push代碼更改或者從其它端點pull代碼更改的時候才會需要進行通訊.
- 每一份代碼拷貝都可以作為程式碼程式庫及其更改曆史的一份遠程備份,這就為資料丟失提供了天然的保護.
- 鼓勵測試性的branch - 建立或者銷毀branch的操作簡單而且快速.
- 同伴之間的合作變得非常容易
現在的項目用的是Subversion做版本控制,但是我們自己用的是git-svn,這裡主要通過這兩種有代表性的工具來比較一下集中式和分布式工具的優勢. Subversion提倡單一的中央程式碼程式庫模型,不提倡大規模的branching.在一個使用持續整合的環境中,其實也就是我們每天工作的環境,這個模型是非常合適的.這也是Subversion為什麼這麼流行,應用範圍這麼廣的原因之一. 雖然分布式的系統使你擁有足夠的靈活性來自己安排自己的工作流程,但是其實大多數人的工作模式仍然是使用持續整合,這也就意味著需要一條可以共用程式碼庫主線.現在的版本控制系統已經有了神奇的合并工具,但這些合并仍然只限於文本.所以對於語義上的一致,仍然需要持續整合來保證.結果就是即使一個團隊在使用分布式的版本控制系統,他們仍然會需要一個主要版本庫. 即使如此,分布式系統仍擁有一些SVN無法提供的體驗.
- 在分布式管理系統中,你可以在自己本地磁碟上擁有程式碼程式庫的完整拷貝,對程式碼程式庫的操作不需要通過網路向中央伺服器進行請求,因此速度會非常的快.特別是你在進行查看日誌,與舊版本代碼進行比較或者其它需要完整程式碼程式庫的操作時,這種速度上的改善會非常明顯.對於集中式的系統,在區域網路內你也許只會覺得有點慢,但如果當你工作在一個分布式的項目中,你的程式碼程式庫在另一個大洲的時候,這就會是非常大的問題了.
- 如果你經常在四處奔走,無法隨時與程式碼程式庫建立網路連接,那麼一個分布式的管理系統會使你可以隨時與程式碼程式庫一起工作.你可以隨時隨地提交你的工作,瀏覽曆史,並且在比較版本間的差異.
- 還有一項體驗也許並不能說是一個工具問題,而更多的是一個社會問題.分布式版本控制工具鼓勵快速的branching和實驗.在SVN中,你當然也可以進行branching操作,但是你所做的操作對於其它在這個程式碼程式庫上工作的人員也是可見的, 這也許並不是什麼大問題,但確實會降低人們進行一些實驗性工作的慾望.分布式系統則會鼓勵你為工作代碼進行記錄:你可以向你的本地程式碼程式庫提交未完成的修改,甚至是無法通過測試和無法編譯的代碼.你同樣可以在SVN中進行這些操作,但是在公用空間中建立這些branches總是讓人望而卻步.
在某種特定情況下,SVN也有其相應的優勢,如果你需要對版本控制系統難以合并的二進位檔案(如word文檔或者ppt)進行管理的話,你就應該回退到獨佔式checkout的鎖機制下,這就需要一個集中式的系統.另外,SVN更容易上手:你有一個程式碼程式庫,所有的更改都指向這個程式碼程式庫,如果你知道如何建立,提交以及checkout,那你就可以開始使用它了,而像branching,更新這些操作在使用過程中自然也就慢慢熟悉了.SVN擁有一些非常好用的用戶端軟體,而且幾乎所有的主流IDE都有與SVN整合的外掛程式,這些都能夠為你使用SVN提供很大的協助. Git則增加了複雜性,似乎總是有兩種模式來進行操作,checkout和clone, commit和push...... 你得知道哪些命令是針對本地進行操作的,哪些命令是對伺服器或者是主程式碼程式庫進行操作的. 實際上,Git的命令和思維模式與其它的版本控制系統是有所不同的.Bulter Cole曾這樣形容Git:"它是一個神奇的功能強大的東西,它幾乎可以做任何你讓它做的事情,只有你知道如何讓它做".Git的反對者也會抱怨Git缺少可發現性,你很難從它的表面設計推斷出它的行為.而Git的支援者則認為這隻是因為Git使用了不同於其它系統的思維模式,你需要先忘掉以前那些關於版本控制系統的知識才能更好的來欣賞Git.無論如何,Git對於那些喜歡研究事物內部工作機制的人來說,還是非常有吸引力的. 通常來講,Git相比Mercurial在處理branching方面表現更好,尤其是用於實驗和檢查點的短期branch.Mercurial提倡是另一種機制,例如快速的clone一份程式碼程式庫或者是使用補丁,但是Git的branching模式更為簡單好用.Mercurial在處理大型二進位檔案時同樣有問題.通常的建議是使用SVN來管理二進位檔案,如果你只有很少的二進位檔案需要管理,不值得建立單獨的管理機制的話,Mercurial將就著也能處理. 此外Git之所以能在網路上引起如此多的共鳴,有一個很大的原因是Git對於開源項目來說是完善的選擇.你可以建立一個項目分支,向你自己的項目分支提交修改,然後讓項目維護人員將你的修改pull過去.有了Git,這一切就變得如此的方便和自然.即使你沒有向項目提交修改的許可權,你也可以線上上建立你自己的程式碼程式庫,將你自己的補丁發布出來,任何喜歡你補丁的人都可以將它們pull到他們自己的程式碼程式庫中,當然也包括項目維護人員. Git有一個被稱為"staging area"的地區.在你向程式碼程式庫提交之間, 你可以在這個中間地區中構建你的提交.更為重要的是,你可以只提交部分的修改,而不是將所有修改的檔案都進行提交.你甚至可以只提交一個檔案中修改的一部分. Git非常的靈活,非常的TIMTOWTDI(There is more than one way to do it). 你可以使用任何你喜歡的工作流程,Git都會對其提供支援. 當前主要的工作流程有以下幾種: 1. SVN形式 集中式的工作流程,這也是一種非常普遍的Git的工作流程.如果在你上一次fetch代碼之後有其他人進行了提交,那麼Git將不允許你向主程式碼程式庫中push你的代碼. 2. 整合管理形式 在這個工作流程中有一個整合管理員,他向"blessed"程式碼程式庫進行提交,其他開發人員從這個程式碼程式庫clone代碼,在他們自己的程式碼程式庫中push修改,並讓整合管理員pull他們的修改.這其實就是大多數開源項目和GitHub所經常使用的開發模式. 3. 獨裁者和中尉形式 對於更大規模的項目來說,你可以將開發人員的開發模式設定成類似於Linux核心的開發模式.某些人會負責項目的某個特定的子系統(中尉),並且將所有關於這個子系統的修改都進行合并.另外會有一個整合者(獨裁者)可能從他/她的中尉那裡pull代碼的修改,並將"blessed"程式碼程式庫進行提交.而所有人都可以從"blessed"程式碼程式庫進行代碼的拷貝. 再次強調,Git對於工作流程的支援非常的靈活,你可以根據自己的需要來匹配,混合,選擇這些工作流程. 我們再來看看相對於SVN,Git還有哪些優點吧:
- Git有一個"clean"命令.SVN急需這個命令.
- Git有一個"bisect"命令.
- SVN會在每一個檔案夾中建立一個.svn目錄.而Git只會建立一個.git目錄.
- 在SVN中,每一個檔案或檔案夾都可能來自於一個不同的版本或是branch.這很可能會引起混亂.
- 無論你什麼時候刪除了點東西,你都需要告訴SVN一聲.Git會自己發現並處理.
- 在Git中,忽略文法很簡單,例如*.pyc,它會被應用到所有的子檔案夾.當然,如果你只想忽略某個特定檔案夾中的內容,也是可以的.在SVN中,很難有什麼方法可以將一個忽略模式應用到所有的子檔案夾中.
- Git中忽略設定是"private"的,這些設定包含在.git/info/exclude中,並不會影響到其他人.
- Git跟蹤的是內容而不是檔案,它對於重新命名檔案的合并有更好的支援.
- Git程式碼程式庫的大小相對於SVN來說小很多.
- 在Git中,你可以重寫曆史.在你提交前,這會對準備補丁集以及修改以前的錯誤提供很大的協助.
- 另外還有一點是在我們目前這個特殊的環境中所遇到的問題,我們每天都在結對程式設計,而且我們會經常的更換pair,甚至是在一個story做到一半的時候.而這裡代碼並沒有完整的提交,而且只存在在一台機器上,而這台機器的主人也許會需要去做其它的story.這時如果使用git-svn的話就會很方便.因為我們不需要將沒有完成,甚至不能編譯的代碼提交到SVN上去.
據說目前Git不支援程式碼程式庫的部分checkout/clone,但是正在開發中,而且已經有submodule方面的支援.SVN則可以根據需要只從程式碼程式庫中checkout某個子檔案夾.SVN的版本號碼更短並且可以預知,而Git的版本號碼則是40位的16進位數字串.而Git在Branch方面的處理應該是有很大的優勢,但是由於我目前為止幾乎沒有使用過branch,所以這一部分還沒有深刻的體會. |