版本控制與CVS

2007-12-29 來源:chinaunix.net

CVS與版本控制
1.版本控制
2.CVS的歷史
3.CVS的特點
4.CVS的術語
5.CVS的目錄文件結構
6.CVS的命令及用法
7.結束語

參考:
1.《談談CVS的由來與發展》 北京信息工程學院 李寧 王慧思
2.CVS wiki
3.CVSNT wiki
4.CVS Manual
5.CVS入門 臥龍小三

我們假設有一個叫HappyBirthday的項目,由wuya提出,這個項目的功能就是根據日期提醒用戶其親人及好朋友的生日,並自動發送電子賀卡,整個項目的開發和維護現在都由wuya一個人來做。

由於這個wuya是菜鳥,所以他會犯錯,而且會犯很嚴重的錯誤。本來HappyBirthday項目有一個很不錯的功能,就是根據過生日的主角的興趣愛好(當然,這得用戶告訴計算機)自動從網上商城選擇推薦生日禮物。wuya同學實現了對生日主角的興趣愛好的分析部分,但在選擇生日禮物時遇到了困難,覺得自己唯有寫一個搜索引擎方能搞定,於是心灰意冷,一衝動就把這個功能全刪了(所以說衝動是魔鬼)。過了一段時間,wuya發現其實不需要自己寫搜索部分,有現在的可以用,腸子都悔青了,可惜代碼已經刪了,於是他想假如時間可以倒流。

過了一段時間,HappyBirthday項目有了新的成員參加,新成員竟然自稱Arthur King,我們暫時稱之爲arthur吧。

人多力量大,項目的進度快了不少,但人多也帶了一個問題,wuya和authur的代碼會有小的衝突,比如wuya同學設定HappyBirthday更新的時間爲自己的生日(一年更新一次,可真夠長的),但arthur同學不滿,偷偷改成了自己的生日,作爲項目發起人的wuya同學卻不知道,想象有一天他發現事實的真相可能會衝動作出一些破壞和諧的事情,所以最好的辦法還是提前解決衝突,項目改爲一年更新兩次。

有一天,wuya同學發現一個叫版本控制的東西,可以解決上述問題,我們就跟着他來看看解決之道。

1.版本控制

軟件的開發和維護過程,離不開版本控制。

版本控制(Revision Control)是一種軟件工程技巧,籍以在開發的過程中,確保由不同人所編輯的同一檔案都得到更新。

版本控制透過文檔控制(Documentation Control)記錄程序各個模組的改動,併爲每次發動編上序號。這種方法是維護工程圖的標準做法,它伴隨着工程圖的誕生一直到圖的定型。一種簡單的版本控制形式,如賦給圖的初版一個版本等級"A",當做了第一次改變後,版本等級改爲"B",以此類推。

對於同一份文件,我們經常需要按不同的版本進行歸檔,或者從資料庫裏找出反映文件修改歷史的不同版本。這樣一方面可以使各個階段的代碼和文檔變得井井有序,另一方面可以在當前版本出現問題時,找回先前的版本。當然人們希望的還不止這些,人們希望可以規定誰在什麼時候可以如何存取某個版本的內容;也希望差異不大的版本按增量的方式存成一個文件,以節省存儲空間;還希望某兩個版本的文件可以合二爲一;因此,我們需要版本控制工具。

2.CVS的歷史

早在CVS之前,就出現過對變化前後的文件進行比較,並根據異同形成"補丁"(Patch)的工具。例如Unix上使用的Diff和Patch,這兩個工具對程序代碼的傳播和維護起到了重要的作用。但是,後來出現許多要求Diff和Patch都顯得無能爲力,例如發現修補出錯而需要退回到以前未修改的狀態等。這要求有一個保存項目歷史紀錄的系統。

當初具備這個功能的工具是SCCS(Source Code Control System),是貝爾實驗室的Marc Rochkind在1972年寫成的。SCCS是一種基本的源文件版本控制工具,適用於正文文件的版本維護。它基於單一文件的版本控制,代碼庫和要維護的文件通常在同一目錄下。SCCS有一個專門的SCCS文件保留源文件的各編碼版本。其中記錄了足夠的信息來恢復一個版本,並記錄了誰對文件有修改、有版本鎖的功能。SCCS是AT&T Unix發行版的一部分。

然後自由軟件項目最終選擇了Walter F. Tichy的版本控制系統RCS來滿足他們的需要。RCSdSCCS的基礎上加以改進,界面也更加友好,屬BSD Unix發行版的一部分。它可以追蹤文件的改變,在工作組中對文件的共享和訪問進行控制,通常用於維護源代碼;也能追蹤文件的歷史。RCS包含一套命令,用於設置RCS源碼庫中的文件屬性、檢入檢出文件、清除文件、比較修訂版本,以及合併文件等。由RCStpgjr文件可以是純文件文件,也可以是二進制文件。

然後,RCS仍存在幾個重要缺陷,其中最重要的是由於使用單一目錄控制與檔案鎖,無法讓多個編程人員同時開發。因爲RCS本身不是針對網絡環境的,開發者只能在RCS代碼庫所在的機器上工作。這些缺點後來在CVS中都得到了改進。

下面三段話摘自Dick Grune的主頁:
CVS的誕生是爲了方便我和我的學生Eric Baalbergen和Maarten Waage在ACK C編譯器項目上協同工作,因爲我們三個人的工作日程安排大相徑庭(一個是典型的朝九晚五,一個是不確定,而我只能在晚上有時間做這個項目)。這個項目從 1984年6月持續到1985年8月。我們剛開始稱CVS爲cmt,因爲它允許我們獨立地提交不同的版本(commit versions independently)。

當Baalbergen-Waage項目結束以後,我開始着手整理cmt的shell腳本,因爲我發現它好像非常有用,我改稱它爲CVS。

我在1986年6月23日把改進後的腳本發佈到了comp.sources.unix上。

1989年3月,Brian Berlinor用C語言重新設計並編寫了CVS的代碼。後來,Jett Polk幫助Brian完成了CVS模型設計,增加了一些關鍵特性。1993年前後,Jim Kingdon最終將CVS設計成基於網絡的平臺,開發者們可以從Internet任何地方獲得程序源代碼。

3.CVS的特點

CVS使用CS結構:服務器負責存儲項目的所有版本和修改歷史,客戶端可以連接到服務器,check out整個項目到本機,然後在本機上進行修改,修改完成後可以存入(check in)到服務器上。通常,客戶端通過LAN或Internet連接,但如果要追蹤版本歷史的項目只有一位本地開發者,客戶端和服務器也可以在同一臺物理機器上。但如果要追蹤版本歷史的項目只有一位本地開發者,服務器軟件通常運行在Unix上(CVSNT可以運行在不同的Windows和Unix上,它是從 CVS中分出去的另一個獨立的項目),CVS客戶端一般可以在任何主流的操作系統平臺運行。

多個開發者可以並行開發項目,每一個人編輯自己從服務器取出(check out)到本機的工作版本(working copy),然後把修改的結果存入(check in)到主機。爲了避免開發者做重複的工作,服務器只接收對一個文件最新版本的更新。所以,開發者應該保證他們的工作目錄經常更新,以擁有別的開發者最新的修改。整個過程可以由CVS自動完成,需要人工介入的情況只有兩種:一是存入(check in)過程中出現衝突(conflicts),一種是存入服務器上不存在的本地文件。

每當一個存入(check in)操作成功時,所以涉及到的文件的版本號會自動加1,同時CVS服務器會在日誌中記錄存入的日期、作者的名字、及存入用戶對於此次存入內容的描述。 CVS也可以在存入(check in)時執行用戶定義的外部的日誌處理腳本。可以通過CVS的loginfo文件來安裝這些腳本。

客戶端通常可以比較算法、請求整個修改歷史,或者取出(check out)某個固定日期或版本號的歷史快照。許多開源項目允許匿名讀取(anonymous read access),這個特點首先是OpenBSD擁有的。這即意味着可以使用空密碼或者公開的密碼從服務器取出(check out)源碼並比較版本間的差別,唯有需要存入(check in)修改部分的人擁有帳號密碼。

客戶端可以使用"更新"(update)命令來更新他們本地的工作目錄,需要更新的文件會被自動更新,整個項目不會被重新下載。

CVS可以維護同一個項目的不同"分支"(branches)。比如一個正式版本(released version)可以形成一個分支,這個分支現在只修正bug即可;還有一個當前正在開發的版本,可以形成另一個分支,較之前的版本有了大的改變和一些新的特徵。

CVS使用delta算法來更有效的存儲同一文件的不同版本。這種實現比較偏愛多行文件(通常是文本文件)。

4.CVS的術語

一些相關的源碼組合在一起,在CVS中稱之爲一個模塊(module)。CVS服務器把它負責管理的模塊存儲在倉庫(repository)中。獲得一個模塊的拷貝稱之爲取出(check out)。被取出(check out)的文件稱之爲一個工作集(working copy)。對工作集所做的修改要通過存入(commit)反映到服務器的倉庫(repository)中。從倉庫把最新的修改下載到本地的工作集中稱之爲更新(update)。

5.CVS的目錄結構

5.1倉庫(repository)的目錄結構
倉庫中全部目錄結構完全對應於工作目錄(working directory)中的目錄結構。比如,假設倉庫在/usr/local/HappyBirthday
下面是一個可能的目錄結構
/usr
|
+--local
| |
| +--HappyBirthday
| | |
| | +--CVSROOT
| (管理文件)
|
+--conf.d
| |
| +--configure.example
| |
| +--configure
| |
| +--test.pl
|
+--other HappyBirthday modules

與目錄一直的是版本控制下的每個文件的歷史文件(history files)。這些文件的是名稱是在對應的文件名後面加上",v"。下面是倉庫中conf.d目錄的可能情況:
$CVSROOT
|
+--conf.d
| |
| +--configure.examples,v
| +--configure,v
| +--test.pl,v
歷史文件中有足夠的信息來重新創建文件的任何一個版本,另外歷史文件還記錄了所有提交信息的日誌,其中包括提交者的用戶名、提交時間等。這些歷史文件就是以前的RCS files,因爲第一個以這種文件格式存儲來進行版本控制的系統是RCS。這種文件格式的應用已經非常廣泛了,除了CVS和RCS之外還有很多其它的版本控制系統,它們至少可以導入這種格式的歷史文件。

5.2工作目錄(working directory)的目錄結構
CVS目錄包含以下若干個文件(這裏只列出最常用的)。文件以符合當前系統規範的文本形式保存,這意味着在擁有不同規範的系統之間工作目錄不可移植。這是故意爲之,所以理論上CVS的管理文件在系統之間無法移植。

Root
該文件包含當前CVS根目錄。

Repository
該文件包含當前目錄對應的倉庫裏的目錄。該路徑可以是絕對路徑也可以是相對路徑;從1.3版本開始,CVS可以讀取這兩種格式的路徑。相對路徑名相對於要目錄並且容易解析,但是絕對路徑更通用。
例如:執行以下命令後
cvs -d :local:/usr/local/HappyBirthday checkout conf.d
Root應該包含
:local:/usr/local/HappyBirthday
Repository包含
/usr/local/HappyBirthday/conf.d

conf.d
如果特定的工作目錄不與倉庫的目錄相一致,Repository應當包含CVSROOT/Emptydir。

Entries
該文件列出了工作目錄中的文件和子目錄。每一行的第一個字符代表該行的類別。爲了保證未來版本的可擴展性,如果一行的第一個不可識別,讀取文件的程序默認忽略該行。
如果第一個是"/",則格式如下:
/name/revision/timestamp[+conflict]/option/tagdate
name 是目錄中文件的名字。
revision 是正在編輯的文件派生的修訂版本號,'0' 代表新添加的文件,'-'revision 代表刪除的文件。
timestamp 爲時間戳,表示 cvs 創建文件的時間;如果時間戳和文件修改的時間不一致,意味着文件已經被修改過。時間戳以 ISO 標準的 C 函數 asctime() 的格式存儲(例如,'Sun Apr 7 01:29:26 1996')。
conflict 表示是否存在版本衝突。如果 conflict 和文件實際的修改時間相同表示用戶還沒有解決版本衝突問題。
options 包含可選項(例如對二進制文件可以使用'-kb')。
tagdate 含有'T'後面跟標籤名,或'D'表示日期,後面跟是sticky標籤或日期。

6.CVS的基本命令及其用法
CVS的基本命令格式如下:
cvs [cvs_options] command [command_options] [command_args]
經常用到的命令有:login、logout、commit、checkout、update、status、diff、log、add、remove等,大部分命令都有簡寫。

下面我們仍以HappyBirthday爲例,來看看如何使用這些命令。

6.1建立倉庫
首先,我們得建立一個倉庫,倉庫建立在CVS服務器上,相對於CVS客戶端,服務器可以是本地的,也可以是遠程的。這裏爲了簡單,我們決定使用本地倉庫。假設此時HappyBirthday項目的開發人員又變成了wuya同學一個人了。

建立倉庫文件夾。
$mkdir /usr/local/HappyBirthday

添加一個用戶組happy_birthday,處於這個組中的成員都可以存取倉庫。把wuya添加到這個用戶組中。
$groupadd happy_birthday
$gpasswd -a wuya happy_birthday

切換到用戶wuya,看看他所屬的組有哪些。
$su wuya
$groups
我們可能會看到happy_birthday組並沒有被groups命令列出,所以我們需要重新登陸一下。

設置項目文件夾的組和權限。
$chmod 2775 /usr/local/HappyBirthday
$chgrp happy_birthday /usr/local/HappyBirthday
$ls -ld /usr/local/HappyBirthday

初始化倉庫
$cvs -d /usr/local/HappyBirthday init
這裏給出了倉庫的絕對路徑,也可以不給出,但需要設置CVSROOT環境變量,可以在~/.bash_profile中添加
export CVSROOT=/usr/local/HappyBirthday

6.2建立工作目錄
建立一個將來工作的目錄,可以隨便建立在任何你習慣的地方。
$mkdir /home/wuya/Projects/HappyBirthday

因爲整個項目分成好幾個模塊,各司其職也方便管理,現在我們先創建一個管理模塊(關於模塊的定義,術語解釋中有給出),其中是項目的配置文件。
$cd /home/wuya/Projects/HappyBirthday
$mkdir conf.d
$touch configure.example

我們這裏是在一個項目HappyBirthday中創建不同的模塊,因爲在建立倉庫時我們就打算這個倉庫只存儲HappyBirthday項目的內容,所以倉庫的名字就叫/usr/local/HappyBirthday。其實也可以在同一個倉庫下面管理多個項目。這些其實都只是邏輯上的概念了。

在本地創建了conf.d模塊,必須更新到倉庫裏。
$cd conf.d
$cvs -d /usr/local/HappyBirthday import -m "configuration files" conf.d wuya start
這樣倉庫裏面也就有了conf.d配置模塊的目錄結構。目錄的結構就是前面講過的結構。

6.3取出模塊
雖然現在我們的HappyBirthday項目中空空如也,就一個conf.d/configure.example,但是這也足以說明問題了。
先裝模作樣的取出conf.d模塊(這裏的模塊概念是個邏輯概念)。
$cvs -d /usr/local/HappyBirthday checkout conf.d
這時,我們就從倉庫中取出了最新的conf.d模塊。

感覺每次都指出倉庫太麻煩了,可以在~/.bashrc中定義CVSROOT
export CVSROOT="/usr/local/HappyBirthday"

6.4修改程序,更新倉庫
我們先看看當前唯一的文件conf.d/configure.example狀態
$cvs status conf.d
顯示結果如下
cvs status: Examining conf.d
===================================================================
File: configure.example Status: Up-to-date

Working revision: 1.1.1.1 2007-07-16 14:10:11 +0800
Repository revision: 1.1.1.1 /usr/local/HappyBirthday/conf.d/configure.example,v
Commit Identifier: 6007469b0bc34567
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

下面我們來修改下configure.example的內容
$vi conf.d/configure.example
輸入
#This is a configuration file
:wq保存退出vi。

再看看文件當前的狀態有無變化。
$cvs status conf.d/configure.example
===================================================================
File: configure.example Status: Locally Modified

Working revision: 1.1.1.1 2007-07-16 14:10:11 +0800
Repository revision: 1.1.1.1 /usr/local/HappyBirthday/conf.d/configure.example,v
Commit Identifier: 6007469b0bc34567
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
注意這裏的狀態由前面的"Update-to-date"變成了"Locally Modified"。

下面我們把修改結果提交給倉庫。
$cvs commit -m "增加了一行註釋" conf.d/configure.example
/usr/local/HappyBirthday/conf.d/configure.example,v <-- conf.d/configure.example
new revision: 1.2; previous revision: 1.1
我們看到configure.example已經成功存入倉庫,版本號由原來的1.1變爲1.2。注意,這裏的"-m"後面是關於此次提交的描述,如果上面命令爲
$cvs commit conf.d/configure.example
就會打開默認的編輯器來要求你輸入描述內容。

查看一下當前的configure.example狀態
$cvs status conf.d/configure.example
===================================================================
File: configure.example Status: Up-to-date

Working revision: 1.2 2007-07-16 14:53:32 +0800
Repository revision: 1.2 /usr/local/HappyBirthday/conf.d/configure.example,v
Commit Identifier: 60b5469b16fa4567
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

6.5添加新的文件
現在我們決定開始寫一個正式的配置文件。
$touch conf.d/configure
$vi conf.d/configure
在文件中寫入
#This is empty now.
:wq保存退出vi。

我們已經知道了如何把修改結果提交給倉庫,但相同的動作再作一次卻對新文件沒有作用。因爲倉庫中根本沒有configure這個文件的存在,自然也不會對其進行更新了。所以我們先得把新文件添加到倉庫中。
$cvs add conf.d/configure
cvs add: scheduling file `conf.d/configure' for addition
cvs add: use `cvs commit' to add this file permanently
根據提示,我們還需要作commit動作。
$cvs commit -m "添加conf.d/configure" conf.d/configure
/usr/local/HappyBirthday/conf.d/configure,v <-- conf.d/configure
initial revision: 1.1
可以看到,configure已經被成爲添加到倉庫中,版本爲1.1。

我們再添加一個文件,方便後面刪除。
$touch conf.d/test.pl
$cvs add conf.d/test.pl
$cvs ci -m "添加conf.d/test.pl" conf.d/test.pl

6.6 刪除文件
現在我們又不想要之前添加的test.pl了,如何刪除呢?
$rm conf.d/test.pl
$cvs remove test.pl
cvs remove: scheduling `conf.d/test.pl' for removal
cvs remove: use `cvs commit' to remove this file permanently
根據命令結果
$cvs ci -m "刪除conf.d/test.pl" conf.d/test.pl
/usr/local/HappyBirthday/conf.d/test.pl,v <-- conf.d/test.pl
new revision: delete; previous revision: 1.1
雖然在commit之前,本地的test.pl已經被刪除了,但倉庫並不知道,所以變化必須提交到倉庫。

6.7 解決衝突
之前的代碼都是wuya一個人在維護,所以代碼的作者全是他,不存在衝突的問題。可是項目中忽然又多了一個人zhuqin同學,他對項目中的文件作了一些自己的修改。下面全是zhuqin的動作。
$cvs co conf.d
$vi conf.d/confiugre
並在其中添加
zhuqin say hello
然後把結果提交到倉庫
$cvs -d /usr/local/HappyBirthday ci -m "更新conf.d/configure" conf.d/configure
/usr/local/HappyBirthday/conf.d/configure,v <-- conf.d/configure
new revision: 1.2; previous revision: 1.1

我們這裏,zhuqin與wuya是工作於同一臺物理機器上的,呵呵。實際項目中這種情況比較少見。

wuya同學並不知道zhuqin對conf.d/configure所做的修改,他編輯自己工作目錄下的conf.d/configure
$vi conf.d/configure
在其中添加
wuya say hello
然後提交給倉庫
$cvs ci -m "wuya更新conf.d/confiugre" conf.d/confiugre
卻得到如下結果
cvs commit: Up-to-date check failed for `conf.d/configure'
cvs [commit aborted]: correct above errors first!

發生了什麼事,wuya趕快查看configure文件的狀態
$cvs status conf.d/configure
===================================================================
File: configure Status: Needs Merge

Working revision: 1.1 2007-07-16 15:11:49 +0800
Repository revision: 1.2 /usr/local/HappyBirthday/conf.d/configure,v
Commit Identifier: 617e469b251a4567
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
我們看到,文件當前的狀態爲"Needs Merge",說明當前wuya手中的1.1版本的文件與倉庫中1.2版本的文件有衝突。

wuya同學使用diff命令來看到底衝突何在
$cvs diff -c conf.d/configure
Index: conf.d/configure
===================================================================
RCS file: /usr/local/HappyBirthday/conf.d/configure,v
retrieving revision 1.1
diff -c -r1.1 configure
*** conf.d/configure 16 Jul 2007 07:15:41 -0000 1.1
--- conf.d/configure 16 Jul 2007 08:02:46 -0000
***************
*** 1 ****
--- 1,2 ----
#This is empty now.
+ wuya say hello
"+"表示wuya手中的文件倉與庫中的文件在這一行發生了衝突。

wuya趕快執行update命令,獲得倉庫中的最新版本。
$cvs update conf.d/configure
執行結果
RCS file: /usr/local/HappyBirthday/conf.d/configure,v
retrieving revision 1.1
retrieving revision 1.2
Merging differences between 1.1 and 1.2 into configure
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in conf.d/configure
C conf.d/configure

此時,conf.d/configure的內容變爲
#This is empty now.
<<<<<<< configure <------ wuya的configure
wuya say hello <------ wuya修改的部分
=======
zhuqin say hello <------ zhuqin修改的部分
>>>>>>> 1.2 <------ 倉庫中的1.2版本

既然知道了衝突在哪裏,wuya就和zhuqin磋商了一下,最終決定configure文件內容如下所示
#This is empty now
wuya and zhuqin say hello

衝突解決後,wuya把新的configure文件提交到倉庫中。
$cvs ci -m "已經解決和zhuqin的衝突" conf.d/configure
/usr/local/HappyBirthday/conf.d/configure,v <-- conf.d/configure
new revision: 1.3; previous revision: 1.2
此時倉庫中的configure文件版本變爲1.3,這個版本解決了wuya與zhuqin之間的衝突。

6.8 查詢歷史
zhuqin剛剛加入HappyBirthday項目,想看看項目的歷史,想了解項目中文件改動的紀錄,於是使用log命令查詢。
$cvs log
於是得到下面的內容
cvs log: Logging conf.d

RCS file: /usr/local/HappyBirthday/conf.d/configure,v
Working file: conf.d/configure
head: 1.3
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 3; selected revisions: 3
description:
----------------------------
revision 1.3
date: 2007-07-16 16:23:57 +0800; author: wuya; state: Exp; lines: +1 -1; commitid: 61c4469b2b1d4567;
已經解決和zhuqin的衝突
----------------------------
revision 1.2
date: 2007-07-16 15:58:52 +0800; author: zhuqin; state: Exp; lines: +1 -0; commitid: 617e469b251a4567;
更新conf.d/configure
----------------------------
revision 1.1
date: 2007-07-16 15:15:41 +0800; author: wuya; state: Exp; commitid: 60e6469b1b1d4567;
添加conf.d/configure
======================================================================
RCS file: /usr/local/HappyBirthday/conf.d/configure.example,v
Working file: conf.d/configure.example
head: 1.2
branch:
locks: strict
access list:
symbolic names:
start: 1.1.1.1
wuya: 1.1.1
keyword substitution: kv
total revisions: 3; selected revisions: 3
description:
----------------------------
revision 1.2
date: 2007-07-16 14:58:20 +0800; author: wuya; state: Exp; lines: +1 -0; commitid: 60b5469b16fa4567;
增加了一行註釋
----------------------------
revision 1.1
date: 2007-07-16 14:10:11 +0800; author: wuya; state: Exp; commitid: 6007469b0bc34567;
branches: 1.1.1;
Initial revision
----------------------------
revision 1.1.1.1
date: 2007-07-16 14:10:11 +0800; author: wuya; state: Exp; lines: +0 -0; commitid: 6007469b0bc34567;
configureation module
=======================================================================
RCS file: /usr/local/HappyBirthday/conf.d/Attic/test.pl,v
Working file: conf.d/test.pl
head: 1.2
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 2; selected revisions: 2
description:
----------------------------
revision 1.2
date: 2007-07-16 15:44:08 +0800; author: wuya; state: dead; lines: +0 -0; commitid: 612d469b21c84567;
刪除conf.d/test.pl
----------------------------
revision 1.1
date: 2007-07-16 15:41:26 +0800; author: wuya; state: Exp; commitid: 6124469b21264567;
添加conf.d/test.pl
======================================================================
可以看到,我們曾經做過的所有動作都被詳細地記錄了下來。

6.9 分支
假設我們的HappyBirthday當前版本爲A,然後我們在其上添加了許多新的功能,形成了版本B,雖然功能強大,可惜Bug多多,甚至影響到版本A 的功能,於是我們開始懷念以前版本A的日子,我們想到從版本A到版本B我們犯了若干錯誤,如果沒有這些錯誤,那現在的版本B就是完美的。

OK,CVS可以幫助你回到美好的版本A的日子。

要回到版本A,就必須知道什麼是版本A,即各文件處於什麼狀態下的集合體才能稱之爲版本A。一個直觀的想法就是在曾經版本A出現的時刻,我們把它標記下來,我們肯定得告訴系統,這個狀態就是版本A。然後某一天,我們告訴系統,把我當時定義的那個版本A的狀態重新還原出來,於是就回到了版本A,CVS正是這樣做的。

CVS提供了兩種不同的方法來獲得項目不同的歷史狀態,一種是依時間來取,一種是依標記來取。

6.9.1
我們先看如何依時間來取出項目的版本A。
假設我們現在要取出2007-07-16 15:42:00時的版本(汗,被大家發現我這篇文章是什麼什麼才寫的,因爲馬上回家了,歸心似箭,所以效率奇低,請原諒),使用下面的命令
$cvs update -D "2007-07-16 15:42:00 +0800" conf.d
其中"-D"後面跟的是時間,這個時間格式一看就明白,其中的"+0800"指的是東八區,也就是我們現在所處的時區。這個可以加也可以不加,CVS可以自動檢測出系統所處的時區,換算成正確的時間,當然前提是你係統的時區設置是正確的。如果系統使用的是GMT時間,則此處的"+0800"應該換成 "GMT"。

查看前面的log可以知道,在"2007-07-16 15:42:00"這個時間點,conf.d/test.pl仍然在,還沒有被刪除,而conf.d/configure仍處於版本1.1, conf.d/configure.example處於版本1.2,我們看看我們是不是真的回到過去了:)
$cvs status conf.d
執行結果如下
cvs status: Examining conf.d
===================================================================
File: configure Status: Up-to-date

Working revision: 1.1 2007-07-16 21:28:23 +0800
Repository revision: 1.1 /usr/local/HappyBirthday/conf.d/configure,v
Commit Identifier: 60e6469b1b1d4567
Sticky Tag: (none)
Sticky Date: 2007.07.16.07.42.00
Sticky Options: (none)

===================================================================
File: configure.example Status: Up-to-date

Working revision: 1.2 2007-07-16 21:28:23 +0800
Repository revision: 1.2 /usr/local/HappyBirthday/conf.d/configure.example,v
Commit Identifier: 60b5469b16fa4567
Sticky Tag: (none)
Sticky Date: 2007.07.16.07.42.00
Sticky Options: (none)

===================================================================
File: test.pl Status: Up-to-date

Working revision: 1.1 2007-07-16 15:41:26 +0800
Repository revision: 1.1 /usr/local/HappyBirthday/conf.d/Attic/test.pl,v
Commit Identifier: 6124469b21264567
Sticky Tag: (none)
Sticky Date: 2007.07.16.07.42.00
Sticky Options: (none)

我們看到,真回到過去了,注意這時候文件的"Sticky Date"不再爲"(none)",而是變成了"2007.07.16.07.42.00"。

那現在的版本B呢,不會消失了吧,如果真是這樣,那CVS也就不是CVS了,使用下面的命令我們即可以回到版本B。
$cvs update -A

需要指出的是,在回到版本A的時候,如果你修改了conf.d的內容,將會無法提交。爲什麼,因爲這是已經過去的事,過去的事怎麼可以改變,這時候分支的概念就被引入來解決這個問題。打個比方,玩過魔獸的人都知道有個劍聖,劍聖有分身術,我們可以把劍聖的分身看做項目的分支,劍聖的真身看做項目的主幹,在使用分身術之前只存在一個劍聖,如我們的版本A,使用分身術之後真身與分身可以各做各的事,但他們始於同一起點。如果真身不幸遇到了敵人大部隊掛掉了,但分身不會受影響。

6.9.2
我們接着來看如何依標記取出項目的版本A。

要使用標記來取出過去的項目,則必須存在標記。
恰好zhuqin同學定義了一個叫versionA的標記,做標記時工作目錄中的conf.d/configure.example和conf.d/configure均還處於版本1.2。
$cvs tag versionA conf.d

看看現在zhuqin的工作目錄下conf.d的狀態
$cvs status conf.d
執行結果
cvs status: Examining conf.d
===================================================================
File: configure Status: Up-to-date

Working revision: 1.3 2007-07-16 22:06:23 +0800
Repository revision: 1.3 /usr/local/HappyBirthday/conf.d/configure,v
Commit Identifier: 61c4469b2b1d4567
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

===================================================================
File: configure.example Status: Up-to-date

Working revision: 1.3 2007-07-16 22:06:23 +0800
Repository revision: 1.3 /usr/local/HappyBirthday/conf.d/configure.example,v
Commit Identifier: 63cc469b6cb84567
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)

現在我們讓我們回到版本A,再看看conf.d中文件的狀態如何
$cvs update -r versionA conf.d
$cvs status conf.d
cvs status: Examining conf.d
===================================================================
File: configure Status: Up-to-date

Working revision: 1.2 2007-07-16 22:07:52 +0800
Repository revision: 1.2 /usr/local/HappyBirthday/conf.d/configure,v
Commit Identifier: 617e469b251a4567
Sticky Tag: versionA (revision: 1.2)
Sticky Date: (none)
Sticky Options: (none)

===================================================================
File: configure.example Status: Up-to-date

Working revision: 1.2 2007-07-16 22:07:52 +0800
Repository revision: 1.2 /usr/local/HappyBirthday/conf.d/configure.example,v
Commit Identifier: 60b5469b16fa4567
Sticky Tag: versionA (revision: 1.2)
Sticky Date: (none)
Sticky Options: (none)

注意,此時的"Sticky Tag"內容由"(none)"變爲"versionA (revision: 1.2)",顯然我們的確回到了版本A。

再回到版本B
$cvs update -A
我們就又回到現在正常的狀態。

6.9.3 分支
前面兩種方法最終都得靠分支方能完美地解決問題,馬上來看如何做分支。
首先取出版本A到old_conf.d文件夾中
$cd /home/zhuqin/Projects/HappyBirthday
$cvs co -d old_conf.d -r versionA conf.d
"-d"會在HappyBirthday目錄下創建old_conf.d文件夾。

然後我們使用版本A來產生一個分支
$cd old_conf.d
$cvs tag -b versionA_branch
"-b"表示產生一個分支。

不過這時只是倉庫中的內容做了標記,對當前工作目錄下的文件還沒有實質性的影響,即此時old_conf.d目錄下的文件還不是獨立的versionA_branch,查看它們的"Stick Tag"即可知道。
$cvs update -r versionA_branch
現在old_conf.d目錄下的所有文件就實實在在地屬於分支versionA_branch了。

我們來看看當前分支的狀態
$cvs status old_conf.d
執行結果
cvs status: Examining old_conf.d
===================================================================
File: configure Status: Up-to-date

Working revision: 1.2 2007-07-16 15:58:52 +0800
Repository revision: 1.2 /usr/local/HappyBirthday/conf.d/configure,v
Commit Identifier: 617e469b251a4567
Sticky Tag: versionA_branch (branch: 1.2.2)
Sticky Date: (none)
Sticky Options: (none)

===================================================================
File: configure.example Status: Up-to-date

Working revision: 1.2 2007-07-16 14:58:20 +0800
Repository revision: 1.2 /usr/local/HappyBirthday/conf.d/configure.example,v
Commit Identifier: 60b5469b16fa4567
Sticky Tag: versionA_branch (branch: 1.2.2)
Sticky Date: (none)
Sticky Options: (none)
注意"Sticky Tag"的內容---"branch: 1.2.2",這表示這個versionA_branch是由版本1.2分出來的。

現在我們可以自由修改old_conf.d目錄下的內容了。修改後old_conf.d/configure,執行commit:
$cvs ci -m "更新versionA branch" conf.d

查看old_conf.d/configure當前的狀態
$cvs status old_conf.d/configure
===================================================================
File: configure Status: Up-to-date

Working revision: 1.2.2.1 2007-07-16 22:31:12 +0800
Repository revision: 1.2.2.1 /usr/local/HappyBirthday/conf.d/configure,v
Commit Identifier: 64d3469b814b4567
Sticky Tag: versionA_branch (branch: 1.2.2)
Sticky Date: (none)
Sticky Options: (none)
可以發現,當前的工作版本變爲1.2.2.1了。

7.結束語
至此,CVS的基本用法大概介紹完了。
雖然說了很多,不過做起來還是很簡單的東西,而且的確是非常有用的工具。在實踐過程中的遇到的問題差不多都可以通過查CVS的手冊獲得答案。
$info cvs
不過這個打開的手冊頁默認停留的節點是"CVS commands",可以使用'u'來回到上層節點,查看整個CVS手冊的結構和內容。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章