版本控制入門插圖教程

阮一峰發表於2008-12-30

我知道版本控制系統(VCS)很有用。

但是,我平時只是業餘寫一些小程式,感覺特地裝一個VCS太麻煩,所以一直沒有用。最近,因為想認真做一箇中等規模的專案,所以決心好好學一下怎麼用。

下面就是我翻譯的一篇入門教程,主要解釋了VCS的一些主要概念。

======================

A Visual Guide to Version Control

版本控制入門插圖教程

作者:Kalid Azad

譯者:阮一峰

原文網址:http://betterexplained.com/articles/a-visual-guide-to-version-control/

版本控制入門插圖教程

版本控制(Version Control)的作用是追蹤檔案的變化。為什麼需要版本控制?簡單說,就是當你出錯了,可以很容易地回到沒出錯時的狀態。

你可能已經在不知不覺中,佈置了自己的版本控制系統。比如,建立了類似下面這樣的檔名:

* KalidAzadResumeOct2006.doc

* KalidAzadResumeMar2007.doc

* instacalc-logo3.png

* instacalc-logo4.png

* logo-old.png

這就是軟體中為什麼有"Save As"命令的原因。它使得你可以在不破壞原始檔的基礎上,得到一個類似的新檔案。檔案的多版本儲存是一個常見問題,通常的解決辦法是這樣的:

* 做一個檔案備份(比如Document.old.txt)。

* 在檔名中加入版本號或日期(比如Document_V1.txt,DocumentMarch2007.txt)。

* 在多人編輯的環境下,共享一個檔案目錄,並且要求每個人編輯完以後,在檔案上做出標識。

什麼是版本控制系統(VCS)?

透過檔名識別版本,對於小型專案或者單個檔案也許可行。但是對於軟體開發來說,是不適用的。

你能想像嗎,要是Windows作業系統的原始檔,是在一個叫做"Windows2007-Latest-UPDATED!!"的共享目錄中開發的,並且每個程式設計師都可以編輯,都有一個自己的子目錄,那會發生什麼情況?那麼,Windows就根本不可能被製造出來。

大型的、頻繁修改的、多人編寫的軟體專案,需要一個版本控制系統(簡稱VCS,行話叫做"檔案資料庫"),追蹤檔案的變化,避免出現混亂。一個好的VCS應該做到以下幾點:

* 備份(Backup)和恢復(Restore)。檔案的每一次編輯都得到儲存,可以恢復到任意一個日期。需要2007年2月23日的版本?沒問題。

* 同步(Synchronization)。讓不同使用者隨時都能得到檔案的最新版本。

* 短期撤銷(Short-term undo)。檔案被你搞亂了,怎麼辦?那就撤銷編輯,回到最近一次的無差錯版本。

* 長期撤銷(Long-term undo)。有時候,你會過了很久才發現出錯了。如果你想撤銷一年前的一次編輯,怎麼辦?那就去取回一年之前的那個版本。

* 追蹤修改(Track Changes)。檔案的每一次編輯,你都可以寫下註解,解釋編輯的原因。(這些資訊儲存在VCS中,而不是檔案中。)這樣就很容易看出,長期中檔案變化的脈絡和原因。

* 追蹤許可權(Track Ownership)。VCS會記錄每一次提交新版本的使用者名稱。這樣就容易追蹤責任。

* 試驗功能(Sandboxing)。當你對檔案做出重大變更時,你可以把編輯內容暫時性地儲存在一個單獨的區域,不斷進行測試和除錯。等到確認正確以後,再加入主版本。

* 分支(Branching)和合並(merging)。分支功能可以看成是一個更大的測試版本。你將整個的程式碼做一份複製,然後再起一個獨立的名字,追蹤其變化,與原版本脫離關係,這就是分支。以後,你還可以將分支版本再併入源版本,這就是合併。

雖然共享資料夾操作起來更快速和簡單,但是它做不到上面這些功能。

一些術語

大多數VCS都有下面一些共同的概念,不過名字可能會稍有不同。

基本概念

* Repository (repo): 儲存檔案的資料庫。

* Server: 儲存repo的計算機。

* Client: 連線repo的計算機。

* Working Set/Working Copy: 當你編輯檔案時,編輯物件所在的本地檔案目錄。

* Trunk/Main: repo中儲存程式碼檔案的主位置。你可以把程式碼想像成一棵家族樹,"trunk"就是主線的那條樹幹。

基本操作

* Add: 將一個檔案第一次加入repo,也就是開始用VCS追蹤這個檔案。

* Revision: 檔案的版本編號(即v1, v2, v3等等)。

* Head: repo中儲存的檔案最新版本。

* Check out:從repo中下載一個檔案。

* Check in: 上傳檔案進入repo(如果檔案發生了變化)。這個檔案將得到一個新的版本編號,使用者將可以"check out"這個檔案。

* Checkin Message: 描述所做修改的短說明。

* Changelog/History: 一個記錄檔案自從建立開始所有變動的清單。

* Update/Sync: 將你本地的檔案同repo中最新版本進行同步的過程。這將使得本地檔案始終能夠跟上最新的變動。

* Revert: 放棄對檔案所做的編輯,從repo中重新獲得未編輯前的版本。

高階操作

* Branch: 在repo中對一個檔案或檔案目錄,建立一個獨立的複製。Branch在這裡既是動詞(branch the code),又是名詞(Which branch is it in?)。

* Diff/Change/Delta: 找出兩個檔案之間的差別。對於比較不同版本之間的變動很有用。

* Merge (or patch): 將一個檔案上的改動,應用於另一個檔案,使得兩者保持相同。比如,你可以將一個branch中的功能merge到另一個branch中。

* Conflict: 當你check in的時候,你所做的變動可能與其他使用者發生衝突。(這時雙方的編輯都不會生效。)

* Resolve: 修改互相沖突的變動,check in正確的版本。

* Locking: 取得一個檔案的"控制權",使得在你解鎖之前,其他人不能編輯這個檔案。有些VCS用這個功能避免conflict。

* Breaking the lock: 強制解鎖一個檔案,使得你可以對其進行編輯。比如,某人lock了一個檔案,但是他又去度假了。

* Check out for edit: Check out到一個檔案"可編輯"的版本。有些VCS預設允許編輯,另一些要求明確發出命令後,才提供可編輯的版本。

一次典型的使用過程是這樣的:

愛麗絲add一個檔案(list.txt)進入repo。然後,她又把這個檔案check out,做了一次編輯(在檔案中加入milk這個單詞)。接著,她將修改後的檔案check in,並附有一條checking message("加入了新的條目")。第二天早上,鮑勃update了他本地的working set,看到了list.txt的最新修訂版,其中包含了單詞"milk"。如果他使用changelog或diff,都可以發現前一天愛麗絲加入"milk"這個詞。

下面,我們用一些例子,來講解VCS的使用。

Check in

最簡單的情況是,check in一個檔案(list.txt),然後經常修改它。

版本控制入門插圖教程

在subversion系統中的命令是:

svn add list.txt
(modify the file)
svn ci list.txt -m "Changed the list"

最後一個命令中的-m標識,表示check in時附帶的message。

Check out和編輯

你不一定總是Check in檔案,有時候你需要check out,進行編輯,然後再check in。這個過程可以用下圖表示:

版本控制入門插圖教程

如果你對自己的編輯不滿意,想要從頭開始,你可以revert到上一個版本。當你check out的時候,預設情況下,你總是會得到最新版本。如果你想得到以前的版本,你可以在命令中指定版本號。在Subversion中,執行下面的命令:

svn co list.txt (get latest version)
...edit file...
svn revert list.txt (throw away changes)

svn co -r2 list.txt (check out particular version)

Diff

Diff就是你編輯時所做的變動。你可以想象一下,單獨將變動部分儲存下來,然後將它們應用到一個檔案上:

版本控制入門插圖教程

比如,從r1版到r2版,我們加入eggs(+Eggs)。你可以將這個過程想象成,單獨將圖中紅色的部分儲存下來,然後將它應用到r1上,就可以得到r2。

從r2版到r3版,我們加入了Juice(+Juice)。從r3版到r4版,我們刪去了Juice加入了Soup(-Juice, +Soup)。

大多數版本控制系統,只儲存diff,而不是檔案的完整版本。這樣可以節省磁碟空間。你做了4次修改,不意味著系統保留了4份複製。實際上,系統內只有1份複製和4個diff。在SVN中,我們用下面的命令diff一個檔案的兩個版本:

svn diff -r3:4 list.txt

Branch

Branch可以將原始檔做一份複製,儲存在VCS的另一個位置,然後我們對複製進行修改,不會影響到原始檔。

版本控制入門插圖教程

比如,上圖中我們建立了一個branch,在其中加入了Rice,而在trunk上加入的是Bread。有的VCS在建立Branch時,可能會修改版本號。

在Subversion中,建立branch的命令很簡單,只要從一個目錄複製到另一個目錄就可以了。

svn copy http://path/to/trunk http://path/to/branch

所以,branch並不難理解,你只要想像將程式碼複製到不同目錄就行了。它的好處在於,不管你做錯了什麼,你總可以回到一個安全的版本。

Merge

如果你要將一個branch中的變動,併入另一個branch,這可不太簡單。

比如,我們要將Rice這個詞從一個branch,併入主線中的檔案。我們應該怎麼做?Diff一下r6和r7,然後再併入主線?

錯了。我們只需要找到branch所做的變動就可以了。也就是說,我們只要diff一下r5和r6,然後再應用到trunk上就可以了。

版本控制入門插圖教程

如果我們diff了r6和r7,我們就會漏掉"Bread"這個已經在主幹中的詞。這是很微妙的一個地方,branch中的變動在於Rice這個詞(+Rice),只要將這個詞加入主幹就可以了。主幹檔案中也許還有其他變化,不過這不要緊,我們所要的只是插入Rice這個特性。

在Subversion中,merge命令與diff很類似。在一個主幹中,執行下面的命令:

svn merge -r5:6 http://path/to/branch

這個命令diff了r5和r6,然後將其加入當前位置的檔案中。不幸的是,Subversion沒有提供一種容易的途徑,追蹤merge中到底有什麼變動。所以如果你不小心的話,你可能將同樣的變動應用兩次。SVN已經計劃提供這個功能,但是目前的建議還是,保留一份changelog message,提醒你r5-r6已經併入了主幹。

Conflict

Conflict往往來自不同使用者,同時對同一個內容做出了不同的修改。Joe想刪除eggs,加入cheese(-eggs, +cheese),Sue想刪除eggs,加入hot dog(-eggs, +hot dog)。

版本控制入門插圖教程

從某個角度看,這有點像一場比賽:如果Joe首先check in,那麼他的編輯將寫入檔案。(Sue的編輯將被拒絕。)

如果他們同時提交了這種互相沖突的變動,VCS將報告一個conflict,不允許check in。由你來決定,是check in一個更新的版本,還是就地解決這個衝突。下面是一些可能的辦法:

* 重做一遍編輯。首先,將檔案Sync到最新的版本(r4),這時cheese已經在檔案中了。你再重做一遍剪輯,加上hot dog。

* 覆蓋掉他人的修改。將檔案check out到最新的版本(r4),用你的版本將這個版本覆蓋,再check in。也就是說,你等於刪掉了cheese,替換為hot dog。

Conflict不是很常見,但是處理起來很麻煩。通常,我會選擇上面第一種處理方法。

Tag

大概不會有人想到VCS早就符合Web 2.0的潮流吧?許多VCS允許你對任意編輯做一個標籤(label),方便以後的引用。這樣一來,你就可以用"Release 1.0",指代內部的版本號碼。

版本控制入門插圖教程

在Subversion中,tag其實是不再讓你編輯的branche,它們只是方便為了以後的使用,讓你能夠明確看到1.0版中到底包含了哪些東西。因此它們就停頓在那裡,不再變動了。

(in trunk)
svn copy http://path/to/revision http://path/to/tag

一個實際的例子:管理Windows原始碼

我們前面說過,微軟公司不用共享資料夾管理程式碼,那麼他們怎麼管理呢?

* 首先有一條main line,專門儲存穩定版本的Windows。

* 然後,每個開發小組(網路、使用者介面、媒體播放器等等)都有各自的branch,來新增新功能。這些新功能還在開發當中,並不穩定。

你在你的branch中,開發了一個新功能。然後,你用"Reverse Integrate (RI)",將它併入主版本。接著,你再用"Forward Integrate",你再去得到最新的主版本,將它併入你的branch。

版本控制入門插圖教程

假設老版本是Media Player 10和IE 6。Media Player開發小組,在他們的branch中製作了第11版,然後他們用一個10 - 11的補丁,將第11版加入老版本中。這是一個reverse integration,從branch到trunk。IE開發小組也是同樣的步驟。

接著,Media Player開發小組從其他小組(比如IE小組)得到最新的程式碼。在這個例子中,Media Player從trunk得到最新的補丁,運用到他們的branch中,這叫做forward integration。

reverse integration和forward integration,分別簡稱RI和FI。這樣的安排讓變動主要在branch中發生,而使得主幹保持相對不受影響。


在微軟實際運作中,有很多層的branch和sub-branch,還有許多質量控制標準,確定什麼時候才可以進行RI。這裡只是希望幫助你建立一個想法,那就是branch有助於管理複雜的專案。現在,你應該明白了世界上最大的軟體專案之一,是怎麼進行組織的。

結束語

如果你以前沒有用過VCS,我建議你使用它。因為它是一種很好的工具,即使你不打算寫一個作業系統,單單就是為了備份,也值得用它。

網上有許多VCS軟體可供選擇,並且都有詳細的教程或手冊,比如SVNCVSRCSGitPerforce等等。Eric Sink寫過一個詳細的version control guide可供參考。

(完)

相關文章