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手册的结构和内容。
发表评论
-
什么样的架构才是清晰的架构?这个有什么需要注意的?(zz from 水木)
2011-10-13 13:00 850发信人: zms (小美), ... -
java面试
2011-10-12 23:52 01、try{}里有一个return语句,那么紧跟在这个try后 ... -
JAVA与C++::关于JNI中文字符串操作问题总结
2011-10-12 23:26 929JAVA与C++::关于JNI中文字符串操作问题总结 Linu ... -
[翻译]Java 范型与集合类 :演化 ,而不是革命 (第一部分)
2011-10-12 23:21 837[翻译]Java 范型与集合类 :演化 ,而不是革命 (第一部 ... -
学习笔记 - java.util.concurrent 多线程框架 [转]
2011-10-12 22:58 687java concurrent 多线程 pac ... -
java反射机制
2011-10-12 22:39 746java反射机制 2007年02月2 ... -
转载: 五种常见开源协议的比较
2011-09-28 11:29 880BSD开源协议 BSD开源协议是一个给于使用者很 ...
相关推荐
版本控制与cvs工具使用说明的ppt CVS简介 客户端工具--TortoiseCVS C/S架构 CVS仓库目录建立在服务器端,客户端的工作目录与之相对应,所有的CVS操作均在此工作目录中进行。 CVS客户端工具: Windows系统:...
**版本控制插件CVS详解** CVS(Concurrent Versions System),即并发版本系统,是一种广泛使用的开源版本控制系统,尤其在软件开发领域中扮演着重要角色。它允许开发者在一个团队中协同工作,对代码进行修改、跟踪...
CVS(Concurrent Versions System)是一种广泛使用的源代码版本控制系统,它允许开发团队协同工作,同时跟踪代码的历史版本。在软件开发过程中,CVS扮演着至关重要的角色,它确保了团队成员之间的协作效率,同时也...
版本控制器CVS~~
版本控制服务器 CVS 的一些经验 版本控制服务器 CVS 是一个开放源代码的版本控制系统,广泛应用于软件开发过程中。以下是使用 CVS 服务器的经验总结: 多个工作目录 在使用 CVS 服务器时,可以在本地设置多个工作...
cvs 版本控制 软件项目管理 cvs 版本控制 软件项目管理
CVS是一个C/S系统,多个开发人员通过一个中心版本控制系统来记录文件版本,从而达到保证文件同步的目的。CVS环境初始化 环境设置:指定CVS库的路径CVSROOT tcsh setenv CVSROOT /path/to/cvsroot bash CVSROOT=/...
CVS(Concurrent Versions System)是一种广泛使用的源代码版本控制系统,它允许开发团队协同工作,同时跟踪文件和目录的历史变化。CVS系统的核心概念包括版本化、分支、合并以及冲突解决,这些都是软件开发中协作的...
CVS版本控制简明使用手册 CVS(Concurrent Versions System)是一种主流的开放源码网络透明的版本控制系统,适合个人开发者到大型、分布团队使用。CVS 的功能非常强大,它是一个 C/S 结构的软件,需要架设一个 CVS ...
在IT行业中,版本控制系统是开发团队协作的重要工具,...通过了解CVS版本控制的工作原理以及如何删除与之相关的文件,你可以更有效地管理和维护你的项目。在团队协作和项目迁移的过程中,理解这些基本操作至关重要。
**CVS(Concurrent Versions System)版本控制系统**是一种开源的、用于管理软件源代码的分布式版本控制系统,它在软件开发领域扮演着至关重要的角色。CVS允许开发团队成员协同工作,跟踪代码更改历史,合并不同人的...
TortoiseCVS将CVS的强大功能与Windows资源管理器集成,使得用户可以通过直观的界面进行版本控制操作,如查看文件历史、提交更改、更新项目、解决合并冲突等,而无需记忆复杂的命令行语法。 在TortoiseCVS-1.8.31....
### CVS版本控制心得详解 CVS(Concurrent Versions System)是一种经典的版本控制系统,主要用于软件开发过程中对源代码的管理。本文基于撰写人金珉的一年多实战经验,深入解析CVS版本控制策略,旨在为项目管理者...
在本学习资料中,我们将重点讨论Eclipse中与CVS(Concurrent Versions System,协同版本系统)的整合,这是一款开源的版本控制系统,用于跟踪项目文件的变更历史,确保团队开发时的数据安全和协作效率。 **一、CVS...
#### 二、CVS与传统备份方法的区别 相比传统的备份方式,如使用`tar`命令进行打包存储,CVS的优势在于高效利用存储空间。具体来说,当使用`tar`命令备份文件时,系统通常会保存整个文件的副本,即使文件只有微小的...
本文将详细介绍Java开发中常用的三种版本控制工具:SVN、CVS和VSS。 **1. SVN(Subversion)** SVN全称是Apache Subversion,是一个开源的集中式版本控制系统。它允许开发者在一个中央存储库中存储和管理项目的...
这份“CVS版本控制器资料”涵盖了如何有效地利用CVS管理项目,以及如何将其与MyEclipse集成使用的教程。下面将详细介绍CVS的核心概念、功能以及与MyEclipse的集成步骤。 1. CVS核心概念: - **版本控制**:CVS允许...