我知道版本控制系統(vcs)很有用。
但是,我平時隻是業餘寫一些小程式,感覺特地裝一個vcs太麻煩,是以一直沒有用。最近,因為想認真做一個中等規模的項目,是以決心好好學一下怎麼用。
下面就是我翻譯的一篇入門教程,主要解釋了vcs的一些主要概念。
======================
a visual guide to version control
版本控制入門插圖教程
作者:kalid azad
譯者:阮一峰
版本控制(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,我建議你使用它。因為它是一種很好的工具,即使你不打算寫一個作業系統,單單就是為了備份,也值得用它。
(完)