每个~写E序的h也许都有q这L(fng)体验Q对~写的程序作?jin)一些修改,几天以后Q我们可能发Cơ的修改D?jin)其它的矛盾Q甚至编译不能通过Q但q时我们却很难找到自己刚在哪里作?jin)改动。一般说来,我们L希望自己完整的记录下一个程序开发的q程Q记录下q个E序的每一Ҏ(gu)q和调整。或许我们可以用备䆾的方法来解决q个问题Q于是我们徏立一个backup的目录,定期或不定期的将自己的源E序打包放进去,直到某一天整个硬盘都被这些文件撑满。这当然有些夸张Q但q种机制带给我们的不方便是显而易见的。更q一步,现在一个Y件品的开发,一个h单枪匚w很难完成。可能是包含几个、几十个乃至上百个程序员协作开发,q时的源代码又该如何理Q解决问题的Ҏ(gu)是使用CVS?
CVS - Concurrent Versions System(q发版本理pȝ)是一个版本控制管理系l,它是目前最为广泛用的一个系l。从gftp到gtk到KDEQ你几乎可以在每一个你熟?zhn)的自pY件的源码里看到它的踪q?下面我们?x)知道,它的t迹指一个称为cvs的子目录)。同P你也可以在几乎每一个Linux的发行版本里看到CVSpȝ。可以说Q如果失M(jin)CVSQ现有的许多多h协作、自由开发的软g都会(x)在一定程度上放慢自己发展的步伐? CVS到底有哪些功能,使得它有如此强大的魅力呢Q?br /> 1.CVS能做什么?
如上所_(d)CVS首先是一个版本管理系l,它可以保留Y件开发过E中的每一个版本的信息Q包括谁、在何时、作?jin)什么样的修改以?qing)?f)什么作q样的修改等。这个功能和以前行于Linux和Unix的版本管理系lRCS(Revision Control System)和SCCS(Source Code Control System)很象。但CVS的功能远非仅此。它的最大的特点是它的ƈ发性,卛_支持分布式项目的开发。在互联|席卷一切的今天Q这个功能太为重要了(jin)。小C个办公室内部开发一个OApȝQ大到KDE组利用互联|开发新版本的KDEQCVS都可以一展n手。一个程序员开发出?jin)自p责模块的新版本后Q迅速的通过CVS让开发组的每一个成员都分n自己的最新成果。甚臻ICVS通过特定的机制允许多个程序员同时修改同一个源E序文g?br /> 另外CVS增强的目录结构以?qing)对二进制文件良好的处理Q都使得它远q优?sh)其它的版本理pȝ。最后,必须一提的是CVS是基于RCS开发而成的?br /> 2.如何得到CVS?
CVS在几乎包含在所有的Linux发布版本中,如RedHat、Turbo Linux、Slackware以及(qing)国的红旗、Xteam Linux{。你可以试着敲一下cvs命o(h)Q大多数情况下都?x)出CUsage: cvs开头的一堆信息,提示你如何用cvsQ这意味着在你的机器上早已有了(jin)CVSQ只是遗憄是它一直未被你发现和利用。运气不好的话,你会(x)看到形如cvs: Command not found.的提C,q意味着你的机器没有安装CVS。这时你有两U选择。一是找C的Linux安装盘,从那里安装CVS。例如在使用RPM方式安装的Linux(上面介绍的几个发布版本中g除了(jin)SlackwareQ其他都?发布版本中,扑ֈcvs***.rpmQ用rpm命o(h)q行安装。第二种方式是到一些站点cvs的源代码Q然后遵循里面附带的指导q行安装Q需要的读者请讉K站点www.cyclic.com/或者http://www.loria.fr/~molli/cvs-index.html?br /> 3.CVS的基本用方?br /> 在这一节里Q我们来学习(fn)掌握CVS的一些基本用方法。现在我们假定已l安装好?jin)CVSQƈ且我们打用它来理自己正在开发的一个YӞ软g名叫netantsQ它存放在硬盘(sh)一个叫做netants的目录里Q目前里面有?jin)文件netants.c、netants.h、http.c、http.h和Makefile。我们ƈ没有和它人协作开发这个YӞ也没有利用Internet或者Intranet来开发这个Y件。或许,它只是自q一个业余作品,试图写出一个比Windows下的|络蚂蚁更好的下载工具出来?br /> 3.1初始化CVS
我们首先要用的命o(h)是cvs initQ这个命令用来初始化CVSpȝ。正如我们所看到的,所有的CVS命o(h)都以cvs开_(d)然后在后面紧跟命令、参数和一些选项。初始化CVSpȝ主要是ؓ(f)?jin)创Z个ؓ(f)CVS所使用的源码储存库(repository)。创建的时候,需要指定在那个目录下创个源码储存库。有两种Ҏ(gu)来指定目录。一是利?-d "选项来指定,例如Q?d /usr/local/cvsroot。另一U更方便的方法是在shell里设定一个名叫CVSROOT的环境变量。用csh或者tcsh的用户可以用命令setenv来设定,在文?csh rc或者文?tcshrc里添加入下的一行:(x) setenv CVSROOT /usr/local/cvsroot 使用sh或者bash的用户需要在文g.profile或者文?bashrc里添加如下两行:(x) CVSROOT=/usr/local/cvsroot export CVSROOT 讄?jin)环境变量CVSROOT后,我们q行命o(h)cvs initQCVS在指定的目录下面徏立自己所需要的一些文Ӟ以后我们使用CVS理的Q何项目,都会(x)被CVS储存在这个目录之下。不q千万要注意的是Q永q不要去试图修改q个目录下的文g。这个目录是由CVS自己q行理的,ȝ的改动可能会(x)D你丢׃部分或全部的交由CVS理的源代码或其他资源?br /> 3.2导入目到CVS中去
初始化结束以后,我们p真正开始利用CVS来管理自qE序|络蚂蚁?jin)。第一步,我们这个项目交由CVS理。用如下的CVS的import命o(h)Q将源程序导入到CVS的源码储存库中去Q cd netants cvs import -m "start my project: Netants" netants yoyo start q个命o(h)看v来有些复杂,需要解释一下。import是cvs的导入命令,默认状况下,它@环的当前目录下的所有文?包括子目?导入到源码库(即CVSROOT指定的目?里去?m "start my project: Netants"告诉CVS你对q一步操作的说明。这是CVS强制要求的,如果你没有用这U?m "字符?的选项QCVS会(x)弹出一个文本编辑器(如果自己不特别指定的话,在Linux下一般是viQ而在Windows下则是Notepad)Q让你输入一些说明信息它才Ş休。netants是这个项目被CVS存储时的路径名,即CVS在创徏一?CVSROOT/netants的目录,q在此目录下存放此项目的文gQ当?dng)它不是原不动的存储QCVS?x)做一番处理。最后两个字W串讑֮?jin)两个标?tag)Q现在ƈ没有什么用处,但它们同hCVS指定必需的,所以我们添上这两个参数。执行此命o(h)ӞCVS自动所有的文g版本设ؓ(f)1.1Q这是它所认ؓ(f)的最低版本。以下ؓ(f)执行上述命o(h)后的昄信息QN netants/netants.c N netants/http.c N netants/http.h N netants/netants.h N netants/Makefile No conflicts created by this import N表示NewQCVS成功的加载了(jin)q些文gQƈ没有发现冲突。 上面的命令稍长了(jin)一些,而且昑־有些J琐Q相信我QCVS不Lq样的,q点"J琐"相对它给我们带来的便利是完全可以忽略不计的?br /> 3.3从CVS中导出项目
好了(jin)Q我们把自己的netants的项目交l了(jin)CVSȝ理,现在Q我们完全可以删除原有的存储我们代码的netants目录(当然Q安全v见,你或许应该再做一ơ备份,q希望是最后一?。我们要q行开发工作了(jin)Q徏一个目录,叫什么呢Q就叫worktmp吧。我们进到此目录下,执行命o(h)cvs checkout netantsQ我们将?x)看到如下的信息Qcvs checkout: Updating netants U netants/Makefile U netants/http.c U netants/http.h U netants/netants.c U netants/netants.h CVS在当前目录下建立一个叫做netants的目录,我们原先的代码文仉在这个目录下出现?jin),而且q多?jin)一个名为CVS的目录。目录CVS下面存放的是一些文本文Ӟ记录?jin)CVSROOT的位|、此目对应源码库中那个目录{一些信息?br /> 3.4保存?sh)改到CVS?br /> 现在Q我们开始艰苦卓的~程工作。经q数十分钟、数时乃至数天的工作,我们对原有的代码做了(jin)较大的修改,现在要告一D落?jin)。我们将修改的内Ҏ(gu)交给C(j)VSQ于是,我们需要执行命令 cvs commit -m "Made some useful changes on some files" q时Q我们将?x)看到CVSl出一些提CZ息,它扫描ƈ比较此目录下的现有文件和它在源码库中保存的原有文Ӟ做了(jin)修改的文件将被更斎ͼq且有了(jin)新的版本P(x)1.2?m参数如同前面所_(d)是ؓ(f)?jin)不惛_启动一个文本编辑器来让自己输入。如果我们仅是修改了(jin)其中一两个文gQ我们可以在上面的命令的最后附上文件名Q这样CVS只会(x)比较、更新指定的文g。注意的是,和自己做备䆾不同QCVS只是保存?sh)(jin)不同版本之间的差异Qƈ没有完整的保存各个版本。现在,你是不是觉得CVS有点用处?jin)?br /> 3.5d文g到项目中
有一天,我们开始考虑l我们的|络蚂蚁加上从ftp站点下蝲文g的功能,于是Q我们需要在原有的项目里d两个文gQftp.c和ftp.h。首先,我们在工作目录下建立q编辑、修攏V生成了(jin)q两个文Ӟ然后我们使用命o(h)add命o(h)来添加。 cvs add ftp.c ftp.h 此时Q文件ƈ没有真正的被dQ只是相当于"注册"?jin)一下,要ɘq个q程生效Q我们仍焉要用commit命o(h)Q cvs commit ftp.c ftp.h -m "Add two files: ftp.c and ftp.h" 此时QCVS把q两个文件添加到目中去Q他们的版本均ؓ(f)初始?.1。 3.6从项目中删除文g
除了(jin)d以外Q我们有的时候可能需要删除某个文Ӟ例如我们发现文gnetants.h其实没有什么用。于是,我们执行下面几个命o(h)来完成删除工作:(x) rm netants.h cvs remove netants.h cvs commit netants.h -m "Delete a file." 要注意的是,CVS只是删除?jin)当前版本的netants.hQ它以前的版本依然存在,除非它恰好仅?.1版本?br /> 3.7讑֮特定版本受
l过一D|间的工作Q程序已l初兯模,形成?jin)较E_的版本。这个时候,netants.c可能已经?.4版本Q而http.c可能?.5版本Q而我们希望将当前的代码作一个版本发布。此Ӟ我们需要用的是tag命o(h)。这个命令赋予指定的一个或多个文g一个给定的文本形式的版本号。版本号必须以字母开始,可以包含数字、下划线和连接符?-)。我们想l当前项目的所有文件赋予相同的版本hQ可以不指定文g或\径参敎ͼCVS默认选择当前目录下所有在CVS中注册的文g(循环q子目录)。下面既是一个例子:(x) 键入命o(h)Qcvs tag release0-1 提示信息Q cvs tag: Tagging . T Makefile T ftp.c T ftp.h T http.c T http.h T netants.c q样当前版本的所有文仉有了(jin)一个叫做release0-1的版本代受当我们需要这个版本的时候,我们使用-r (版本代号)参数来得到指定的版本。例如命令:(x) cvs checkout -r release0-1 netants 在当前目录下徏立netants目录Qƈ导出所有版本代号ؓ(f)release0-1的文件?br /> 3.8更新当前工作目录中的文g
q里使用的命令ؓ(f)updateQ它?yu)比较指定的在CVS源码库中的文件和当前目录下的文gQ如果CVS源码库中有更高版本的源文Ӟ则更新当前目录下的文件。这个功能主要是多h协作开发项目时使用的,让你?qing)时分n同伴的工作成果。但它另外一个重要的用途,同样适用于单人开发的目。这个用途需要?j参数Q我们看下面的例子:(x) cvs update -j 1.5 -j 1.3 netants.c q个命o(h)的功能是Q在当前目录的netants.c文g中,忽略从版?.3到版?.5所作的修改。毫无疑问,对程序员来说Q这是一个非帔R要的功能。因为在某个阶段我们对程序所作的修改在现在可能会(x)被视为是无效乃至错误的,q个功能很好的解决了(jin)q个问题。 在更新的q程中,CVS执行一个自动合q的q程。例如我们的工作目录中的netants.c文g版本?.1Qƈ且我们已l对此文件作?jin)一番修改,而CVS源码库中的是版本2.2Q此时我们执行update命o(h)ӞCVSq不是简单的版?.2覆盖版本2.1Q而是试图自版本2.1到版?.2的修Ҏ(gu)加到当前目录中的文g中去Q如果它和我们刚刚所作的修改有冲H,则C(j)VS?x)以字符?>>>>"表示由冲H发生,期待用户M攏VCVS拒绝接受包含有上q特定字W串的文件。下面即是一个冲H的例子Qnetants.c:版本?.2Q保存在CVS中……getPartFile( ); showFinished(); return(A); }…?br /> netants:版本?.1l过我们的修改……getPartFile( ); return(B); }……我们执行命令cvs update netants.c后,会(x)包含如下内容的新的netants.cQ?br /> ……getPartFile( ); showFinished(); >>>>>> 2.2 }…… 除非我们做出修改q删?>>>>>>"Q否则在执行cvs commit的时候,netants.c不?x)更新原有?.2版本?br /> 4.CVS的其他功?br /> CVS当然q不止上面所说的q些内容Q这些仅是CVS的基本功能,CVSq有许多重要的功能,如上面所说的|络工作方式、支持二q制文g{。下面我们对q些功能作简单的说明?br /> 4.1 CVS的网l工作方式
CVS的网l功能采用client-serverl构Q两地均需安装CVS。CVS采用rsh方式或者口令校验方式进行工作。对client端,同前面讲q的讄环境变量CVSROOT一P用户需要设|新的环境变量CVS_SERVERQ指明CVS在server上的路径Q例如:(x)/usr/local/cvsroot1。CVS?d参数指定路径名,它后面可以用Q?local或server或ext)Q来指明是在本地q是在异地服务器上,默认当然是在本地Q正如我们在初始化CVS一节所使用的那栗下面的命o(h)假定我们的CVS服务器ؓ(f)cvs.rdcps.ac.cnQ用户名为crazyyaoQCVS源码库在服务器的/usr/local/cvsroot1目录下,我们的工作项目还是netantsQ我们用rsh方式导出目文gQ cvs -d : server : crazyyao@cvs.rdcps.ac.cn :/usr/local/cvsroot1 checkout netants 采用口o(h)校验方式Ӟ需要对修改pȝ文g/etc/inetd.confQ以便inetd知道如何分配、处理CVS Server的请求和响应。CVS?x)在源码库所在的目录中创Z个名为passwd的口令文Ӟ对用戯行校验。用口令校验时QCVS支持匿名登陆Q而且CVS目用户可以讄目中文件的存取权限。 关于如何配置CVS使之工作在网l方式下的详l信息请参考CVS的文档?br /> 4.2 CVS的分支和融合功能
CVS增强的目录工作方式得CVS提供分支和融合功能。有的时候,当项目进展到一定程度时Q可能需要暂时中断,d另外一些修改和发展。例如,我们的Y件原有版本ؓ(f)1.0Qƈ已提交用户用,现在正在开?.0。某一天,1.0的用户发C(jin)一个较大的bug或者需要添加某个短的功能Q这时我们不能让用户L?.0版本Q又必须l用h意的{复Q比较理想的解决方式是把现在的工作先攑ֈ一边,另开一个分支,L用L(fng)需要。当此分支完成后Q程序源q可以用CVS的融合功能将q一部分修改d到我们开?.0版本的主工作q程中去。 创徏分支可以使用tag -b命o(h)。例如下面的命o(h) cvs tag -b netants-1-0-patch 在当前的工作目录的基上创Z个叫做netant-1-0-patch的分支。 融合的命令参数是-jQ我们在前面已经提及(qing)它了(jin)?br /> 4.3 CVS处理二进制文件的功能
CVS可以保存?sh)进制文Ӟ但和文本文g相比Q它的许多功能׃(jin)。对于文本文ӞCVS可以辨别出文件的M一Ҏ(gu)动,但对于二q制文g它无能ؓ(f)力。但是,CVS可以区分出文件作?jin)改动,q会(x)提示用户自己修改、保存。与文本文g不同QCVS保存?sh)进制文件每个版本的完整信息。在操作二进制文件时Q需要添加参?KBQ以便告诉CVS不把它当作文本文件看待?br /> 4.4 CVS比较文g的功能
执行的命令ؓ(f)diffQ这个功能和shell下的diff功能基本一栗例如下面的命o(h)比较CVS源码库中的最新的netants.c文g和当前目录下netants.c文g有什么不同:(x) cvs diff netatns.c
5.l束?br /> 通过上面的介l,希望能激起大家用CVS的兴,q掌握用CVS的一些基本方法。碰到困难时Q别忘(sh)(jin)阅CVS附带的手册,不过Q它有厚厚的172c(din)希望CVS能加速你的Y件开发?/p>