CVS 簡(jiǎn)單教程
v2.1
版權聲明:本文檔遵循 FDL 版權發(fā)布,有關(guān) FDL 的詳細信息,請參考:
http://www.fsf.org/copyleft/fdl.html
何偉平
CVS 是 Concurrent VersionSystem(并行版本系統)的縮寫(xiě),用于版本管理.如果大家曾經(jīng)參與過(guò)多人協(xié)作開(kāi)發(fā)的項目,大家肯定有這樣的痛苦經(jīng)歷:由于多個(gè)人同時(shí)修改同一個(gè)文件,自己辛辛苦苦修改的程序被別人徹底刪除了.另外,如果你的軟件/程序已經(jīng)發(fā)布了三個(gè)版本,而這時(shí)候用戶(hù)需要你修改第二個(gè)版本的東西,也許你會(huì )因為只保留了最新版本而痛哭流涕.還有就是你對程序做了一些修改,但是修改很少,你只想給遠方的同事發(fā)一個(gè)兩個(gè)版本之間的差別文件,這樣可以免于郵箱不夠大,網(wǎng)速太慢之類(lèi)的問(wèn)題.為了解決類(lèi)似這樣的問(wèn)題,以及諸如生成補丁文件,歷史版本修改等,一幫黑客(褒義)在原先 Unix 體系里很成熟的 SCCS 和RCS 的基礎上,開(kāi)發(fā)了 CVS.(SCCS:Source Code Control System,RCS:Revision ControlSystem)
CVS的基本工作思路是這樣的:在一臺服務(wù)器上建立一個(gè)倉庫,倉庫里可以存放許多不同項目的源程序.由倉庫管理員統一管理這些源程序.這樣,就好象只有一個(gè)人在修改文件一樣.避免了沖突.每個(gè)用戶(hù)在使用倉庫之前,首先要把倉庫里的項目文件下載到本地.用戶(hù)做的任何修改首先都是在本地進(jìn)行,然后用 cvs命令進(jìn)行提交,由 cvs 倉庫管理員統一修改.這樣就可以做到跟蹤文件變化,沖突控制等等.
由于 CVS 是典型的 C/S 結構的軟件,因此它也分成服務(wù)器端和客戶(hù)端兩部分.不過(guò)大多數 CVS 軟件都把它們合二為一了.
下面是我的步驟和做法:
前提要求:
- root 權限;
- CVS軟件,請找到相關(guān)的rpm,tgz,deb 等包裝上,或者到
http://www.cvshome.org/CVS/Dev/code
下載原程序編譯安裝,這里我不準備介紹它的安裝,請參考CVS自身的文檔安裝.我使用Slackware的tgz包,安裝的命令是
#installpkg cvs*.tgz
其他包請參考對應包管理工具的命令. - 一定的系統資源,要有一定內存(32M就能工作得很好),要一定的磁盤(pán)空間,看你的項目的大小和多少而定.
- 建立 CVSROOT 目錄,因為這里涉及到用戶(hù)對CVSROOT里的文件讀寫(xiě)的權限問(wèn)題,所以比較簡(jiǎn)單的方法是建立一個(gè)組,然后再建立一個(gè)屬于該組的帳戶(hù),而且以后有讀寫(xiě)權限的用戶(hù)都要屬于該組.假設我們建一個(gè)組叫cvs,用戶(hù)名是cvsroot.建組和用戶(hù)的命令如下
- #groupadd cvs
#adduser cvsroot - 用 cvsroot 用戶(hù)登陸,修改 /home/cvsroot (CVSROOT)的權限,賦與同組人有讀寫(xiě)的權限:
- $chmod 771 . (或者770應該也可以)
注意:這一部分工作是按照文檔說(shuō)明做的,是否一定需要這樣沒(méi)有試驗,我會(huì )在做試驗后在以后版本的教程說(shuō)得仔細一點(diǎn).如果您有這方面的經(jīng)驗請提供給我,謝謝.
- 建立CVS倉庫,(仍然是 cvsroot 用戶(hù)),用下面命令:
- $cvs -d /home/cvsroot init
- 以root身份登陸,修改 /etc/inetd.conf(使用 xinetd 的系統沒(méi)有此文件)和 /etc/services,
如果用的是 inetd 的系統,在 /etc/inetd.conf 里加入:
cvsserver stream tcp nowait root /usr/bin/cvs cvs -f --allow-root=/home/cvsroot pserver說(shuō)明:上面的行是單獨一整行,/usr/bin/cvs 應該是你的cvs版本的命令路徑,請根據自己的系統調整./home/cvsroot 是你建立的CVSROOT的路徑,也請根據上面建立目錄的部分的內容做調整.
如果是使用 xinetd 的系統,需要在 /etc/xinetd.d/ 目錄下創(chuàng )建文件 cvspserver(此名字可以自己定義),內容如下:
# default: on
# description: The cvs server sessions;
service cvsserver
{
socket_type = stream
wait = no
user = root
server = /usr/bin/cvs
server_args = -f --allow-root=/cvsroot pserver
log_on_failure += USERID
only_from = 192.168.0.0/24
}
其中only_from是用來(lái)限制訪(fǎng)問(wèn)的,可以根據實(shí)際情況不要或者修改。修改該文件權限:
# chmod 644 cvspserver
在/etc/services里加入:
cvsserver 2401/tcp說(shuō)明:cvsserver 是任意的名稱(chēng),但是不能和已有的服務(wù)重名,也要和上面修改 /etc/inetd.conf 那行的第一項一致.這里我用的是 CVS 的口令認證方式,CVS 還有其他認證方式,我沒(méi)有做試驗,如果您有經(jīng)驗,請補充,謝謝.
- 添加可以使用 CVS 服務(wù)的用戶(hù)到 cvs 組:
- 以 root 身份修改 /etc/group,把需要使用 CVS 的用戶(hù)名加到 cvs 組里,比如我想讓用戶(hù) laser 和gumpwu 能夠使用 CVS 服務(wù),那么修改以后的 /etc/group 應該有下面這樣一行:
cvs:x:105:laser,gumpwu
在你的系統上GID可能不是105,沒(méi)有關(guān)系.主要是要把laser和gumpwu用逗號分隔開(kāi)寫(xiě)在最后一個(gè)冒號后面.當然,象RedHat等分發(fā)版有類(lèi)似linuxconf這樣的工具的話(huà),用工具做這件事會(huì )更簡(jiǎn)單些. - 重起inetd使修改生效:
- #killall -HUP inetd
如果使用的是 xinetd 的系統:
# /etc/rc.d/init.d/xined restart
這樣服務(wù)器就設置完成了.我們接著(zhù)搞客戶(hù)端.
設置客戶(hù)端
如果是 Linux(或者其他 *nix),客戶(hù)端和服務(wù)器端的軟件是一樣的,如果是Win,MAC等平臺,請到
http://www.loria.fr/cgi-bin/molli/wilma.cgi/rel
找相應的客戶(hù)端軟件,這里我先說(shuō)一下在 Linux(*nix)里怎么做:
- 設置環(huán)境變量CVSROOT:
- $export CVSROOT=:pserver:laser@the_server_name:/home/cvsroot
注意:這里的pserver是訪(fǎng)問(wèn)方式,我在上面設置的是口令認證,所以這里是pserver,如果你的CVS服務(wù)器設置成別的訪(fǎng)問(wèn)模式,那么需要相應修改.laser是可以使用CVS服務(wù)器的用戶(hù)名,這里可以根據你的設置修改,我在這個(gè)版本設置的是直接使用系統用戶(hù)的口令文件,也就是說(shuō)laser必須是CVS服務(wù)器上的合法用戶(hù),這里當然有安全問(wèn)題,CVS可以設置成為擁有自己的用戶(hù),我將在以后的版本里面增加這些內容,或者您也可以提供一些補充,或者直接讀CVS的文檔.the_server_name是CVS服務(wù)器的名稱(chēng)或者IP地址,根據你的情況填寫(xiě),/home/cvsroot是你的CVS服務(wù)器的CVSROOT目錄,根據你的CVS服務(wù)器設置做修改或者詢(xún)問(wèn)管理員.你可以把這行放到你的shell的profile里(.bash_profile,.profile等)這樣就不用每次敲一長(cháng)串命令了.
- 登陸CVS服務(wù)器:
- $ cvs login,這時(shí)候 cvs 會(huì )問(wèn)你口令,請把你在 CVS 服務(wù)器上的口令敲進(jìn)去,這里是 laser 在 CVS服務(wù)器上的系統用戶(hù)的口令:
Passwd:xxxxxxxx
成功登陸后將在你的家目錄建立一個(gè) .cvspass 文件,以后就不用輸入口令了.
管理 cvs 服務(wù)器
服務(wù)器可以用了,現在大家最關(guān)心的就是如何管理服務(wù)器,比如,我想讓一些人有讀和/或寫(xiě) CVS 倉庫的權限,但是不想給它系統權限怎么辦呢?
不難,在 cvs 管理員用戶(hù)(在我這里是 cvsroot 用戶(hù))的家目錄里有一個(gè) CVSROOT 目錄,這個(gè)目錄里有三個(gè)配置文件,passwd, readers, writers,我們可以通過(guò)設置這三個(gè)文件來(lái)配置 CVS 服務(wù)器,下面分別介紹這幾個(gè)文件的作用:
passwd:cvs 用戶(hù)的用戶(hù)列表文件,它的格式很象 shadow 文件:
{cvs 用戶(hù)名}:[加密的口令]:[等效系統用戶(hù)名]
如果你希望一個(gè)用戶(hù)只是 cvs 用戶(hù),而不是系統用戶(hù),那么你就要設置這個(gè)文件,剛剛安裝完之后這個(gè)文件可能不存在,你需要以 cvs 管理員用戶(hù)手工創(chuàng )建,當然要按照上面格式,第二個(gè)字段是該用戶(hù)的加密口令,就是用 crypt (3) 加密的,你可以自己寫(xiě)一個(gè)程序來(lái)做加密,也可以用我介紹的偷懶的方法:先創(chuàng )建一個(gè)系統用戶(hù),名字和 cvs 用戶(hù)一樣,口令就是準備給它的 cvs 用戶(hù)口令,創(chuàng )建完之后從 /etc/shadow 把該用戶(hù)第二個(gè)字段拷貝過(guò)來(lái),然后再把這個(gè)用戶(hù)刪除.這個(gè)方法對付數量少的用戶(hù)比較方便,人一多就不合適了,而且還有沖突條件(race condition)的安全隱患,還要 root 權限,實(shí)在不怎么樣.不過(guò)權益之計而已.寫(xiě)一個(gè)小程序并不難,可以到 linuxforum 的編程版搜索一下,有個(gè)朋友已經(jīng)寫(xiě)了一個(gè)貼在上面了.
第三個(gè)字段就是等效系統用戶(hù)名,實(shí)際上就是賦與一個(gè) cvs 用戶(hù)一個(gè)等效的系統用戶(hù)的權限,看下面的例子你就明白它的功能了.
readers:有 cvs 讀權限的用戶(hù)列表文件.就是一個(gè)一維列表.在這個(gè)文件中的用戶(hù)對 cvs只有讀權限.
writers:有 cvs 寫(xiě)權限的用戶(hù)的列表文件.和 readers 一樣,是一個(gè)一維列表.在這個(gè)文件中的用戶(hù)對 cvs 有寫(xiě)權限.
上面三個(gè)文件在缺省安裝的時(shí)候可能都不存在,需要我們自己創(chuàng )建,好吧,現在還是讓我們用一個(gè)例子來(lái)教學(xué)吧.假設我們有下面幾個(gè)用戶(hù)需要使用 cvs:
laser, gumpwu, henry, betty, anonymous.
其中 laser 和 gumpwu 是系統用戶(hù),而 henry, betty, anonymous 我們都不想給系統用戶(hù)權限,并且 betty 和 anonymous 都是只讀用戶(hù),而且 anonymous 更是連口令都沒(méi)有.那么好,我們先做一些準備工作,先創(chuàng )建一個(gè) cvspub 用戶(hù),這個(gè)用戶(hù)的責任是代表所有非系統用戶(hù)的 cvs 用戶(hù)讀寫(xiě) cvs 倉庫.
#adduser
...
然后編輯 /etc/group,令 cvspub 用戶(hù)在 cvs 組里,同時(shí)把其它有系統用戶(hù)權限的用戶(hù)加到 cvs 組里.(見(jiàn)上文)
然后編輯 cvs 管理員家目錄里 CVSROOT/passwd 文件,加入下面幾行:
laser:$xxefajfka;faffa33:cvspub
gumpwu:$ajfaal;323r0ofeeanv:cvspub
henry:$fajkdpaieje:cvspub
betty:fjkal;ffjieinfn/:cvspub
anonymous::cvspub
注意:上面的第二個(gè)字段(分隔符為 :)是密文口令,你要用程序或者用我的土辦法生成.
編輯 readers 文件,加入下面幾行:
anonymous
betty
編輯 writer 文件,加入下面幾行:
laser
gumpwu
henry
這樣就 ok 了,你再用幾個(gè)用戶(hù)分別登陸測試,就會(huì )發(fā)現一切都 ok 了.這里面的原理和說(shuō)明我想就不多說(shuō)了,其實(shí)很簡(jiǎn)單,和系統管理用戶(hù)的概念是一樣的.
現在服務(wù)器和客戶(hù)端都設置好了,那么怎么用呢,我在這里寫(xiě)一個(gè)最簡(jiǎn)單的(估計也是最常用的)命令介紹:
首先,建立一個(gè)新的CVS項目,一般我們都已經(jīng)有一些項目文件了,這樣我們可以用下面步驟生成一個(gè)新的CVS項目:
- 進(jìn)入到你的已有項目的目錄,比如叫 cvstest:
- $cd cvstest
- 運行命令:
- $cvs import -m "this is a cvstest project" cvstest v_0_0_1 start
說(shuō)明:import 是cvs的命令之一,表示向cvs倉庫輸入項目文件.
-m參數后面的字串是描述文本,隨便寫(xiě)些有意義的東西,如果不加 -m 參
數,那么cvs會(huì )自動(dòng)運行一個(gè)編輯器(一般是vi,但是可以通過(guò)修改環(huán)境變量
EDITOR來(lái)改成你喜歡用的編輯器.)讓你輸入信息,
cvstest 是項目名稱(chēng)(實(shí)際上是倉庫名,在CVS服務(wù)器上會(huì )存儲在以這個(gè)名字
命名的倉庫里.)
v_0_0_1是這個(gè)分支的總標記.沒(méi)啥用(或曰不常用.)
start 是每次 import 標識文件的輸入層次的標記,沒(méi)啥用.
運行下面的命令:
$cvs checkout cvstest從倉庫中檢索出cvstest項目的源文件.
如果你已經(jīng)做過(guò)一次checkout了,那么不需要重新checkout,只需要進(jìn)入cvstest項目的目錄,更新一把就行了:
$cd cvstest一下即可.又或者你不想直接更新,只是想看看有沒(méi)有更新的東西,那么:
$cvs update
$cvs status這時(shí)后會(huì )打印出一長(cháng)串狀態(tài)報告(你可能需要用類(lèi)似less這樣的命令分頁(yè)顯示,或者定向到一個(gè)輸出文件里慢慢看.),對項目中的每個(gè)文件有一份狀態(tài)報告,類(lèi)似這樣:
===================================================================
File: foo.c Status: Up-to-date
Working revision: 1.1.1.1 'Some Date'
Repository revision: 1.2 /home/cvsroot/cvstest/foo.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
這里最重要的就是 Status 欄,這里總共可能有四種狀態(tài):
Up-to-date:表明你要到的文件是最新的.
Locally Modified:表明你曾經(jīng)修改過(guò)該文件,但還沒(méi)有提交,你的版本比倉庫里的新.
Needing Patch:表明有個(gè)哥們已經(jīng)修改過(guò)該文件并且已經(jīng)提交了!你的版本比倉庫里的舊.
Needs Merge:表明你曾經(jīng)修改過(guò)該文件,但是偏偏有個(gè)不識相的也修改了這個(gè)文件,而且還提交給倉庫了!
如果你只是想保持軟件的同步的話(huà)(象我),那么上面的東西就足夠用了.可是如果多人協(xié)作開(kāi)發(fā)項目的話(huà),可就不是了這么簡(jiǎn)單了.當你參加項目,維護文件時(shí),就需要更多命令,比如說(shuō)你我都是某 nasdaq 項目的開(kāi)發(fā)人員:
1,你對某個(gè)文件做了修改,比如說(shuō)改了ceo.c,加了一行程序:printf("where can I find VC to cheat!");
改完之后你要把修改提交給倉庫,用命令:
$cvs commit -m "add a complain" ceo.c
或者就是:
$cvs commit -m "worry about money"
讓cvs幫你檢查哪個(gè)文件需要提交.
2,當我開(kāi)始干活的時(shí)候,可能我先:
$cvs status
一把,這時(shí)候我會(huì )看到:
==================================================================
File: ceo.c Status: Needing Patch
Working revision: 1.1.1.1 'Some Date'
Repository revision: 1.2 /home/cvsroot/nastaq/ceo.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
于是我知道有人改了ceo.c,于是我就:
$cvs update ceo.c
或者干脆:
$cvs update
把ceo.c這個(gè)文件更新為最新版本,然后再干活.然后提交.
如果這天你修改了coo.c,加了一行 puts("how about another kind of bragging?");
并且提交了,但是這時(shí)候我已經(jīng) $cvs status 過(guò)了,就是說(shuō)我不知道你的修改.
而我加了一行printf("You must shamelessly and seems knowingness to act as a coo");
并且傻乎乎地提交:
$cvs commit coo.c
這時(shí)候,CVS會(huì )告訴我
cvs commit: Examing .
cvs server: Up-to-date check failed for 'coo.c'
cvs [server aborted]: correct above error first!
于是我知道有個(gè)狗屎在我修改文件的當口做了提交,于是我
$cvs update
這時(shí)cvs會(huì )報告:
RCS file: /home/cvsroot/nasdaq/coo.c,v
retrieving revision 1.1.1.1
retrieving revision 1.2
Merging differences between 1.1.1.1 and 1.2 into coo.c
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in coo.c
C coo.c
告訴你coo.c有版本沖突,于是我編輯coo.c,這時(shí)一般文件里看起來(lái)象這樣:
...
printf("You must shamelessly and seems knowingness to act as a coo");
<<<<<<< foo.c
=======
...
puts("how about another kind of bragging?");
>>>>>>> 1.2
...
于是我把上面改成:
printf("You must shamelessly and seems knowingness to act as a coo");
puts("how about another kind of bragging?");
然后
$cvs commit -m "merged" coo.c
于是下回你再更新的時(shí)候就有新的補釘要打...如此往復,直到完成所有修改.
不過(guò)這里有一些要注意的地方就是刪除程序,如果你刪掉一行對你可能沒(méi)有用的程序
puts("to be honest"); 而我不想刪除(因為我有用),而我不知情地直接:
$cvs update
了,那么我的這行程序也完蛋了,所以這里我們要注意所有開(kāi)發(fā)人員的協(xié)調,千萬(wàn)不要亂刪東西,大不了用
#if 0
#endif
宏定義對括起來(lái).實(shí)在要刪東西,那最好先標記一個(gè)版本:
$cvs tag v_0_0_1
然后你可以發(fā)布并刪除你自己的工作目錄里這個(gè)版本的文件(注意:不是刪除倉庫里的.):
$cvs release -d nasdaq
然后你再生成一個(gè)新分支:
$cvs rtag -b -r v_0_0_1 v_0_0_1_1 nasdaq
然后再建立v_0_0_1_1的分支
$cvs checkout -r v_0_0_1_1 nasdaq
編輯并修改這個(gè)分支的文件,這樣的做法比較好.
不過(guò)要注意的是,新標記和新分支的建立最好由項目的管理人員負責,否則每個(gè)人都做一個(gè)分支,那么倉庫就太亂了.因此,比較的開(kāi)發(fā)人員之間的直接溝通是不能忽略的.一般來(lái)說(shuō),在互聯(lián)網(wǎng)上的標準模式是有一個(gè)管理員(可能自己并不寫(xiě)程序),有一個(gè)郵遞列表,大家都在郵遞列表上交流看法和做各種決議.當形成決議之后,管理員做一個(gè)新版本的標記.以此循環(huán).
還有一些命令,比如要增加一個(gè)文件 garbage_china_concept_stocks_list:
$cvs add garbage_china_concept_stocks_list
然后還要:
$cvs commit garbage_china_concert_stocks_list
看起來(lái)有點(diǎn)象數據庫里的事務(wù)?的確是這樣.CVS維護著(zhù)一個(gè)本地的參考文件(在CVS/Entries里),這樣提交的時(shí)候就可以一次地把所有改變放到服務(wù)器端,這樣也更安全.同樣,如果想刪除文件 bankrupted_web_site:
$rm bankrupted_web_site
$cvs remove bankrupted_web_site
$cvs commit bankrupted_web_site
3,一些小技巧:
$Header$ 標記:把這個(gè)標記放在文件的任何地方都會(huì )被 cvs 替換成最后修改的 cvs 用戶(hù)名,該文件當前版本號,最后修改時(shí)間,該文件的 cvs 倉庫路徑,看起來(lái)象下面這個(gè)樣子:
// $Header: /home/cvsroot/simhost/simhost.cpp,v 1.2 2001/04/20 08:26:10 jqliu Exp $
一般我們把它放在開(kāi)頭,這樣對程序員修改文件非常便利,很多時(shí)候你只要看一眼開(kāi)頭就知道文件是否最新.
$Id$標記:把這個(gè)標記放在文件的任何地方都會(huì )被 cvs 替換成最后修改的 cvs 用戶(hù)名,該文件當前版本號,最后修改時(shí)間,該文件的 cvs 倉庫路徑,看起來(lái)象下面這個(gè)樣子:
$Id: simhost.cpp,v 1.3 2001/04/24 02:27:36 simhost Exp $
好了,上面所有的東西,估計就是我們用cvs時(shí)80%情況下用的命令和內容,包括文件的更新,提交,沖突的解決,分支的派生,增刪文件等.實(shí)際上cvs的功能之強大,遠遠超出我在這里描述的內容,我這個(gè)"速成"也就管不了太多了,希望隨著(zhù)時(shí)間的推移,我們能夠更加有效地使用CVS.也希望大家能夠不斷補充這篇文章,最后能夠成為手冊,而不僅僅是速成.當然,還要更多地參考別的文檔.
參考資料:
http://www.loria.fr/cgi-bin/molli/wilma.cgi/doc.865331095.html
http://www.loria.fr/~molli/cvs/doc/cvs_toc.html
文章來(lái)源于領(lǐng)測軟件測試網(wǎng) http://kjueaiud.com/