分类: LINUX
2006-05-10 16:23:31
Client/ Server结构:
CVS以客户端/服务器模式工作,所有的用户都在客户端进行CVS操作,而所有命令的执行都在CVS服务器端进行。
CVS仓库(repository):
又称master copy,是CVS系统保存软件资源的地方。所有项目的所有文件的所有版本都保存在这个仓库中。
CVS服务器上,一个源代码仓库被称为一个repository,一个server上通常可以运行多个repository,每个repository都是完全独立的,可以有不同的用户列表和访问规则。在一个repository之下,文件按照module组织,每一个module就相当于一个工程,大致上相当于Source safe里面的project。
工作拷贝(working copy):
从CVS服务器端取出的,保存在我们正在使用的客户端计算机上的代码拷贝。一个项目在开发的过程中会有多个工作拷贝。典型的情况是一个开发小组在开发一个项目,所有的小组成员都共用同一个CVS服务器,共用同一个CVS仓库,共用该项目在CVS仓库中的同一个版本库(version base),但是每个小组成员都有一个属于自己的工作拷贝。每个人的工作拷贝在工作过程中独立进行修改,互不干扰。大部分情况下,这些工作拷贝各不相同。工作拷贝的独立性防止了协同开发中相互干扰,但是也带来了一些问题:
1. 文件更新问题:一个开发者得代码更新了,如何让其他开发人员得到他的最新的代码
2. 协同通信问题:一个开发者的代码更新了,如何让其他开发人员知道他做了什么修改
导入(import)代码:
将未被CVS进行版本管理的代码引入CVS系统之中,由CVS开始对它进行版本管理。
检出(check out)代码(创建工作拷贝):
从服务器取出代码,这也就是创建工作拷贝的过程。从CVS服务器取出代码的时候,CVS会根据用户的要求逐个检查文件的版本,选择正确的版本发送给用户。
提交(commit)代码:
将代码送到服务器保存,commit又叫作check in。CVS服务器在将代码存入仓库的时候要进行版本检查,以及做相应的记录。
CVS日志:
即log message,是CVS用来记录每次操作的内容和目的的信息。在提交和导入等操作的时候,操作者必须提供一些注释信息,这些注释信息被CVS作为日志的一部分。日志信息可以用cvs log命令方便地查看。CVS日志对于跟踪软件的变化过程具有重要价值。有关变化控制、配置审计等内容,日志信息对这些管理内容十分重要。
Module模块:
一个目录层。一个软件工程通常作为一单一模块存放在库中。
版本:
对于文件,一个开发者向CVS提交他所作的修改的时候,就形成了一个新版本;对于整个软件项目,一个版本就是不停改变的项目在某个时间点上的一个快照。
Release发行版本:整个产品的版本
Revision修订版:单个文件的版本。当你第一次增加一个文件到repository的时候,它会有一个初始revision是1.1,以后每次提交,就会增加到1.2,1.3...
Branch:
Branch是一棵正常生长的代码树中的枝杈。开始的时候,任何一个module都有一个主枝。一个branch最终要么被合并到主干中去,要么被结束。branch通常用来debug,如果这个bug被fix了,修改bug的代码应该被合并到主枝上去。一个branch也可能经历多次与主枝的合并。
Tag标签:
Tag用来进行标识必要的信息,在一个开发的特定期对一个文件集给定的符号名。当您进行一次公开发布之前,您有必要对主枝标示"release 1.0"。这样您以后就可以随时回到这个版本。
更新(update):
在协同开发的环境下,将其他人所作的最新修改从CVS仓库中取到你的工作拷贝中,从而使得你的工作拷贝与仓库中的最新版本保持一致,也与项目的最新进展保持一致。更新是同步各个工作拷贝的手段。如果你只是个人单独开发,这个概念就没有用了。
冲突(conflict):
在协同开发的环境下,当两个开发人员对同一个文件的同一行代码进行修改,并且依次提交CVS仓库的时候,就发生了冲突。因为CVS不能在一个项目里保存两个相同名字的文件,而对同一个文件,它的一行代码只会是一种结果,而不是两种。这种冲突需要开发者手工消除,并提交到CVS仓库中形成解除冲突之后的新版本。
CVS服务器是一个接受远程和本地CVS命令请求的服务程序,安装的整个过程都是由超级用户执行的。
下面假设我们选择一台LINUX服务器,确认是否是否安装了安全服务程序SSH。这台服务器的主机名(hostname)为cvshost。
确认是否安装SSH,以超级用户身份在命令提示符下执行:
$ rpm –q ssh
确认是否安装CVS服务器软件包,以超级用户身份在命令提示符下执行:
$ rpm –q cvs
cvs-
如果系统给出类似于如上所示的信息,你就可以跳过这一节,直接进入下一节。如果系统给出“package cvs is not installed”(cvs软件包尚未安装)的消息,可以下载RPM格式的安装软件包或者.tar.gz格式的源代码压缩文件。
linux下ssh发布的权威网站是,在LINUX系统下进入命令提示符状态,运行su命令并输入超级用户口令,进入RPM文件或者.tar.gz压缩文件存放的目录。
如果你下载了rpm程序包,那么可以使用如下命令来安装:
$ rpm -i ssh*.rpm
如果你下载的是源程序包,那么可以先解压缩该包,然后进入ssh目录来安装。
$ tar -vxzf ssh*.tar.gz
$ cd ssh*
$ ./configure
$ make
$ make install
如果没有出现任何错误信息,ssh安装成功。
如果服务器设置在Unix上,在安装CVS服务器之前需要安装如下服务程序:
a)安装zlib软件包:zlib-
zlib是通用的压缩库,提供了一套in-memory 压缩和解压函数,并能检测解压出来的数据的完整性(integrity)。zlib支持读写gzip (.gz) 格式的文件。openssh使用zlib库作为压缩引擎。
zlib软件包用custom安装。安装过程中无任何出错信息,则退出后安装成功。
b)安装prngd软件包:prngd-
OpenServer使用prngd(Pseudo Random Number Generator Daemon)得到随机数。
prngd软件包用custom安装时会自动建立prngd用户,并要求输入密码。安装过程中无任何出错信息则退出后安装成功。
在/etc/rc2.d/P88USRDEFINE文件的末尾添加如下一行:
/usr/local/sbin/prngd /usr/local/var/prngd/prngd-pool&
c)安装openssh软件包:openssh-3.4p1-VOLS.tar
许多网络程序,如telnet、rsh、rlogin或rexec,用明文(plain text)传送口令和秘密的信息,所以就可利用任何连接到网络上的计算机监听这些程序和服务器之间的通信并获取口令和秘密信息。OpenSSH就是那些过时的、不安全的远程登录程序,如:telnet、rlogin、rsh、rdist或rcp的替代品。
openssh软件包使用custom安装。安装过程中出现This platform does not support both privilege separation and compression Compression disabled可以安全忽略,只是不能使用压缩。
如果是rpm格式的文件,运行:
$ rpm –i cvs*.rpm
如果是.tar.gz压缩格式的文件,假设为cvs-
$ gzip –d cvs-
$ tar –xvf cvs*.tar
$ cd cvs-
$ ./noautomake.sh
$ ./configure
$ .make
$ make install
安装结束后,如果系统没有给出任何错误信息,你的CVS服务器就已经安装完毕了。
安装好CVS服务器软件包之后,需要选定CVS将用户的源代码等软件资源和CVS的系统文件保存在什么地方,也就是选定CVS仓库的位置。CVS仓库的内容在逻辑上十文件的版本结构树,每个文件有自己的版本实体,CVS只存储不同版本之间的改动,从而节省空间。
假如选择scmrepository目录作为CVS仓库的位置,执行:
$ mkdir scmrepository
$ cd scmrepository
下面,我们再在CVS仓库下,为以后所有的项目源代码等软件资源建立一个目录。
$ mkdir myproject
在确定CVS仓库的位置之后,初始化CVS服务器。初始化之前,可以设置CVSROOT环境变量(见CVS环境设置中对CVSROOT的设置),指向仓库的绝对路径,然后调用CVS的init命令。也可以用如下方式:
$ cvs –d $HOME/scmrepository init (注:路径根据实际的设置而定)
许多CVS的命令允许用这种方式指定cvs的根目录。
你的源代码的工作拷贝也可以存在不同于CVS库机器上;这种方式CVS以client/server的模式运作。你先运行一台装有自己工作目录的电脑,它被称为客户机(client);然后告诉它你将连到一台装有CVS库的电脑,它被称为服务器(server)。通常,使用远程CVS库除了CVS库名称格式不同,其他方面与本地机没什么区别:
:method:[[user]@]hostname[:[port]]/path/to/repository
在集成开发环境中,建议项目经理建立cvsadmin用户进行cvs管理。项目经理根据项目需要建议创建组(下面假设cvs组)和项目组成员对应的cvs用户。
CVS的权限设定基本上遵循下面几点规则:
1. CVS用户必须拥有对$CVSROOT/CVSROOT目录及其中所有文件的读权限;
2. CVS用户必须拥有对$CVSROOT/CVSROOT目录下history文件的写权限;
3. CVS用户如果希望拥有修改某个项目(CVS系统中称之为模块)代码等资料的权利,必须拥有对该目录的写权限。
4. 在unix/Linux系统中,对一个目录的读/写的权限必须包括对所有上级目录的读权限和可执行权限。
对用户和权限设置建议如下:
1、 在项目开发环境中,单独建立cvsadmin用户,在该用户环境下建立CVS源码库,由项目经理负责管理;
2、 根据管理的需要以及不同的项目建立不同的组(groupadd),如cvs管理员组,cvs普通帐户组;
3、 根据项目需要建立项目成员对应的CVS普通帐户,将用户归属于不同的组,使项目成员能使用CVS进行操作,权限设置的策略遵循上述的几点规则;
4、 在源码库中根据不同的项目分别建立不同的目录,原则上一个项目一个顶层目录,并对目录进行权限设置,使得项目(模块)对应的目录的组属性设定为对它有控制权的组,对非项目用户进行必要的安全设置,如只读不写等。
5、 保证多数CVS用户没有对仓库中的CVSROOT目录拥有UNIX系统级权限。
1、设置CVSROOT环境变量
客户端配置中最重要的是CVSROOT环境变量的设置。其格式是:
:ext:username@hostname(或者IP地址):CVS仓库路径
下面一一介绍各个字段的意义:
n “hostname”是CVS服务器的主机名,它以用CVS服务器的IP地址代替。
n “username”是“你”——使用CVS客户端的用户在服务器端的帐号/用户名。注意,这个用户名与你在客户端的帐号/用户名没有任何必然关系。CVS客户端程序把这个用户名发给CVS服务器,CVS服务器就要求用户输入他在CVS服务器上的口令,并进行认证。
n “CVS仓库路径”是CVS客户端发送给服务器的另外一个参数。由于CVS服务器上可能有多个不同的仓库,所以如果不指定这个仓库路径,CVS服务器就不知道用户想访问哪个仓库。实际上,CVS服务器的“hostname”(或者IP地址)和仓库路径唯一确定了网络上的一台计算机上的一个目录。这个目录就是你要访问的仓库所在的目录。
n “ext”是一种CVS远程访问方式。
综合上面四条,这些信息都是CVS的客户端/服务器方式必须的,如果没有这些信息中的任何一条,CVS客户端都无法发起和完成到CVS服务器的连接。
a)对于LINUX系统:
(sh/bash)用编辑器打开$HOME/.profile或者$HOME/.bash_profile文件,在文件末尾添加:
export CVSROOT=:ext:jyang@192.168.1.100:/src/master
(CVS管理员:export CVSROOT=/src/master)
(csh)用编辑器打开$HOME/.cshrc文件,在文件末尾添加:
setenv CVSROOT=:ext:jyang@192.168.1.100:/src/master
(CVS管理员:setenv CVSROOT=/src/master)
b)对于UNIX系统,编辑.profile文件,添加如下:
CVSROOT=:ext:cvsuser1@192.168.1.100:$HOME/scmrepository
(对CVS管理员:export CVSROOT=$HOME/scmrepository)
export CVSROOT
2、 设置环境变量CVS_RSH=ssh,设置方式和上面的相同。
3、 在PATH变量中加入cvs的路径/usr/local/bin
4、 添加完毕,保存修改,并关闭编辑器。
5、 运行. .profile
6、 测试环境变量的配置是否正确,执行如下命令:
$echo $CVSROOT
:ext:cvsuser1@192.168.1.100:/usr/cvsadmin/scmrepository
上述输出结果与配置相同,证明配置成功。如果输出是一个空行或者其他的值,则表明配置不成功或者尚未生效。重新检查前面的步骤。PATH、RSH_CVS类似。
CVS软件包是服务器/客户端一体化的,所以客户端的安装见CVS服务器在Linux/Unix上的安装。
CVS客户端设置见CVS环境设置。
当完成以上配置后,CVS仓库就已经可以从网络上任何可以访问到这个安装CVS的计算机的终端上访问了。这种共享网络仓库正是开放源代码项目的模式。在一个公司内部使用CVS时,应将CVS仓库放在公司的防火墙内,员工可以从公司内部任何一台联网的计算机上访问CVS仓库。如果需要从客户方访问CVS仓库,则安全的做法是建立VPN网络,保证远程访问在有授权、数据加密的方式下进行。
注:1、在ssh授权方式下,在客户端执行CVS命令的时候,需要输入远程用户的密码。
2、注意服务器和客户端的时钟同步,否则可能出现提交被忽略的情况。
当你开始使用CVS时,你可能已经有几个项目可以使用CVS进行管理了。在这种情况下,最容易的方法就是使用:import命令。我们要将需要管理的文件加入仓库,并命名。CVS命令import的使用如下:
$cvs [–d仓库绝对路径] import -m "write some comments here" project_name vendor_tag release_tag
-m:指定填写注释,如果你没有使用-m参数记录一个日志信息,CVS将调用一个编辑器(通常是vi)并且提示输入信息。
project:仓库名
vender_tag:开发商标记
release_tag:版本发布标记,不能带$ ,.;:@这些特殊符号。
执行后会将当前目录下所有源文件及目录导入到$CVSROOT/project_name目录下。
-I NAME——给出导入时忽略的文件的名称。你可以指定多个文件。该选项支持通配符。*.foo表示将忽略所有以.foo结尾的文件。
默认情况下忽略以下文件和目录:
.
..
.#*
#*
,*
_$*
*~
*$
*.a
*.bak
*.BAK
*.elc
*.exe
*.ln
*.o
*.obj
*.olb
*.old
*.orig
*.rej
*.so
*.Z
.del-*
.make.state
.nse_depinfo
core
CVS
CVS.adm
cvslog.*
RCS
RCSLOG
SCCS
tags
TAGS
可以使用-I
!
选项禁止忽略这些文件和目录。例如
:
$ cvs import -I ! -m "importing the universe" proj VENDOR VENDOR_0
上面的命令将导入当前目录树下全部文件,包括那些本来会被忽略的文件。
-I !选项清空被忽略的文件类型的记录。所以在-I !之前的-I选项不会起作用,但是-I !之后的-I选项仍然有效。因此:
$ cvs import -I ! -I README.txt -m "some msg" theirproj THEM THEM_0
不同于下面的命令:
$ cvs import -I README.txt -I ! -m "some msg" theirproj THEM THEM_0
前者忽略README.txt文件,后者则将导入这个文件。
注:import 是相对当前目录及其所有子目录的。CVS命令针对文本类型的文件,对于非文本类型文件的操作见CVS与二进制文件。第一次导出以后,就不是通过cvs checkout来同步文件了,而是要进入刚才cvs checkout project_name导出的project_name目录下进行具体文件的版本同步(添加,修改,删除)操作。
根据CVS的工作原理,代码一旦装入(或者“提交”)到CVS仓库,我们就不应该再使用原来的源代码。如果要改动代码,我们应该先把代码从知识库“签出”(Check Out)到独立的工作目录,然后进行必要的修改,完成后再把经过修改的代码重新提交到仓库。例如,假设你目前工作的项目名为httpc:
$ cd
$ cvs checkout httpc
cvs checkout: Updating httpc
U httpc/.cvsignore
U httpc/Makefile
U httpc/httpc.c
U httpc/poll-server
命令cvs checkout httpc意为:“从由CVSROOT环境变量指定的仓库中检出称为httpc的源树。”CVS将树放在名为“httpc”的子目录中:
$ cd httpc
$ ls -l
total 8
drwxr-xr-x 2 jimb 512 Oct 31 11:04 CVS
-rw-r--r-- 1 jimb 89 Oct 31 10:42 Makefile
-rw-r--r-- 1 jimb 4432 Oct 31 10:45 httpc.c
-rwxr-xr-x 1 jimb 460 Oct 30 10:21 poll-server
这些文件的大多数是你的httpc源的工作拷贝。
我们可以将当前源代码树的全部或者部分“签出”到工作目录,然后再修改工作目录下的源代码。工作目录下会有一个CVS/目录,但我们无需理会它。
cvs diff 比较并显示两个版本的不同之处。如果不使用任何选项,cvs diff命令比较仓库的库版本(base revision)和工作拷贝中的(常常是未提交过的)文件。库版本是指仓库中从本地工作拷贝提交的最后一个版本。需要注意的仓库中可能有更加新的版本。有可能其他用户已经提交了新的文件,而本地工作拷贝没有更新。
-B:忽略空行。比较时忽略只包括空格的行。
-b:忽略重复的空格。该选项把空格看作是相同的,并忽略行末的空格。这个选项使diff命令执行之前把输入中连续的空格合并成一个,并删除每一行最后的空格 (参见-w选项)。
-c:以包含上下文的diff 格式输出。在默认情况下,在比较结果里每一处有区别的地方包含3行上下文(因为补丁程序至少需要两行上下文)。
-C NUM:和-c选项作用相同,包含的上下文数由参数NUM确定。
-I:进行模糊比较,不区分大写和小写字母。
-u:以统一的diff格式输出比较结果。
-w:忽略所有的空白字符,甚至在一个文件中包含空白字符而在另一个文件不包含空白字符时也将其忽略。-w可以看作-b选项的增强。
你忘记了其中的httpc.c是否被更改,想查询一下这个文件的更改情况。
$ cd httpc
$ cvs diff httpc.c
这个命令diff可以检查httpc.c的检出版本和工作目录中版本的差异。
$ cvs diff -u -r0.5 AUTHORS
Index: AUTHORS
===================================================================
RCS file: /home/hahalee/CVS/hftpd/AUTHORS,v
retrieving revision 0.5
retrieving revision 0.6
diff -u -r0.5 -r0.6
--- AUTHORS 2000/04/07 10:46:02 0.5
+++ AUTHORS 2000/04/07 14:05:57 0.6
@@ -1,3 +1,4 @@
+ah! let me in!
So then, who can't spell
Develloppopotamus?
Quite a lot of us.
还有一个rdiff,用来生成两个不同的release 之间的patch。
rdiff类似于diff 命令, 区别仅在于rdiff直接在仓库中操作,因此不需要工作拷贝。此命令的目的是获得项目中二个的不同版本之间的差异,并且其格式适合于补丁(patch)程序的输入(这样你可以向那些需要升级的用户发布补丁文件)。
注:补丁(patch)程序的操作机理已超出本文的范围,然而需要注意的是,如果补丁文件中包含了子目录下文件的diff结果,你需要使用带-p 选项的patch命令以便正确地应用这些差异。
$ cvs -Q rdiff -s myproj
File myproj/Random.txt is new; current revision 1.4
File myproj/README.txt changed from revision 2.1 to 2.20
File myproj/baar is new; current revision 2.3
-t——显示每个文件最高的两个版本的差异。这是用来确定项目最新修改的一个便利方法。这一选项与-D 及 –r选项不兼容。
一旦CVS创建了工作目录树,你可以通过平常的方式来编辑、编译和测试该目录中所包含的文件——它们就只是文件而已。
因为每个开发者使用他们自己的工作目录,你对你的工作目录所做的改动并不会自动地对你的开发组中的其他开发者变得可见。不到你准备就绪,CVS不会公布你的改动。当你完成了对你的改动的测试时,你必须将它们提交(commit)给仓库,以使它们能为组的其他成员所用,见向CVS提交你的修改。
但是,如果另一个开发者已经改动了你所改动的同一文件、或同一行,该怎么办呢?谁的改动应该成功?一般而言,要自动回答此问题是不可能的;CVS无疑没有能力来作出那样的判断。 因而,在你提交你的改动之前,CVS要求你的源与其他组成员提交的任何改动保持同步。
“cvs update”通过打印单个字符、空格和文件名告诉您它都做些什么,看到些什么;如下例所示:
# cvs update -dP
? distfiles
? packages
? profiles
"cvs update" 用 "?" 字符指示在本地副本中找到的这些特殊文件。它们不是资源库的正式部分,也不是计划要添加的部分。这里有一个CVS使用的所有其它单字符信息性消息的列表:
U [path]
在本地资源库中创建新文件,或者更新了您没有动过的文件时使用。
A [path]
该文件是计划要添加的,使用 "cvs commit" 时,它被正式添加到资源库。
R [path]
象 "A" 一样,"R" 让您知道该文件计划要除去。输入 "cvs commit" 后,该文件就会从资源库中除去。
M [path]
这意味着您已经修改过该文件了;而且,有可能资源库中新的更改已成功地合并到该文件。
C [path]
表明该文件存在冲突,需要在使用 "cvs commit" 提交更改前手工修改它。
$ cvs update
cvs update: Updating .
U Makefile
RCS file: /u/src/master/httpc/httpc.c,v
retrieving revision 1.6
retrieving revision 1.7
Merging differences between 1.6 and 1.7 into httpc.c
M httpc.c
让我们一行一行地来查看:
U Makefile
“U file”形式的行意味着该file已被明确地更新(Updated);另外有人对此文件做了改动,而CVS已将被修改的文件拷贝进你的主目录中。
RCS file
retrieving revision 1.6
retrieving revision 1.7
Merging differences between 1.6 and 1.7 into httpc.c
这些消息指示另外有人改动了“httpc.c”;CVS将他们的改动与你的合并在了一起,并且没有发现任何文本上的冲突(对于冲突的处理见处理冲突(conflict))。数字“1.6”和“1.7”是修订版号(revision number),用于标识文件的历史中的特定点。注意CVS只是将改动合并进你的工作拷贝中;仓库和其他开发者的工作目录没有受到打扰。要由你来测试合并的文本,并确保它是有效的。
M httpc.c
“M file”形式的行意味着该file已被你修改(Modified),并含有对其他开发者还不可见的改动。这些是你需要提交的改动。这样,“httpc.c”现在同时含有你的修改和其他用户的修改。
因为CVS已将其他人的改动合并进你的源中,最好确定程序还能工作。在你已使你的源跟上了组的其余成员那里的最新情况、并对它们做了测试,你可以提交你的改动给仓库、并使它们对组的其余成员成为可见的。唯一被你修改过的文件是“httpc.c”,但运行cvs update来从CVS那里获取被修改过的文件的列表总是可靠的:
$ cvs update
cvs update: Updating .
M httpc.c
如所预期的,CVS所提到的唯一文件是“httpc.c”;它说该文件含有你还未提交的改动。你可以像这样来提交它们:
$ cvs commit httpc.c
在这时,CVS会启动编辑器,并提示你输入日志消息来描述改动。当你退出编辑器时,CVS将提交你的改动:
Checking in httpc.c;
/u/src/master/httpc/httpc.c,v <-- httpc.c
new revision: 1.8; previous revision: 1.7
现在你已经提交了你的改动,它们对组的其他成员是可见的。当另外的开发者运行cvs update时,CVS将把你对“httpc.c”的改动合并进他们的工作目录中。
此外,使用“cvs status”命令,我们可以检查是否已经得到指定文件的最新版本,或者获得指定文件的详细信息,见cvs_status。
现在你可能很好奇,其他开发者都对“httpc.c”做了什么改动。为了查看特定文件的日志条目,你可以使用cvs log命令:
$ cvs log httpc.c
RCS file: /u/src/master/httpc/httpc.c,v
Working file: httpc.c
head: 1.8
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 8; selected revisions: 8
description:
The one and only source file for the trivial HTTP client
----------------------------
revision 1.8
date: 1996/10/31 20:11:14; author: jimb; state: Exp; lines: +1 -1
(tcp_connection): Cast address structure when calling connect.
----------------------------
revision 1.7
date: 1996/10/31 19:18:45; author: fred; state: Exp; lines: +6 -2
(match_header): Make this test case-insensitive.
----------------------------
revision 1.6
date: 1996/10/31 19:15:23; author: jimb; state: Exp; lines: +2 -6
...
你可以忽略这里的大多数文本;要仔细查看的部分是第一行连字号后面的日志条目。假定较近的修改通常也更为有趣,所以这些条目以反向的年月日顺序出现。每个条目描述对文件的一次改动,并可被解析如下:
revision 1.8
文件的每个版本都有唯一的修订版号。它看起来像是“1.1”、“1.2”、“
date: 1996/10/31 20:11:14; author: jimb; ...
这一行给出改动日期,以及提交它的人的用户名;行的余下部分不怎么有趣。
(tcp_connection): Cast ...
这是(相当明显)对改动进行描述的日志条目。如果你实际上想要查看正在讨论的改动,你可以使用cvs diff命令。例如,如果你想要查看Fred作为修订版1.7提交的改动,你可以使用下面的命令:
$ cvs diff -c -r 1.6 -r 1.7 httpc.c
在我们查看该命令的输出之前,让我们先看一下各个部分的含义:
-c:
该选项要求cvs diff为它的输出使用让人更能理解的格式。
-r 1.6 -r 1.7:
这告诉CVS显示要将httpc.c的修订版1.6变为修订版1.7所需的改动。(通过向后指定修订版:-r 1.7 -r 1.6,你甚至可以请求改动被反向显示,就好像它们正在被撤消(undo)。这听起来奇怪,但有时候是有用的。)
httpc.c:
这是要检查的文件的名字。如果你没有给出特定的文件,CVS将为整个目录生成报告。下面是该命令的输出:
Index: httpc.c
===================================================================
RCS file: /u/src/master/httpc/httpc.c,v
retrieving revision 1.6
retrieving revision 1.7
diff -c -r1.6 -r1.7
*** httpc.c 1996/10/31 19:15:23 1.6
--- httpc.c 1996/10/31 19:18:45 1.7
***************
*** 62,68 ****
}
! /* Return non-zero iff HEADER is a prefix of TEXT. HEADER should be
null-terminated; LEN is the length of TEXT. */
static int
match_header (char *header, char *text, size_t len)
--- 62,69 ----
}
! /* Return non-zero iff HEADER is a prefix of TEXT, ignoring
! differences in case. HEADER should be lower-case, and
null-terminated; LEN is the length of TEXT. */
static int
match_header (char *header, char *text, size_t len)
***************
*** 76,81 ****
--- 77,84 ----
for (i = 0; i < header_len; i++)
{
char t = text[i];
+ if ('A' <= t && t <= 'Z')
+ t += 'a' - 'A';
if (header[i] != t)
return 0;
}
值得注意的部分是从第一处由***和---起头的两行开始的;它们描述较旧和较新的被比较的文件。余下部分由两个大块(hunk)组成,每个大块都由一行星号开始。这里是第一个大块:
***************
*** 62,68 ****
}
! /* Return non-zero iff HEADER is a prefix of TEXT. HEADER should be
null-terminated; LEN is the length of TEXT. */
static int
match_header (char *header, char *text, size_t len)
--- 62,69 ----
}
! /* Return non-zero iff HEADER is a prefix of TEXT, ignoring
! differences in case. HEADER should be lower-case, and
null-terminated; LEN is the length of TEXT. */
static int
match_header (char *header, char *text, size_t len)
来自较旧版本的文本出现在*** 62,68 ***行后面;来自较新版本的文本出现在--- 62,69 ---行后面。每对数字指示所显示行的范围。CVS在改动的周围提供上下文,并将实际被影响的行标上“!”字符。因而,我们可以看到上半边的单行被下半边的双行取代了。
下面是第二个大块:
***************
*** 76,81 ****
--- 77,84 ----
for (i = 0; i < header_len; i++)
{
char t = text[i];
+ if ('A' <= t && t <= 'Z')
+ t += 'a' - 'A';
if (header[i] != t)
return 0;
}
这个大块描述插入的两行,它们被标上了“+”字符。在这种情况下CVS省略了旧文本,因为它是多余的。CVS使用类似的大块格式来描述删除。
该命令的另一个用途就是将工作拷贝与仓库进行对照。
$cvs diff test.c
Index: test.c
=============================================
RCS file: /src/master/test/test.c,v
retrieving revision 1.2
diff -r1.2 test.c
> //comment added by cvs tester
注意,这里的输出是UNIX diff命令的输出格式。其中
< void thread_func();
---
> void *thread_func(void *);
> case 'd':
> if(argc == 0) {
> usage();
> }
> dbname = *argv;
> argc--;
> argv++;
> break;
< debug = 1;
---
> debug_flag = 1;
> if(!debug_flag) daemon(1,0);
输出中以“<”开头的行是原有版本内容,而以“>”开头的行是新版本的内容。“---”表示两个版本之间的界限。“
在修改代码之后,我们需要将自己的工作成果保存起来(不是在本地保存,而是保存到CVS的仓库之中),即提交源代码。在保存之后,我们就再也不用担心找不回我们的各个不同版本的工作成果了。
执行如下命令完成提交源代码的工作:
$ cvs commit –m "add string variable and header string.h" test.c
Checking in test.c;
/src/master/test/test.c,v <-- test.c
new revision: 1.2; previous revision: 1.1
done
这里,-m后面是CVS注释。CVS强制要求用户在任何一次提交(commit)、导入(import)等操作的时候,写一条注释信息。养成每次提交、导入操作的时候给出非常清晰的注释的习惯,对于跟踪软件的变化是非常重要的,很难有人记得3个月甚至1年之前开发的代码各次提交之间做了什么修改,如果没有清晰的注释,以后维护代码将变得非常艰难,相关信息见编写良好的日志条目。
CVS支持我们使用中文作为注释信息。如果我们在Windows客户端上(或者支持中文输入的UNIX/LINUX客户端上)输入中文注释,以后的检查、维护就会变得更加容易。如果你不在命令行附加注释,那么CVS将启动默认的编辑器(注:通过设置CVSEDITOR环境变量可以改变编辑器设置),请用户输入注释,只有用户保存注释并退出这个编辑器之后,提交操作才能成功。下图是一个输出窗口的例子:
等待用户输入注释的CVS窗口
如果提交时被提示“up-to-date check failed”(与最新版本一致性检查失败),表明文件尚未更新到最新版本,如果CVS源码库中的文件版本和工作拷贝中的版本不一致的情况下会出现这种警告。CVS只允许基于源码库中最新版本的修改提交。如何更新源代码见合并你的改动。
注:CVS的很多动作都是通过cvs commit进行最后确认并修改的,最好每次只修改一个文件。在确认的前,还需要用户填写修改注释,以帮助其他开发人员了解修改的原因。
注:CVS本身不是一个“Modification Tracker”,所以要想实现完善的变化跟踪管理,需要把CVS和一个“Modification Tracker”或者“Bug Tracker”结合起来使用。
在向模块或者目录添加文件之前,必须首先取得某个版本的工作拷贝,然后再工作拷贝中进行操作。CVS像对待其他改动一样对待文件创建和删除,它在文件的历史中记录这样的事件。也就是说,CVS记录目录以及它们所包含的文件的历史。
要把文件增加到项目中,你必须先创建该文件,然后使用cvs add命令来为它做上增加标记。于是,下一次对cvs commit的使用会把该文件增加到仓库中。例如,这里演示你可以怎样将README文件增加到httpc项目中:
$ ls
CVS Makefile httpc.c poll-server
$ vi README
... enter a description of httpc ...
$ ls
CVS Makefile README httpc.c poll-server
$ cvs update
cvs update: Updating .
? README ---CVS doesn't know about this file yet.
$ cvs add README
cvs add: scheduling file `README' for addition
cvs add: use 'cvs commit' to add this file permanently
$ cvs update --- Now what does CVS think?
cvs update: Updating .
A README --- The file is marked for addition.
$ cvs commit README
CVS prompts you for a log entry ...
RCS file: /u/jimb/cvs-class/rep/httpc/README,v
done
Checking in README;
/u/src/master/httpc/README,v <-- README
initial revision: 1.1
done
下面的命令将目录newdir添加到CVS仓库中:
$cvs add newdir
注意:添加目录只需要一步而不是两步。CVS无法知道目录添加的时间。
CVS以类似的方式对待被删除的文件。你必须在工作拷贝中先删除该文件,然后使用cvs remove命令为它做上删除标记。运行cvs commit会把该文件从仓库中删除。值得注意的是:即使要删除文件,也应该把已经做过的修改保存下来,就是说,要把删除之前的版本提交给CVS仓库作为一个新版本保存起来。
提交通过cvs remove标记的文件不会销毁该文件的历史。它只是增加一个新的修订版,标记为“不存在”。仓库还有该文件先前内容的记录,并可在需要时恢复它们——例如,通过cvs diff或cvs log。
如果用户删除(remove)文件之后,在提交以前又改变了主意,可以使用add命令来取消删除(remove)操作。
$ ls
CVS ja.h oj.c
$ rm oj.c
$ cvs remove oj.c
cvs remove: scheduling oj.c for removal
cvs remove: use 'cvs commit' to remove this file permanently
(cvs remove –f将上面两步和为一步)
$ cvs add oj.c
U oj.c
cvs add: oj.c, version
如果你在运行删除(remove)命令前意识到了错误,可以使用更新(update)命令来恢复文件:
$ rm oj.c
$ cvs update oj.c
cvs update: warning: oj.c was lost
U oj.c
由于CVS并不对目录进行版本管理和控制,所以删除目录不像删除目录那样,没有一个直接的删除目录的命令可以完成这个任务。
删除目录的方法就是删除目录下的所有文件。用户不能直接删除目录本身,目前CVS中也没有方法可以办到这一点。可以在cvs update或cvs checkout命令中使用-P选项来让CVS删除工作目录中的空目录。
$cd newdir
$cvs remove –f new.c
$cd ..
$cvs update –P
这时,newdir目录就在当前的工作目录中消失了,注意remove不要删除CVS的系统目录。
在这里有一个相关的现象需要注意:就是执行了cvs update命令时,CVS并不会自动取出在工作拷贝创建后添加到仓库中的目录,解决办法是在执行cvs update 的时候用-d参数。
$cvs update –d
注:删除,移动目录最方便的方法是让管理员直接移动,删除CVSROOT里相应目录对目录进行了修改后,要求其开发人员重新导出项目cvs checkout project_name 或者用cvs update -dP同步。
CVS对文件和目录的更名或者移动缺乏合适的支持。有若干策略可用于重命名文件;最简单的是简单地重命名在你的工作目录中的文件,并对旧名字运行cvs remove,对新名字运行cvs add。该方法的缺点是旧文件内容的日志条目不会结转给新文件。
$ mv old new
$ cvs remove old
$ cvs commit –m “removed for renameing” old
$ cvs add new
$ cvs commit -m "Renamed old to new" old new
这样old就改为new,现在的问题是查看日志文件的时候,必须记住两个不同的文件名,可以养成将旧文件名包含在新文件第一版的注释中,然后再遇到“renamed version”就查看旧文件的日志。
注:更为直接的办法是在服务器端直接重命名“,v”结尾的相应文件,注意你应确信这样做无误时使用该命令。
对于目录的更名,和文件相类似:
$mkdir ndir
$mv newdir/* ndir
$cvs –Q remove newdir/*
$cvs –Q commit
$cvs –Q add ndir
$cvs –Q commit ndir
如果我们可以使用cvs diff来取得改动的实际文本,为什么还要费事地编写日志条目呢?显然地,日志条目可以比补丁更短,并允许读者获得对改动的全面了解,而无需深入它的细节。
但是,好的日志条目应描述开发者做出改动的原因。例如,为上面所示的修订版1.7编写的糟糕的日志条目可能会说:“将t转换为小写。”这是准确的,但却完全没有用;cvs diff提供同样的信息,更为清楚。更好的日志条目是:“使该测试对大小写不敏感。”因为这样其他人就会对代码有大体的了解,从而弄清楚其目的。
如上面所提到的,cvs update命令将其他开发者所做的改动并入你的工作目录中。如果你和其他开发者修改了同一文件,CVS将他们的改动和你的改动合并在一起。当改动应用于文件的不同区域时,很容易想像这是怎样工作的。但当你和另外的开发者修改了同一行时,又会发生什么呢?CVS称这种情况为冲突,并将它留给你来消除。例如,假设你刚刚给主机名查找代码增加了某种错误检查。在提交你的改动前,你必须运行cvs update,以使你的源保持同步:
$ cvs update
cvs update: Updating .
RCS file: /u/src/master/httpc/httpc.c,v
retrieving revision 1.8
retrieving revision 1.9
Merging differences between 1.8 and 1.9 into httpc.c
rcsmerge: warning: conflicts during merge
cvs update: conflicts found in httpc.c
C httpc.c
在此例中,另外的开发者已经改动了你所改动的文件的同一区域,因而CVS会抱怨有冲突。不像它通常所做的那样打印“M httpc.c”,它打印“C httpc.c”,以指示在该文件中已经发生了一处冲突。为消除冲突,在你的编辑器中打开该文件。CVS这样标记冲突的文本:
/* Look up the IP address of the host. */
host_info = gethostbyname (hostname);
<<<<<<< httpc.c
if (! host_info)
{
fprintf (stderr, "%s: host not found: %s\n", progname, hostname);
exit (1);
}
=======
if (! host_info)
{
printf ("httpc: no host");
exit (1);
}
>>>>>>> 1.9
sock = socket (PF_INET, SOCK_STREAM, 0);
重要的是要理解CVS是怎样判断冲突的,CVS只是简单地把它的源代码作为文本文件树来对待。CVS对冲突的理解是严格的文本意义上的。
解决冲突的办法是编辑文件,将发生冲突的部分进行改写。对于逻辑上的问题,CVS是根本不会发现的。所以对版本的处理应该与软件开发中的需要结合起来,同时需要小组成员之间的沟通。
对源文件作必要修改后, 可以用cvs release -d 删除本地工作拷贝,并通知其他开发者这个模块不再使用。release命令通常在结尾处告诉你在工作目录有多少源件的拷贝已经被更改了,然后在删除或在历史档案中进行更改之前向你确认。你认为安全的情况下,按n RET就可以了。例:
$cvs release -d print
-d:删除
print:目录
由于CVS命令需要经常键入,所以各个命令都有缩写形式。最常用的CVS命令的缩写形式与原型对应如下:
命令原型
|
命令缩写形式
|
cvs checkout
|
cvs co
|
cvs commit
|
cvs ci
|
cvs update
|
cvs up
|
cvs status
|
cvs st
|
版本在本文档的第一个含义就是“修订(revisions)”,它的第二个含义就是“发布(releases)”。
对于大多数cvs用户来说,在提交(cvs commit)时不需要考虑修订号,他们只要知道CVS已经自动地加上了类似1.1、1.2之类的修订号就可以了。如果用户想跟踪许多文件的一系列版本号,例如一个特别的发布版本,他使用了一个特殊的标签(tag)作为修订号,这个符号标记的功能和每个文件的数值修订号的功能是相同的。
每个文件的版本都有一个唯一的"revision number",修订号的形式一般是这样的:1.1, 1.2, 1.3.2.2甚至是1.3.2.2.4.5。一个修订号总有偶数个用句号分割的十进制数。一个文件的缺省修订号是1.1。每个新的修订号的最右边的数会比它的上一个修订号的最右边的数大1。如果修订号包含了不止一个句点,如1.3.2.2.这种修订号表示修订是在分支上进行的。一个文件可以有几个版本(version),就像上面描述的。同样,一个软件产品也可以有几个版本。一般一个软件产品都有一个版本号,比如4.1.1。
有时候我们需要根据一个特定的事件或者某种条件来获取整个项目的源代码。如上一次公开发布的版本、项目中版本的某个稳定点、或者添加或者删除了某些重要功能的版本。但是,同一个项目中各个不同文件的版本号相差很大,而且CVS版本编号机制在很大程度上是自动的,我们很难对其进行控制,所以CVS提供了一个“标签系统”,它能够帮助我们获取特定阶段代码树的快照(即,文件当时的状态)、或者为快照加上“标签”、通过引用标签返回指定的快照。标签是赋于一个文件或一组文件的符号。在源代码的生命周期里,组成一组模块的文件被赋于相同的标签。版本标签提供了一条链子,将我们想要的不同文件的不同版本穿接起来。
在状态命令status中你可以使用-v选项(见status命令)来查看一个文件的代表版本号的所有标签。很少对单个文件添加标签。一种更常见的用法是在产品开发周期中的各个里程碑任务完成后对一个模块的所有文件添加标签,比如各种基线版本的标记,其中包括各种测试版本、发布版本等。
在工作目录里执行cvs tag:
$ cvs tag rel-0-4 backend.c
T backend.c
这将基于当前的工作目录拷贝中的文件backend.c分离出一个分支,并分配rel-0-4名字给分支。标签名必须以字母开始,中间可以包含字母、数字、下划线、连字符。在整个软件开发过程中,标签的命名要规范。
$ cvs status -v backend.c
===================================================================
File: backend.c Status: Up-to-date
Version: 1.4 Tue Dec 1 14:39:01 1992
RCS Version: 1.4 /u/cvsroot/yoyodyne/tc/backend.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
Existing Tags:
rel-0-4 (revision: 1.4)
通常情况下很少对单个文件指定一个版本标签,通常对一个基线版本指定标签,也就是说,对软件配置中的所有代码、文档、数据的某项版本需要统一贴上一个版本标签。基线版本是一个软件项目中所有的源代码、文档、数据文件的某种版本组合,每个文件只取唯一一个版本或者一个版本都不取。基线版本的设定和软件过程定义有密切的关系。
标签不是贴在我们的工作拷贝中,而是贴在CVS的仓库中最新版本上。这就导致一个问题,有可能我们还没有提交就贴上了标签,没有把标签贴在我们希望贴的版本上,防止这种错误的方法是:
$cvs tag –c BASELINE_1
cvs tag :test.c is locally modified
cvs [tag aborted]:correctg the above errors first!
发现这个错误,可以参考移动标签。
使用cvs rtag –D date tag modules可以根据时间来贴标签。
$cvs rtag –D 20011010 NIGHTLY_20011010 test
将test模块的所有文件在2001年10月10日0时0分前的最后一个版本NIGHTLY_20011010标签。
也可按照版本号或已有标签贴标签,格式为:cvs rtag –r revision/tag tag modules…
$cvs rtag –r NIGHTLY_20011010 REL_2_1_0 test
如果一个标签为NIGHTLY_20011010的版本已经通过所有审议和测试,可以作为发行版本了,就可以使用以上命令将该标签版本贴上发行版的标签REL_2_1_0。选项-f用来强制将上述两种情况下的标签在某个文件找不到任何匹配的时候将最新版本贴上标签。这个选项可以防止给整个软件配置贴上标签的时候遗漏某些文件。
在checkout命令中使用-r选项可以检出一个模块某个版本的所有文件。下面的命令可以很容易地检出模块tc 1.0.4版的所有源文件。
$ cvs checkout -r rel-0-4 tc
如果我们记得为最初的代码树加上的标签是“base”,现在想要重新从最初的源代码树开始,我们可以用如下命令签出“base”版本的代码:
$ cvs checkout -r base community
或者,如果我们当前编写的代码属于3.8版本,那么可以用下面的命令为所有文件加上此时的标签:
$ cvs tag release-38
cvs tag: Tagging .
T functions.php3
T index.php3
T post.php3
T read.php3
T search.php3
以后,当我们想要获得“release-38”的文件时,只需执行如下命令:
$ cvs checkout -r release-38 community
cvs checkout: Updating community
U community/functions.php3
U community/index.php3
U community/post.php3
U community/read.php3
U community/search.php3
U community/sql.php3
另外,我们也可以使用“cvs update”命令将现有版本跳转到该版本号对应的另一个版本上:
$ cvs update -r release-38 community
也可根据时间获得特定版本:
$cvs update –D “2001-05-08”
-D选项让CVS从仓库中找到2001年5月8日0时0分时的每个文件的最新版本,将这些版本从仓库中取出。
有些版本编号包含了其他属性数据,这种附着性标签主要用在:
1)版本分支:版本分支标签在创建分支的时候与版本分支关联在一起,并且在解除之前一直有效。
2)保持某几个文件不改动:有时候可能需要保持某几个文件目前版本一段时间,可以用cvs update –r,命令为文件建立附着性标签,以后在这个目录下执行cvs update命令,该文件都不会被修改。类似的,如果开发者希望检出某个日期时间点上的版本,而不希望后来执行cvs update的时候,这几个文件被更新,就可以用cvs checkout –D 或者cvs update –D 来达到将该日期与该版本关联的目的。
对于这种老版本的修改和提交形成新版本可以使用下列方法:
1)逐个处理,处理方法见版本的回退之方法一。
2)导出代码,一次处理所有文件。
首先解除附着性标签使用$cvs update –A 取消所有附着性标签、版本号、日期等;然后在一个临时目录下导出代码:$cvs export –D “2001-05-08” test;最后将所有代码拷贝到原来目录下覆盖所有原来的文件$cp –r 。
cvs export的使用见发布源码(export)。
3)用“-j”选项的CVS更新命令,见版本回退之方法二。
通常不会去修改标签。他们的存在是为了纪录源码库的更新历史。如果删除或修改本身就是违反了初衷。如果使用的是临时标签,或者误打了标签,你也可以进行删除,移动,更名操作。要删除标签,在cvs tag或cvs rtag后面加上-d选项。
$cvs tag –d BASELINE_1
对标签重命名
$cvs tag –r BASELINE_1 BASELINE_2
$cvs tag –d BASELINE_1
将非分支(non-branch)标签rel-0-4从模块tc上删除:
$cvs rtag -d rel-0-4 tc
移动一个标签,是将这个名字赋给另外的版本。例如,stable标签现在是用在文件backend.c的版本1.4上,我们想将它转到版本1.6上面。在cvs tag or cvs rtag命令后面加上-F选项。我们可以这样做:
$cvs tag -r 1.6 -F stable backend.c
重命名一个标签,是说给一个版本上的已有标签名改名。例如,有人将标签名写错了并要更正它(希望其他人还没有使用这个错误的标签)。要改标签名,先用带有-r选项的cvs rtag命令,然后删除旧标签名(注意:该方法对分支标签无效)。改名后标签位置与原标签相同。例如:
$cvs rtag -r old-name-0-4 rel-0-4 tc
$cvs rtag -d old-name-0-4 tc
警告:删除、移动和重命名标签操作具有危险性,他们会永久性的抹去历史纪录信息并且一旦出错无法恢复。
版本分支是用户在一个主要版本序列上导出的一个辅助版本序列,分支版本与主版本序列并存,可以进行并行开发,如果需要,分支版本可以合并到主版本上。版本分支是CVS最重要的特性之一,也是最容易被错误使用的特性之一。将一些不是很有把握而且会破坏整个项目的功能独立到一个分支上进行开发能发挥巨大的作用,但是如果管理不善将导致重大的混乱。
CVS分支可以运用于诸如多小组(人)并行开发、清除发行版的臭虫、多平台(客户)并行开发、Nightly Build中。
CVS分支主要是用来修改工作拷贝的历史记录,禁止不加分支就修改以前版本的信息。建议每发布一次版本就建立一个分支。在使用分支时严格遵循一下原则:
u 尽量减少同时活跃的分支数,以减少合并时出现冲突的可能性,并尽量频繁地进行版本合并;
u 尽量减少分支的层次;
u 使用统一和有意义的标签来命名分支;
u 指定专人负责版本分支的创建和管理。
创建版本分支有两种方法:检出版本后创建和直接创建。版本刚创建时,分支版本的所有代码与这个分支基于的版本(成为分支基点)完全相同。分支创建后,分支标签与分支树的头部版本关联,而不是与分支基点关联。如果今后需要访问分支基点,需要在创建分支的时候,同时给主干代码树贴上一个便于标识的标签。
可以使用tag -b命令来给一个文件的某个版本分配一个符号化的名称,对一些文件进行修改而不会影响主干(当commit时)。CVS允许你修改代码到不同的开发线上,这就是分支(branch)。当你改变一个分支中的文件时,这些更改不会出现在主开发主干(main trunk)和其它分支中。
从工作拷贝中检出一个版本的代码,然后创建分支,用tag –b,例如:
$cvs checkout –r BASELINE_1 test
$cvs tag -b BASELINE_1_PATH test
这将基于当前的工作目录拷贝分离出一个分支,并分配BASELINE_1_PATH名字给分支。
使用rtag命令可以直接在仓库中建立一个分支:
$cvs rtag -b -r BASELINE_1 BASELINE_1_PATH_2 test
-r BASELINE_1说明这个分支是基于有BASELINE_1这个标签的文件,对需要从老的版本分出一个分支很有用(例如:给以前一个认为稳定的发行版改bug)。
你可以通过两种方式进入分支:重新检出一份或是从现有的工作拷贝切换过去。重新检出使用checkout命令并带上-r标识,后面是这个分支的标笺(tag)名。如:
$mkdir work
$cd work
$cvs checkout -r rel-1-0-patches tc
或者如果你已有了一个拷贝,你可以使用update -r命令转到这个分支:
$cvs update -r rel-1-0-patches tc
一但你的工作拷贝已经转向一个特定的分支。它将一直保持在这个分支内,除非你又做了其它的操作。
$ cvs status -v backend.c
===================================================================
File: backend.c Status: Up-to-date
Version: 1.4 Tue Dec 1 14:39:01 1992
RCS Version: 1.4 /u/cvsroot/yoyodyne/tc/backend.c,v
Sticky Tag: (BASELINE_1_PATH(branch:1.1.2)
Sticky Date: (none)
Sticky Options: (none)
版本的合并就是将不同版本的代码进行合并,使得合并后的版本包括不同版本上所进行的所有工作。使用cvs update -j命令,将这些变更合并到工作目录。然后你可以提交这个版本。
$cvs update -j rel-1-0 print.c
每次将分支版本中的修改合并到主干版本的时候,都是将分支基部到分支最新版本之间的全部修改都合并到主干版本上。在合并时可能会发生冲突,如果这种情况发生,你可以在提交新版本之前解决它,对冲突的处理见处理冲突(conflict)。使用两个-j revision标志,update(和checkout)命令能合并两个任意不同的版本的差异到你的工作目录。
$ cvs update -j 1.5 -j 1.3 backend.c
进行多次反复合并的一个技巧是在每次合并后,将分支版本树再贴上标签。这样每次合并时的分支版本都有标签可以标识,这样就能只合并未被合并的内容。
注意:你必须表达清楚你希望只合并未被合并的内容的意思。这样需要使用两个“-j“参数。CVS合并从第一个“-j”的版本到第二个“-j”版本的变化。否则cvs可能试图合并你已经合并过的东西,这可能写导致一些不希望发生的东西。
1、在工作目录中检出最新的版本以及上次的发行版本
$cvs checkout test
$cvs checkout –d test_old –r rel_2001_10_10 test
将上次的发现版本保存在test_old目录下。
2、创建bugfix分支
$cd test_old
$cvs tag –b rel_2001_10_10-bugfix
3、转到分支版本上
$cvs update –r rel_2001_10_10-bugfix
4、修改分支版本中的文件,如test.c
5、提交bugfix分支版本
$cvs commit –m”fixed bug no.12345” test.c
6、回到主干版本所在目录,将分支版本合并到最新的主版本上
$cd ../test
$cvs update -j rel_2001_10_10-bugfix test.c
7、最后,将合并后的结果提交,形成合并后的新版本
$cvs commit –m”merged from branch rel_2001_10_10-bugfix” test.c
对于开发者而言,如果需要获取软件的源代码,使用CVS的检出机制已经足够了。但是,对于普通用户而言,他们没有必要学会使用CVS,然后检出所有的源代码,他们需要更加简单的办法直接获得打包的软件源代码。CVS使用cvs export 供开发者将源代码输出到目录下。不同的是,cvs export取出的是一个干干净净的没有CVS痕迹的源代码目录树。
$cvs export –r rel-1_0_0 –d test-1.0.1 test
-d选项将输出文件保存为test-1.0.1的子目录里。
CVS模块是CVS中非常重要的概念,它是CVS的项目组织方式。CVS模块的概念大大方便了项目管理。通常,模块和CVS仓库中的目录对应。项目经理可以按照类似于目录结构的树型结构组织项目的子模块,然后将子模块合成为模块。
基本模块定义:
模块名 目录名 //目录名相对于仓库路径。
选择文件组成模块:
模块名 目录名 文件名列表 //定义模块由该目录下的文件组成,目录名
//对于仓库路径。
排除目录:
模块名 !目录名列表 //检出模块时,排除目录名列表中列出的目录
聚合多个目录:
模块名 –a 目录列表 //将多个仓库目录归于一个模块之下,不过捡出时仍在各//自的目录下。
模块别名:
模块名 &模块名 //CVS允许指向其他模块
定义CVS模块的方法:
1)、从CVS仓库中检出modules配置文件
2)、在检出的modules文件中加入模块定义
3)、将模块定义提交CVS仓库
成功后,CVS会提示:Rebuilding administrative file database。没有这个信息,表明提交失败。
如果不定义CVS模块,也可以检出代码,前提是我们知道CVS仓库中这些目录和文件的位置。让开发人员去记住CVS仓库中每个目录的位置显然不是一个好办法。在正规的开发过程中,还是要遵循定义模块的方式。
到目前为止,我们主要是对文本类型的文件进行操作,CVS对文本文件进行版本管理时,会进行一些特殊的处理,让我们使用起来更方便。如对于回车换行的处理。另外,CVS在文本文件中还会做些其他的处理工作,扩展一些包括某些关键字的字符串。但是这些修改会破坏二进制文件、控件、.dll文件、.doc文件等非文本类型的文件,而这些文件也需要进行版本管理。可以通过修改CVS配置文件禁止CVS进行替换和扩展。但是这样就没有办法对非文本类型的文件进行版本比较。因此,对于word文档必须使用word软件的修订功能进行。
1、 配置cvswrappers文件
$cvs checkout CVSROOT/cvswrappers
在该文件的末尾加上如下值(可根据具体的文件):
*.gif -k 'b'
*.GIF -k 'b'
*.jpg -k 'b'
*.JPG -k 'b'
*.png -k 'b'
*.PNG -k 'b'
*.pdf -k 'b'
*.PDF -k 'b'
*.avi -k 'b'
*.AVI -k 'b'
*.mp3 -k 'b'
*.MP3 -k 'b'
*.mpg -k 'b'
*.MPG -k 'b'
*.doc -k 'b'
*.DOC -k 'b'
*.xls -k 'b'
*.XLS -k 'b'
*.xl* -k 'b'
*.XL* -k 'b'
*.mpp -k 'b'
*.MPP -k 'b'
*.ppt -k 'b'
*.PPT -k 'b'
*.dot -k 'b'
*.DOT -k 'b'
*.jar -k 'b'
*.JAR -k 'b'
*.tif -k 'b'
*.TIF -k 'b'
*.swf -k 'b'
*.SWF -k 'b'
*.bmp -k 'b'
*.BMP -k 'b'
*.exe -k 'b'
*.EXE -k 'b'
*.o -k 'b'
*.O -k 'b'
*.tgz -k 'b'
*.TGZ -k 'b'
*.gz -k 'b'
*.GZ -k 'b'
……
$cvs commit CVSROOT/cvswrappers
一个问题是如何得到源代码的最新拷贝。首先,使用cvs -q update命令,再用make或build工具中别的命令即可。其次,在完成你的任务之前,不必去考虑得到其他人做的修改。建议的作法是更新源代码、修改、build和测试你的修改,然后提交(如果需要先更新)你的源码。通过周期性地(修改之间,如刚才的描述)更新你的源码树,你就能确定你的源代码足够新。
还有一个常见的需要是纪录build中源代码的版本。在cvs中最好的解决方法是使用tag命令来纪录特别build的版本。
大多数简单使用cvs的方式下,每个开发人员会有一份整个源码树的拷贝用于特定的build。如果源码树比较小,或者开发人员地理位置分散,这是一种比较合适的作法。对于大的项目,应当将其分成小的可以独立编译的子系统,它们可以内部发布,这样开发人员只需检出自己工作的相应的子系统。
另一种方式是创建一种结构,开发人员对部分文件有自己的拷贝,其他文件从中心获得。在许多系统上可以使用符号链接,或者使用make的VPATH特性。
试验步骤如下:
1. 创建新文件test
2. 添加新文件 cvs add test
3. 检入:cvs ci test(生成1.1版本)
4. 删除工作目录中的文件:rm test
5. 删除cvs仓库中的文件:cvs rm test
6. 检入: cvs ci test(生成1.2版本 dead)
以下是恢复过程:
7. 创建文件test
8. 添加文件cvs add test (注意屏幕上的结果显示)
9. 检入:cvs ci test (生成1.3版本)
下面比较有趣:
10.恢复文件内容:cvs up -j 1 -j 1.1 test
11. 检入:cvs ci test (生成1.4版本)
至此test文件恢复。
如果有一个项目组成员提交了一个错误的版本,大家当然不希望错误的版本是当前的最新版本。一种简单方便的解决方案是把版本回退到最近的正确版本上去。CVS不允许用户真正删除它保存的任何一个版本。所以我们不可能将提交的错误版本从CVS仓库中去掉,让上一个版本成为最新版本。
如果用cvs update -r1.2 file.name
这个命令是给file.name加一个STICK TAG: "1.2" ,虽然你的本意只是想将它恢复到1.2版本,如果不小心已经加成STICK TAG的话:用cvs update -A 解决。
这里有两个办法可以解决这个问题:
第一步,将版本更新到最新。因为在提交之前,必须更新到最新版本,否则提交的时候将出错。
$ cvs update
cvs server: Updating .
P test.c
cvs server: Updating newdir
第二步,将CVS仓库中的旧版本取出,注意,不能直接用“cvs update –r 1.2 test.c”来完成这个工作(见附着性标签(sticky tags))。下面这个命令用了一个“-p”选项,这个选项让CVS不真正执行更新工作拷贝中文件版本的任务,而是将从CVS仓库取出的源文件输出到屏幕(标准输出设备)上。为了能够直接将源代码文件原封不动地输出并保存下来,使用“-Q”(Quiet安静)选项让CVS屏蔽相关信息。将这些输出内容可以用重定向运算符“>”写入到文件中。下面的命令使得输出内容写入到test.c文件中。
$ cvs -Q update -p -r 1.2 test.c > test.c
然后,我们将test.c文件提交形成新版本。
$ cvs ci -m "reverted to revision 1.2" test.c
Checking in test.c;
/src/master/test/test.c,v <-- test.c
new revision: 1.4; previous revision: 1.3
done
我们来看看1.2版本和1.4版本是不是一模一样。
$ cvs diff -r 1.2 -r 1.4 test.c
Index: test.c
==========
RCS file: /src/master/test/test.c,v
retrieving revision 1.2
retrieving revision 1.4
diff -r1.2 -r1.4
结果表明,两个版本一模一样,任务正确完成。
这个命令最多能够用两个“-j”选项,这个选项后面的参数是版本号。当使用两个“-j”选项的时候,这个命令将两个版本之间的差异作为补丁(patch)打到当前的文件上。所以,两个“-j”选项后面的版本号的顺序是不能弄错的。它相当于连续执行了“diff”和“patch”两个命令。
$ cvs update –j 1.3 –j 1.2 test.c
RCS file: /src/master/test/test.c,v
retrieving revision 1.3
retrieving revision 1.2
Merging differences between 1.3 and 1.2 into test.c
上述命令将1.3版本与1.2版本之间的差异补到当前的1.3版本上,所以,就相当于从1.3版本上将1.2到1.3之间的变化又减去了。下面的命令显示文件的状态:
$ cvs status test.c
File: test.c Status: Locally Modified
Working revision: 1.3
Repository revision: 1.3 /src/master/test/test.c,v
Sticky Tag: (none)
Sticky Date: (none)
Sticky Options: (none)
提交,形成1.4版本。
$ cvs ci -m "reverted to revision 1.2" test.c
Checking in test.c;
/src/master/test/test.c,v <-- test.c
new revision: 1.4; previous revision: 1.3
done
查看其内容与1.2版本之间的差异:
$ cvs diff -r 1.2 test.c
$
果然,文件已经与1.2版本完全相同。任务成功完成。如果要一次回退多个文件的版本,采用第二种方法更快一些。
需要强调的一点是,如果因为开发小组成员之间的相互了解、相互沟通不够而造成大量的版本回退是一个非常危险的信号。这说明这个团队的合作现状非常差,开发小组成员之间的沟通不是CVS能够替代的。
如果你合并的文件包含关键词,你通常将会在合并时得到无数个冲突报告,因为在不同版本中非常不同。因此,常需要在合并时使用“-kk”选择项,以使用关键字名字,而非去扩展关键字的值的方法,这个功能选择项确保你合并的版本之间互相相同,而避免了冲突。
例如:假设你有一个文件如下:
并且你的当前工作目录拷贝为主干(1.2版本)。那么对于以下的合并将会产生一个冲突的结果。请看例子:
$cat file1
Key $Revision: 1.3 $
...
$cvs update -j br1
U file1
RCS file: /cvsroot/first-dir/file1,v
retrieving revision 1.1
retrieving revision 1.1.2.1
Meging differences between 1.1 and 1.1.2.1 into file1
rscmerge: warning: conflicts during merge
$ cat file1
$<<<<<<< file1
Key $Revision: 1.3 $
=======
Key $Rerision: 1.1.2.1 $
$>>>>>>> 1.1.2.1
...
冲突发生在试图将1.1和1.1.2.1合并到你的工作目录中去的时候。因此,当这个关键词从“Revision:1.1"到"Revision:1.1.2.1"时,CVS将试图合并这个变化到你工作目录, 这就同你的目录中的变更“Revision:1.2"发生了冲突。 以下是使用了:“-kk”后的例子:
$cat file1
key $Revision: 1.3 $
...
$cvs update -kk -j br1
V file1
RCS file: /cvsroot/first-dir/file1,v
retrieving revision 1.1
retrieving revision 1.1.2.1
Merging differences between 1.1 and 1.1.2.1 into file1
$ cat file1
key $Revision: 1.3 $
...
在这里版本“1.1”和“1.1.2.1"都扩展为单纯的 "Revision",因此,合并时就不会发生冲突了。
然而,使用 "-kk"参数还一个主要的问题。即,它使用了CVS通常使用的关键字扩展模式。在特殊情况下,如果模式使用针对二进制文件的 "-kb"参数。这将会产生问题。因此,如果你的数据库中包括有二进制文件,你将必须手工处理这些问题,而不能使用 "-kk"。
CVS备份的时候,CVS服务器不能处于接受用户命令的状态,就是不能做任何操作。有两种方法做到这一点:
1、一段时间内禁止任何用户使用CVS,保证所有之前提交的CVS命令已经执行完毕;
2、自动禁止所有用户使用CVS,通过在每个仓库目录下创建#cvs.rfl文件锁定CVS仓库,或者用cvslock来锁定整个CVS仓库。
CVS仓库的移动和CVS备份一样,需要首先保证没有任何CVS操作在进行才能开始。将$CVSROOT下的所有文件和目录用tar打包,用gzip压缩,然后ftp到目标计算机或者目录处。
CVS不是一个BUILD系统。cvs不包含将源代码build成软件的功能。CVS不能指导你如何构造什么,它只是将你所设计的文件以一种树结构保存下来以备恢复之用。CVS不能决定如何在一个检出的目录使用磁盘空间。如果你在每一个目录中都写下Makefiles或脚本,且必须知道其它一切的相对位置,有时不得不要检出整个源码库。见CVS与build系统的交互。
CVS不能替代管理。CVS只是一个用来使你的资源与你的步调一致的工具。项目组成员应经常交流以确保时时记得进度表、合并点、分支名和发布日期。如果不这样做,CVS也没用。
CVS不能代替开发者之间的交流。当在一个文件内或多个文件中同时发生变化时,CVS并不知道何时它们会在逻辑上发生冲突。它的冲突(conflict)概念是纯粹文本意义上的。CVS不会指出程序逻辑上非文本或分布式的冲突。例如:假如你在文件A中改变了函数X的参数。同时,别人在编辑文件B,仍用旧参数调用X这个函数。此时产生的冲突CVS可就无能为力了。
CVS没有变化控制。变化控制可以指许多事情。首先它的意思可以是BUG跟踪(bug-tracking),就是说它能维持一个数据库,其中包括已报告的BUG和每一个BUG状态(比如:是否已更正?在哪一个版本中?提交这个BUG的人是否认为已经更正?)。为了使CVS和一个外部的跟踪BUG系统协调一致,请参考rcsinfo和verifymsg。
变化控制的另一个方面指跟踪这样的情况,即对好几个文件的改变实际上只是同一个逻辑变动。如果你在一次cvs commit操作中改变了几个文件,CVS会忘掉它们是一起改变的,即便它们共用一个LOG信息。
在一些系统中,变化控制的另一个方面是跟踪每一个变化的状态的能力。一些变化由一个开发者写出,而另一些变化则由另一个开发者来作出评论,等等。一般来讲,用CVS来做,是产生一个diff(用cvs diff或diff),可以用patch来利用。这个非常灵活,但依赖于CVS之外的机理以保证事情不会崩溃。
CVS没有内建的处理模型。有些系统提供一些方法确保变更或发布通过不同的步骤,以及各种所需的批准过程。一般地,你可以用CVS来完成它,但是有点不太够。有些情况下你想用commitinfo, loginfo, rcsinfo或verifymsg文件,要求在CVS提交之前完成某些操作。
CVS仅仅是一个管理代码版本的系统。CVS不能帮助我们提高代码的质量,不能帮助我们更深入地认识代码的结构和体系,提高软件稳定性也已经完全超过了CVS的职责范围。