CVS (Concurrent Version Control System) 是一个能让很多程式开发者同时做软体开发的非常强大工具。对于它可能大部分软件工程师都应该有所接触,起码也是对这个名字如雷贯耳了。CVS的基本命令和 使用,网上已经有了很多的教程,我就不再罗嗦。本文想介绍的,可以说是CVS的精华,同时又是对初学者来说很难理解和掌握的(包括当时我也花了很多精力去 学习)的两个功能:tag和branch。
一、 tag
1.1 revision number(修订号)
要理解tag,首先要介绍一下revision number(修订号)。在CVS中每个文件的版本都有一唯一的 revision number。修订号的形式一般是这样的:`1.1',`1.2',`1.3.2.2' 甚至是 `1.3.2.2.4.5'。一个修订号总有偶数个用句号分隔的十进制数。按照默认,文件第一个修订号是 1.1。每个新的修订号的最右边的数会比它的上一个修订号的最右边的数大 1。下图显示了一些修订号,较新的版本在右边。
+-----+ +-----+ +-----+ +-----+ +-----+
! 1.1 ! ----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 !
+-----+ +-----+ +-----+ +-----+ +-----+
其实,对于大多数 cvs 用户来说,不需要考虑修订号;他们只要知道 cvs 已经自动地加上了类似 1.1,1.2 之类的修订号就可以了。
1.2 tag (标签)
由于每个文件都有自己的修订号,每次提交该文件一次,它的修订号就会就会增加。这样就会产生一个问题:对于软件的某一个发行版,源文件的修订号可能都不一样。例如:
ci.c 5.21
co.c 5.9
ident.c 5.3
rcs.c 5.12
rcsbase.h 5.11
rcsdiff.c 5.10
rcsedit.c 5.11
rcsfcmp.c 5.9
rcsgen.c 5.10
rcslex.c 5.11
rcsmap.c 5.2
rcsutil.c 5.10
因此,要checkout某一个特定的发行版的所有源码的时候,如果是要根据修订号来的话,是异常繁琐和难以跟踪的。这个时候,就要用到CVS提供的很炫的一个功能了:tag(标签)。
标签可以用来标记多个文件的一组修订号,你可以想象标签以文件名为横轴,以版本号为纵轴绘制了一个曲线图(或者也可以想象成在一个由文件名和版本号组成的矩阵里面绘制的曲线)。
在过去的某个时候带 * 的版本号已被标记上标签。你可以把标签想象成一条经过所有被标记的文件的曲线。当你抓住线就得到所有标签标记的版本了。也可以通过另一种方式来看待这一点:把被同一个标签标记的所有版本号经过的曲线拉直, 然后直直地看过去。
1.3 tag命令的用法
下面的例子说明了怎样给一个文件添加标签。命令必须在模块的工作目录中发出。也就是说,你应该在 backend.c 文件所在的目录中发出该命令。
$ cvs tag rel-0-4 backend.c
T backend.c
$ 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 tag rel-1-0 .
cvs tag: Tagging .
T Makefile
T backend.c
T driver.c
T frontend.c
T parser.c
(当把一个目录作为 cvs 的一个参数的时候,该命令不仅对该目录下的所有文件执行操作,而且也会递归地对该目录下的所有子目录中的文件执行操作。)
如果要检出一个模块的某个版本,可以使用checkout -r命令。在 checkout 命令中使用 `-r' 标志可以检出一个模块某个版本。下面的命令可以很容易地取出模块 `tc' 1.0 发行版的所有源文件:
$ cvs checkout -r rel-1-0 tc
1.4 什么时候使用tag
tag的功能就像是给你的工程的某个时刻建立了一个快照。添加了tag后,不论你最源文件做了任何 修改,只要发现你的修改发生了错误,或者是如果有人宣称在某个版本里有个 bug,但你在当前工作的副本中是找不到那个 bug,都可以根据tag重新rollback回去,或checkout出那个快照。
tag给开发人员带了这样的便利,因此在任何重要的开发阶段,都应该打上tag。
一般性的,在下面的情况都应该考虑给你的工程简历一份快照(tag):
完成了某个重要的功能
在每一个milestone
在去掉某个存在功能之前
在测试开始之前
在你对源文件做重大修改之前
新建分支(branch,下文会详细谈到)的时候
很并分支之前
当然了,这些都是一般性的建议。其实我感觉是只要你觉得做的修改可能会有副作用的时候,就应该打上tag。
二 tag and branch
在SVN中Branch/tag在一个功能选项中,在使用中也往往产生混淆。
在实现上,branch和tag,对于svn都是使用copy实现的,所以他们在默认的权限上和一般的目录没有区别。至于何时用tag,何时用branch,完全由人主观的根据规范和需要来选择,而不是强制的(比如cvs)。
一般情况下,
tag,是用来做一个milestone的,不管是不是release,都是一个可用的版本。这里,应该是只读的。更多的是一个显示用的,给人一个可读(readable)的标记。
branch,是用来做并行开发的,这里的并行是指和trunk进行比较。
比
如,3.0开发完成,这个时候要做一个tag,tag_release_3_0,然后基于这个tag做release,比如安装程序等。trunk进入
3.1的开发,但是3.0发现了bug,那么就需要基于tag_release_3_0做一个branch,branch_bugfix_3_0,基于这
个branch进行bugfix,等到bugfix结束,做一个tag,tag_release_3_0_1,然后,根据需要决定
branch_bugfix_3_0是否并入trunk。
对于svn还要注意的一点,就是它是全局版本号,
其实这个就是一个tag的标记,所以我们经常可以看到,什么什么release,基于xxx项目的2xxxx版本。就是这个意思了。但是,它还明确的给出
一个tag的概念,就是因为这个更加的可读,毕竟记住tag_release_1_0要比记住一个很大的版本号容易的多。
branches:分枝
当多个人合作,可能有这样的情况出现:John突然有个想法,跟原先的设计不太一致,可能是功能的添加或者日志格式的改进等等,总而言之,这个想法
可能需要花一段时间来完成,而这个过程中,John的一些操作可能会影响Sally的工作,John从现有的状态单独出一个project的话,又不能及
时得到Sally对已有代码做的修正,而且独立出来的话,John的尝试成功时,跟原来的合并也存在困难。这时最好的实践方法是使用branches。
John建立一个自己的branch,然后在里面实验,必要的时候从Sally的trunk里取得更新,或者将自己的阶段成果汇集到trunk中。
(svn copy SourceURL/trunk DestinationURL/branchName -m "Creating a private branch of xxxx/trunk." )
trunk:主干
主干,一般来说就是开发的主要呆的地方,
tag:
在经过了一段时间的开发后,项目到达了一个里程碑阶段,你可能想记录这一阶段的代码的状态,那么你就需要给代码打上标签。
(svn cp file:///svnroot/mojavescripts/trunk file:///svnroot/mojavescripts/tags/mirrorutils_rel_0_0_1
-m "taged mirrorutils_rel_0_0_1")
另有一说,无所谓谁对谁错。
trunk:表示开发时版本存放的目录,即在开发阶段的代码都提交到该目录上。
branches:表示发布的版本存放的目录,即项目上线时发布的稳定版本存放在该目录中。
tags:表示标签存放的目录。
在这需要说明下分三个目录的原因,如果项目分为一期、二期、三期等,那么一期上线时的稳定版本就应该在一期完成时将代码copy到branches上,这样二期开发的代码就对一期的代码没有影响,如新增的模块就不会部署到生产环境上。而branches上的稳定的版本就是发布到生产环境上的代码,如果用户使用的过程中发现有bug,则只要在branches上修改该bug,修改完bug后再编译branches上最新的代码发布到生产环境即可。tags的作用是将在branches上修改的bug的代码合并到trunk上时创建个版本标识,以后branches上修改的bug代码再合并到trunk上时就从tags的version到branches最新的version合并到trunk,以保证前期修改的bug代码不会再合并。