技术开发 频道

第N次亲密接触CVS :Eclipse开发必备

        【IT168 专稿】说起CVS,作为版本控制与管理工具,几乎是无人不知、无人不晓。好吧,我承认。但是说起CVS的全称,未必所有人都知道吧?索性我们拿关键字CVS去Google英文搜索一下。有意思的是,第一条是http://www.cvs.com,但却是一家在线药品与医疗网站,并非我们想要的。第二条CVS维基百科才是我们想要的结果。

  维基百科对CVS的表述为:CVS的全称是Concurrent Versions System或者Concurrent Versioning System。无论老外是何种叫法,中文翻译过来都是并发版本系统。不言而喻,CVS有三个特征,并发性,版本控制,系统性。

  为什么我会写这篇文章呢。原因也很简单,我天天使用Eclipse做软件开发,我天天使用CVS。可是,我并非天天只做update/commit操作。当有一天,我需要一个patch,一个branch,一个tag,我就开始疑惑了,这些东西怎么用啊?怎么网上讲的总是东一块、西一块的,不怎么系统。索性,我就萌生了写一篇文章,系统性地介绍Eclipse的CVS功能。

  你可以说,具有这三大特征的版本控制与管理工具多的是,坦白来讲,CVS也不是完美的。比如由于实现方式的不同,SVN比CVS的速度要快。为什么我会选择CVS呢?主要原因我是Eclipse的忠实粉丝,而Eclipse天生就集成了一个CVS客户端。所以就变成了爱屋及乌了。

  前面介绍过,与一般性的介绍性文章相比,本文更注重系统性。再者,本文注重CVS的关键特性,特别是一些容易被忽视但却实用的高级特性。不但如此,文章会深入CVS的原理,将SVN与CVS做对比,以及总结一些实战技巧。本文目标读者是经常使用Eclipse CVS开发项目的开发人员。

  CVS三部曲:拷贝,修改,合并

  简单的说,CVS是一个遵循GNU的开源客户机/服务器端系统,允许多个开发人员通过一个中心版本控制系统来记录文件版本,从而达到保证文件同步的目的。这种客户机/服务器工作模式使得开发者可以从任何因特网的接入点存取最新的代码。

  CVS的工作原理很简单,就是Copy-Modify-Merge(拷贝、修改、合并)。具体来讲:CVS服务器端创建一个源代码库(CVS Repository),库里可以存放许多不同项目的源程序,由管理员统一管理。CVS客户端支持不通的平台。在使用CVS任何源代码库之前,开发人员需要将项目文件检出(Check Out)到本地,然后根据需要编辑(Edit),最后叫文件提交(Check in)至CVS服务器。

  CVS Repository

  CVS源代码库(CVS Repository)指的是CVS 存储所有修订版本历史记录的地方。每个项目都有自己的一个确定的源代码库。需要指出的是,创建一个新的Repository需要管理员权限。

  我们可以通过Eclipse创建一个新的CVS Repostority。具体方法如下:

  选择CVS Repository透视图 -> 右键New –> Repository Location ,弹出Add CVS Repository对话框,如下图所示。

 

  1. Location

  Location区描述的是CVS Repository的定位信息,包括主机(Host)与存储路径(Repository)。CVS Repository支持local与remote两种方式。对于本地,主机名为localhost或者127.0.0.1,存储路径前缀为“:local:”。例如,:local:/usr/local/cvsroot表示CVS Repository位于本地/usr/local/cvsroot目录。其URL的写法也因操作系统而有所差别。对于Linux,:local:/usr/local/cvsroot等价于/usr/local/cvsroot。对于Windows,:local:c:/src/cvsroot等价于c:\src\cvsroot。

  Repository的目录分为两部分:$CVSROOT/CVSROOT包含的是CVS的管理文件,而其余部分为用户自定义模块。我们除了使用Eclipse CVS客户端的方式指定Repository之外,还可以通过CVS命令行的方式,如下:

  cvs -d /usr/local/cvsroot checkout yoyodyne/tc #–d选项表示direcotry,即CVS Repository对应的目录。

  它等价于:

  setenv CVSROOT /usr/local/cvsroot

  export CVSROOT

  checkout yoyodyne/tc

  我们来分析一下CVS Repository对应的数据结构。我们假设当前的CVS Repository为/usr/local/cvsroot,如图。CVS Repository目录包含两部分,一部分为administrative files, 给CVS系统管理员使用,记录一些Repository相关的元数据等。另一部分就是源代码目录结构,这里源代码项目的根目录为yoyodyne。

 

  2. Authentication

  Authentication区描述的是认证信息。前面提到,匿名用户不允许创建Repository,只有管理员才有权限。

  3. Connection

  Connection区描述的是CVS客户端与CVS 服务器端的Repository之间的通信协议。当然,CVS客户端与服务器端可以是同一台机器,此时的主机名为localhost或者127.0.0.1。按照类型与需求的不同,又分为以下三类协议,分别为:pserver,ext/extssh,pserverssh2。具体含义如下:

  pserver协议:指CVS客户端向服务器发送的密码以明文的方式传送。对于匿名用户,URL为cvs -d :pserver:fun.example.com:/usr/local/cvsroot; 而对于密码用户,按照显式与隐式划分,URL分别表示为:

  cvs -d :pserver:doublelife@fun.example.com:/usr/local/cvsroot login

  CVS password:

  或者

  cvs -d :pserver:doublelife:p4ss30rd@fun.example.com:/usr/local/cvsroot login

  注意:cvs –d 命令表示指定CVS Repository。

  ext/extssh:指使用SSH建立CVS客户端/服务器间的安全连接。因此,从应用场景上说,Pserver通常适用于普通用户或匿名用户,而对于要求安全性较高的开发人员,则推荐使用ext/extsssh。ext与extssh的区别在于extssh只支持SSH1,而ext支持SSH1与SSH2。换句话说,extssh是ext的子集,推荐使用ext。需要说明的是,Eclipse CVS Repository透视图本身已经内置有SSH客户端,无需额外的插件。下面是演示的是ext协议对应的CVS命令:

  export CVS_RSH=ssh

  cvs -d :ext:doublelife:p4ss30rd@fun.example.com:/usr/local/cvsroot

  pserverssh2:指的是pserver over SSH2。我们简单的理解为pserverssh2是SSH2与pserver两种协议的叠加。相比pserver与ext/extssh来说,并不常用,这里就不给出示例了。

  对于CVS客户端/服务器间的通信协议,除连接类型外,还需要指定服务器端监听端口。服务器既可以使用默认端口,也可以根据需要,指定端口。比如,pserver协议对应的默认端口为2401,如果该端口被其他的服务器占用,处于侦听状态,此时CVS 服务器可以为Repository指定可用端口。

  在完成所有字段的填充后,我们推荐选中“是否在向导完成后验证连接有效”单选框,确保所填信息完整、有效。

  作为示例,我们演示一下使用Eclipse来连接著名开源网站sourceforge的FTP软件filezilla的CVS Repository,如下图所示。这里我们采用的是匿名用户,对应的连接协议为pserver,端口默认值。

  如之前所述,CVS Repository为树状层级结构。在正式开始copy->modify->merge三部曲之前,有必要花些时间熟悉上图CVS的几个关键概念:Module,HEAD,Branches,Versions,Dates。

  Module

  如前所述,CVS Repository分为两部分,一部分是CVSROOT,描述CVS工程相关的元数据。另一部分即为Module,表示工程的模块。比如filezilla分为三大模块,分别为FileZilla,Filezilla Server,Filezilla3。

  HEAD

  简单的说,HEAD表示CVS代码的主干与主体部分,正常情况下,我们对CVS的操作三部曲均发生在HEAD目录中。

  Branch

  而对于一些特殊的情况,我们采用的Branch方式。Branch相对于HEAD而言,指的是代码的分支部分,我们简单的理解为补丁。举个例子,假设我们的项目发布了第一个版本V1.0,第二版本V2.0正在开发当中,处于不稳定状态,随时有代码的改动。而与此同时,客户报告重大的bug,需要我们立即修复。于是,我们check out V 1.0的代码,调试,并找到解决办法,并发布补丁。为了让补丁与开发代码隔离,我们可以创建一个新的branch,用于V 1.0的补丁,用户从branch中check out的是V1.0的补丁,而不会得到位于HEAD目录的尚在开发阶段V2.0代码。当然根据需要,开发人员可以将branch代码合并至HEAD中。

  为更好地说明问题,我们从版本Revision控制的角度来对比HEAD与Branch的关系。一般说来,HEAD的Revision历史呈线性增长趋势,如下图:

  由于Branch概念的介入,使得CVS并不局限于线性的开发,HEAD版本可以分为若干不同的Branch,每一个Branch是一个独立开发的自我维护的开发线。如下图所示,出现了三个不同的分支,分支号的编排依赖于它分离出的主线版本。使用分支号允许一个特定版本分离出多个分支,图中Revision 1.2同时派生出两个不同的Branch。一个分支也允许派生出多个子分子,Branch 1.2.2派生了一个sub branch。

 

  一个Branch的变更可以很容易转移到HEAD中。可以通过CVS命令update配合-j选项实现合并。这里的-j表示join。

  举个例子,我们当前的HEAD版本为1.4。现在需要将分支1.2.2合并到HEAD。

 

      我们假设模块”mod”只包含一个文件”mod.c”。分支1.2.2分配了一个名字叫R1fix。

  CVS checkout mod #检出最新版本1.4

  CVS update –j R1fix m.c #合并所有分支中的变更,即1.2与1.2.2.2.2的合并

  CVS commit –m “Included F1fix” #建立新版本1.5

  下图就是合并后的Revision历史结构。

  在合并过程中可能会发生冲突,可以通过手工地方式解决。

  Versions/Tag

  Version是标签Tag的集合。所谓Tag指的是当项目达到某一个milestone时,对所有文件做一个标签,记录历史记录。通常对一个release做一个tag,如下图所示。

  为更好地理解tag的工作原理,我们可以将 tag 想象成为一条在由文件名和修订号组成的矩阵上穿过的“曲线”,如下图所示:

  

        【IT168 专稿】当我们将该 tag 曲线上的 * 标记拉直后,你就得到了由所有 tag 修订号组成的如下图的水平线:

  由于tag的目的是记录开发历史,因此人们通常不会删除或者改变标签。对tag的删除,移动,重命名多半是因为临时使用标签或者不小心放错位置。警告,下列命令会永久删除历史信息,使用时应尽量小心。

  cvs rtag -d rel-0-4 tc

  Dates/Tags

  与Versions一样,Dates也是标签Tag的集合。区别在于前者以版本作为tag的标记,而后者则以时间为标记。相比与Date类型,Version Tag更加常用。

  为了更好的全面理解这些核心CVS概念,我们通过Show History视图将这些概念串联起来,如下图所示。

  有对了相关概念的理解,接下来,我们将介绍CVS的常用操作。相信大家对日常的操作如update,commit非常熟悉,只做简单介绍。更多地是关注那些容易忽视但是特别使用的命令。

  作为三部曲之一的Copy操作,CVS对应的命令为check out。具体操作是,选择HEAD目录下的目标源文件夹,右键单击Check Out,如图:

  Check Out

  Check out之后的本地工作目录与CVS目录一致。如果想自定义文件夹的根目录,则可选择Check Out As命令。

  Switch to another Branch or Version

  作为三部曲之二的Modify操作,除正常的源代码变更操作外,对应一个非常重要的Eclipse CVS命令,即Switch to another Branch or Version,即切换到其它分支或者标签做源代码的修改,如图:

  Merge

  作为三部曲之三的Merge操作,除正常的update/commit外,也对应的一个非常重要的CVS命令merge,即将branch或tag与HEAD中的Base version进行合并,如图。

  Branch

  此外,如果想创建一个新的分支,可通过右键Team -> Branch打开Create a new CVS Branch。

  Tag

  如果需要发布一个新的版本2.0.6,可以新创建一个Tag。步骤为右键源代码工程 –> Team -> Tag as

  Version,如图。通过CVS Repository透视图可以查看相应的Tag。

  Patch

  补丁(patch)允许开发人员共享尚未提交到CVS的代码变更。补丁程序包含的是本地资源与CVS资源库的差别CVS Diff命令。补丁在许多场合都非常有用:

  · 由于权限的限制,需要将补丁程序发送给权限用户提交。

  · 需要一个临时工作区间隔离待提交的代码。

  · 在提交CVS之前,需要对更改的文件测试,可以将补丁程序发送给测试人员。

  要创建一个补丁,使用右键Team –> Create Patch,开启Create Patch向导完成补丁文件的创建。

  这里我们将补丁程序导出成文件patch.txt。按照默认方式完成向导。patch.txt内容如下:

  ### Eclipse Workspace Patch 1.0

  #P source

  Index: AsyncGssSocketLayer.cpp

  ===================================================================

  RCS file: /cvsroot/filezilla/FileZilla/source/AsyncGssSocketLayer.cpp,v

  retrieving revision 1.14

  diff -u -r1.14 AsyncGssSocketLayer.cpp

  --- AsyncGssSocketLayer.cpp 4 Mar 2005 10:35:04 -0000 1.14

  +++ AsyncGssSocketLayer.cpp 28 Nov 2011 07:26:39 -0000

  @@ -1,6 +1,6 @@

  // GSSAsyncSocksifiedSocket.cpp: implementation of the CAsyncGssSocketLayer class.

  //

  -//////////////////////////////////////////////////////////////////////

  +//////////////////////////////////////////////////////////////////////

  // Part of this code is copyright 2001 Massachusetts Institute of Technology

  #include "stdafx.h"

  patch.txt实际上是CVS Diff命令的输出,从而实现了CVS Repository之外的资源共享。相应地,开发人员可以共享补丁程序。

  Apply Patch命令提供了打补丁的方法,如图所示。

  比较&替换

  人生没有后悔药,但是Eclipse提供了,而且特别简单。快捷菜单操作Replace With和Compare With提供了本地历史记录与CVS Repository比较与替换的机会。需要说明的是,CVS Repository的资源既可以是HEAD也可以来自某一个分支,或者某一个tag。

  重命名

  一般情况下,要避免对CVS Repository作重命名操作。对于工程而言,CVS对待重命名后的工程为新工程。相应地,需要使用Team -> Disconnect操作解除项目与CVS的关联,然后再对项目重命名,最后,在重新连接到CVS之后,需要像其它新的工程一样定义。而对于文件而言,重命名会导致出现一个新的文件,不过幸运的是,原文件的内容会被拷贝到新的文件中。

  CVS vs SVN

  至此,相信读者对Eclipse CVS客户端有一个基本的了解。不过,CVS并非是完美的。下面我们来比较下两者的一些主要的不同点,如下表。

  存储类型存取速度事务总体评价

  CVS文件相比SVN慢没有实现4

  SVN数据库相比CVS快完全实现5

  实际上,CVS是一个古老的系统,其内部结构有许多改进之处。知道今天,仍有人想重头开始,重写CVS,但都未能成功,SVN的开发人员花了很多时间与心思在改进内部结构,使其变得简单、优雅,甚至有人认为SVN是CVS的接班人。

1
相关文章