分类:
2008-07-17 22:35:08
CVS 是一种客户机/服务器系统,可以让开发人员将他们的项目存储在称为资源库的中央位置。使用 cvs 客户机工具,开发人员可以对资源库的内容进行更改。CVS 资源库会依次记录对每个文件所做的每个更改,并创建一个完整的项目开发进展历史。开发人员可以请求特定源文件的旧版本、查看更改日志,并根据需要执行其它一些有用的任务。
许多开放软件项目都有他们自己的 CVS 服务器,项目开发人员把这些服务器作为他们工作的源码仓库。源码仓库的源码目录中保存的都是实现版本控制的历史文件(history file),历史文件名为filename,v。历史文件包含用来恢复所有版本文件的足够信息、所有提交的信息以及提交者信息。历史文件常常被称作RCS 文件,因为最早是RCS程序用这种格式来保存文件的所有修改信息,可以从man rcsfile得到历史文件的。
开发人员每天都会改进 CVS 资源库内的源码,且他们往往分布在世界各地,而 CVS 提供了一种必要的机制,将他们的项目联合成一个集中的、协作的整体。CVS 创建了"组织粘和剂",可以让这些开发人员改进代码而不会干扰别人、丢失重要数据或遗漏彼此对特定源文件的重要更新。
当开发人员准备好以后,他们把 CVS 上部分当前工作打包成 .tar.gz 文件,作为软件包的新官方版本来发布它。然而,由于种种原因,最新的官方发行版有时并不是最新的。在本教程的第一部分将首先介绍如何使用CVS为个人使用获取最新和最高开发人员版本的源码。
CVSROOT
在开始前,您需要了解一些 CVS 的基础知识。首先,为了连接到 CVS 资源库,您需要知道称为 "CVSROOT" 的路径。CVSROOT 是一个字符串,就象 URL,它告诉 cvs 命令远程资源库在哪里,以及如何连接它。不仅如此,根据 CVS 资源库是本地的还是远程的,以及连接到它的不同方式,CVS 还有许多不同的 CVSROOT 格式。这里有一些带有解释的 CVSROOT 示例。
本地 CVSROOT
|
这是一个本地 CVSROOT 路径的示例;如果您想连接到 /home/cvsroot 中存在的本地资源库,或者有一个经 NFS 安装在 /home/cvsroot 的资源库,需要象这样使用 CSROOOT。
远程密码服务器 CVSROOT
|
这里是一个远程资源库的 CVSROOT 示例,该资源库位于 foo.bar.com 主机上,并在这台机器的 /home/cvsroot 目录中活动。前导 ":pserver:" 告诉我们的客户机使用 CVS 密码服务器协议连接到这台远程机器,该协议内置在 CVS 中。一般情况下,公共 CVS 资源库使用密码服务器协议以允许匿名用户访问。
远程 rsh/ssh CVSROOT
|
这是一个使用 RSH 或 SSH 协议的 CVSROOT 的示例;在该例中,CVS 服务器尝试使用 drobbing 帐户来访问在 foo.bar.com 上的资源库。如果 CVS_RSH 的环境变量设置成 "ssh",那么我们的客户机就尝试用 ssh 去连接;否则就使用 rsh。那些关注安全性的用户往往使用 ssh 访问法;但是,无论是 RSH 还是 SSH 方法都不能对匿名用户提供一种获取源码的方式。为了使用这种方法,您在 foo.bar.com 上必须有个登录帐户。
除了 CVSROOT 之外,您还需要知道要检出的模块(源码集合)的名称,以及登录到 CVS 密码服务器的匿名密码。与匿名 ftp 不同,匿名密码没有什么"标准"格式,所以您需要从开发人员网站或开发人员那里获得具体的密码。一旦知道了所有这些信息,就可以开始了。
与 CVS 交互
获取源码需要两个步骤。首先,以远程密码服务器的方式登录到CVS服务器。然后,使用"checkout"命令获取源码。这里有一组命令的示例,用于检出最新的 Samba 源码(一个流行的 UNIX/Windows 集成项目):
|
第一个命令设置 CVSROOT 环境变量。如果没有设置这个变量,下面两个命令将需要跟在 "cvs" 命令后再加上 "-d :pserver:cvs@pserver.samba.org:/cvsroot"。设定 CVSROOT 环境变量省去了一些输入。
|
上面第一个 cvs 命令是让我们登录到 pserver,第二个命令告诉 CVS 客户机使用 gzip 压缩级 5 ("-z5") 在慢速连接上加快传输速度,来检出 ("checkout") samba 模块。对于每个在本地创建的新文件,cvs 都会打印 "U [path]" 表明这个特定的文件已经在磁盘上更新过了。
一旦检出命令完成,将在包含最新源码的当前工作目录中看到 "samba" 目录。还会注意到每个子目录下都有一个"CVS"目录 -- CVS 在这些目录中存储帐户信息,可以放心地忽略它们。一旦检出结束,用户就无需担心是否设置了 CVSROOT 环境变量,也无需再在命令行上指定它,因为现在所有额外的 "CVS" 目录里都有它的缓存。
记住 -- 只需要为初始登录和检出设置 CVSROOT。
更新源码
现在已经有了源码,就可以继续编译和安装它们、检查它们,或者对它们执行任何操作。
偶尔,也需要将已检(checkout)出的源目录与 CVS 上的当前版本保持同步。为了做到这一点,您无需再次登录到 pserver;cvs 会将您的认证信息缓存到那些 "CVS"帐户目录中。首先,进入主检出目录(在这里是 "samba"),然后输入:
|
如果有任何新文件,cvs就会在更新每一行的时候输出 "U [path]" 行。另外,如果本地编译了源码,您有可能会看到许多 "? [path]" 行;cvs 指出这些目标文件不来自于远程资源库。
另外,请注意我们用于 "cvs update" 的两个命令行选项。"-d" 告诉 cvs 创建可能已添加到资源库的新目录(缺省情况下,这不会发生),"-P" 告诉 cvs 从本地已检出的源码副本中除去所有空目录。"-P" 是个不错的选择,因为 cvs 倾向于收集许多随时间产生的空(曾经使用过,但现在已经放弃)目录树。
如果只是要获得最新的源码,这些就是您所需要了解的。现在,来看一下作为一个开发人员如何与 CVS 交互。
修改文件
作为一名开发人员,您需要修改 CVS 上的文件。要修改文件,只需要对资源库的本地副本进行适当的更改。在您明确地告诉 cvs "提交"更改之前,您对源码做的更改不会应用到远程资源库。测试过所有修改以确保它们可以正常运作之后,就可以准备将这些更改运用到资源库中,遵循下面的两个步骤。首先,在主源码目录中输入以下命令来更新源码:
|
CVS 合并其他人的更改
我们在前面已经看到,"cvs update"将用资源库中的当前版本使您的源码保持最新状态 -- 但对您已经做过的更改会发生什么情况呢?不要担心,它们不会被丢弃。如果另一个开发人员对您没动过的文件做了一些更改,您的本地文件将进行更新以使它与资源库中的版本保持同步。
如果您修改了本地文件中的第 1-10 行,而另一个开发人员删除了第 40-50 行,在文件末尾添加了 12 行新行,同时修改了 30-40 行,然后在您之前他向资源库提交了他的更改,cvs 会智能地将这些更改合并到您本地已修改的副本中,这样你们的更改就都不会丢失。这可以让两个或更多开发人员针对同一文件的不同部分同时操作。
然而,如果两个或多个开发人员更改同一文件的同一部分,那么事情就有些复杂了。如果发生这种情况,cvs 会告诉您有冲突发生。所做的工作不会丢失,但需要手工干预,因为 cvs 需要您提供意见来决定如何合并这些有冲突的更改。
提交
我们过一会儿来看看冲突究竟是如何解决的,但现在,先让我们假设在输入 "cvs update -dP" 后没有冲突 -- 通常都没有冲突。由于没有冲突,本地源码是最新的。可以在主源码目录中输入以下命令来提交对资源库的更改:
|
提交所起的作用
"cvs commit" 不只将您的更改应用到资源库。在真正将您的更改提交给远程资源库之前,cvs 会调用缺省编辑器,可以让您输入修改的描述。输入了注解后,保存该文件,退出编辑器,您的更改(和注解)就会应用到远程资源库,小组中的其他开发人员可以看到这些更改。
查看日志
要查看某个特定文件完整的历史以及提交时开发人员(包括您)所加的注解是很容易的。要查看这些信息,输入:
|
"cvs log" 命令是递归的,所以如果您想查看整个目录树的完整日志,只需要进入该目录,输入:
|
提交选项
您可能想要使用另一个输入 "cvs commit" 时 cvs 在缺省情况下启动的编辑器。如果是这样,只需要把您希望使用的编辑器的名称放进 EDITOR 环境变量中。另外,也可以将更新日志消息作为命令行选项指定,这样, cvs 就不需要一上来就装入编辑器:
|
.cvsrc 文件
在继续了解其它 cvs 命令前,建议设置 ~/.cvsrc 文件。通过在您主目录中创建 .cvsrc 文件,可以告诉 cvs 在缺省情况下使用您所需的命令行选项,这样就不必每次都输入它们。这里有一个推荐的缺省 .cvsrc 文件:
|
除了为一组 cvs 命令设置有用的选项以外,.cvsrc 的第一行将 cvs 置于安静模式下,它的主要好处是使 "cvs update" 输出更简洁,更具可读性。另外,一旦完成 .cvsrc 后,就可以输入 "cvs update" 而不是 "cvs update -dP"。
将文件添加到资源库
要将源文件添加到 CVS 很容易。首先,用您喜爱的文本编辑器创建该文件。然后,输入以下命令:
|
这将告诉 cvs 在您下次执行 "cvs commit" 时,将该文件添加到资源库。在那之前,其它开发人员看不它。
将目录添加到资源库
将目录添加到 CVS 的过程类似于:
|
与添加文件不同,当您添加目录时,它会立即出现在资源库中;不需要 cvs commit。将本地目录添加到 cvs 后,您会注意到在远程cvs服务器的对应目录中创建了一个 "CVS" 目录,它作为包含 cvs 帐户数据的容器。因而,您只要看一下其中是否有 "CVS" 目录,就可以很容易地知道某个目录是否已添加到远程cvs服务器的 cvs中了
在将文件或目录添加到资源库之前,您必须确保它的父目录已经添加到 CVS。否则,您会看到类似于下面的错误:
|
熟悉 "cvs update"
在了解如何解决冲突之前,先让我们熟悉一下 "cvs update" 命令的输出。如果创建了一个包含 "cvs -q" 行的 ~/.cvsrc 文件,您会发现 "cvs update" 的输出相当容易理解。"cvs update" 通过打印单个字符、空格和文件名告诉您它都做些什么,看到些什么;如下例所示:
|
"cvs update" 用 "?" 字符指示在地副本中找到的这些特殊文件。它们不是资源库的正式部分,也不是计划要添加的部分。这里有一个CVS使用的所有其它单字符信息性消息的列表:
U [path]
在本地资源库中创建新文件,或者更新了您没有动过的文件时使用。
A [path]
该文件是计划要添加的,使用 "cvs commit" 时,它被正式添加到资源库。
R [path]
象 "A" 一样,"R" 让您知道该文件计划要除去。输入 "cvs commit" 后,该文件就会从资源库中除去。
M [path]
这意味着您已经修改过该文件了;而且,有可能资源库中新的更改已成功地合并到该文件。
C [path]
"C" 字符表明该文件存在冲突,需要在使用 "cvs commit" 提交更改前手工修改它。
如何解决冲突
现在,让我们看一下如何解决冲突。我参与了大部分的 Gentoo Linux 项目,我们在 cvs.gentoo.org 上设置了自己的 cvs 服务器。我们这些开发人员花了绝大部分时间来修改 "gentoo-x86" 模块内部的源码。在 gentoo-x86 模块中,有一个叫 "ChangeLog" 的文件,它包含了(您猜一下它)我们对资源库中该文件做的主要更改的描述。
冲突的示例
因为几乎每次开发人员对 CVS 进行主要更改时都会对该文件做一些修改,这就成为冲突的主要根源 -- 这里有冲突的一个示例。假设我在 ChangeLog 的顶部添加了以下行:
|
然而,在我提交这二新行之前,另一个开发人员也在 ChangeLog 的顶部添加了这些行并提交了他的更改:
|
现在,当我运行 "cvs update -dP" (每次提交前您都应该这么做),cvs 不能把他的更改合并到我的 ChangeLog 本地副本,因为我们俩都向文件的同一部分添加了行 -- cvs 怎么知道用哪个版本?所以,CVS 会出现以下错误:
|
啊 -- 有冲突!幸好消除这些冲突很容易。如果我启动我喜爱的文本编辑器,将会在 ChangeLog 文件的顶部看到以下文本:
|
cvs 不是选其中一个而舍弃另一个,而是把两个版本都加进 ChangeLog 文件,并用特殊的分隔符将它们圈起,以明确地标记出有问题的冲突。现在,我要用一个应该出现在 ChangeLog 里的文本来替换这部分;在这种情况下,替换的文本既不是我的版本也不是他的版本,而是两者的组合:
|
我用适当的文本替换掉了有冲突的部分(并除去了 "=======" 等标记),可以顺利地将我的更改提交给 cvs。
无论什么时候需要编辑文件来解决冲突时,都要确保已经浏览过整个文件,以便您知道所有内容;如果您忘记解决某个冲突,那么在这个冲突解决之前,cvs 是不允许您提交的!很显然,除去 cvs 添加到冲突文件中的特殊标记是很重要的。
如果您在解决某个冲突时犯了错误,然后意外地保存了您所作的更改,那么您可以在 ".#filename.version" 文件中找到您的原始副本。
删除文件
现在应该学习关于 CVS 的最后一个技巧 -- 从资源库中除去文件。除去文件是一个两阶段过程。首先,从源码的本地副本删除该文件,然后执行相应的 "cvs remove" 命令:
|
在您下次提交时,该文件计划将从资源库中除去。一旦提交,该文件会从资源库当前的版本中正式删除。然而, cvs 不会将该文件抛弃,而是仍然完整地保留该文件的内容及其历史,以备您以后需要它。这只是 cvs 保护您有价值的源代码的众多方法之一。
"cvs remove" 是递归的,这意味着您可以删除一批文件,然后从父目录运行不带其它自变量的 "cvs remove" 命令。这样做会在下次提交时标记所有已删除的文件。
如果您想除去整个目录,我推荐使用下列过程。首先,从物理上删除,"cvs remove" 删除目录中的所有文件:
|
然后执行提交:
|
这里有一个诀窍。执行以下步骤删除目录:
|
请注意,除去目录不需要另一次提交 -- 把目录添加到资源库和从资源库除去目录是实时的。
修订号
源码仓库中的一个文件可以有多个版本,同样一个软件可能有多个版本。一般在CVS中前一种意义上的版本(源码仓库中文件的版本)一般称为修订号(revisions),而对软件的版本一般称为发布(release)。
一个文件的修订号一般是依次递增的,一般以偶数个通过"."连接的十进制数串来表示,如:`1.1', `1.2', `1.3.2.2'甚至`1.3.2.2.4.5
标签的定义和使用
源码仓库中各个文件的修订号是独立增加的,相互之间没有任何关联关系,和软件的发布号也没有任何关系,例如一个项目中的各个文件的修订号可能是这样的:
|
为了便于标记,可以使用标签来为某个特定版本的特定文件设定一个标记以方便访问,可以使用cvs tag和cvs rtag来定义标签,其中cvs tag用来为仓库中当前工作文件(或文件集合)指定一个符号标记;cvs rtag用来显式地为源码仓库的特定修订号的文件定义一个标记。例如下面的例子就是给文件backend.c的当前修订号定义一个标签,然后察看该文件的状态:
|
但是在实际应用中很少会为特定文件定义一个标签,而往往是在开发过程中的特定阶段为特定项目的所有文件定义一个标签,以方便发布或者定义分支,例如:
|
定义标签以后,可以在随后的任何时候访问对应该标签的项目文件,例如下面这个命令就用来实现检出对应于标签rel-1-0的所有文件(可能是软件的1.0发布):
|
可以想像标签为联系特定修订号文件的曲线索引,例如:
这里表示在软件开发过程中的定义了标签*,也可这样看待标签:
当然也可以删除标签,例如:
cvs rtag(/tag) -d rel-0-4 tc 删除模块tc的rel-0-4标记
备份源码仓库
备份: 首先断开所有的cvs连接,然后使用cp命令备份即可
分支
通过CVS可以实现将对源码的修改提交给一个独立的开发线,被称为分支(branch)。当对分支的文件进行修改时,这些修改不会对主分支和其他分支产生影响。
随后可以将将一个分支的修改合并(merging)到其他分支。合并是通过cvs update -j来合并修改到当前工作目录(本地),然后就可以提交修改来影响其他分支了。
分支的重要性
让我们假设这种情况,项目tc的1.0版本已经搞定,你将继续开发tc项目,计划在几个月内发布1.1版本,但是客户抱怨软件中有致命的错误。因此你检出1.0版本(这里就是需要使用标记的原因)并发现了这个bug的原因。然而当前的源码处于1.0和1.1版本之间因此代码处于混乱状态,而且在一个月内不大可能出现稳定版本,因此不大可能根据当前的版本得到一个修复错误的版本来发布。
对于这种情况这就需要创建一个包含错误修改的1.0分支发布,而不需要影响当前的开发,在合适的时候可以将修正合并的主发布中去。
创建分支
创建分支首先为拟修改的某些文件创建一个标签(tag),标签是赋于一个文件或一组文件的符号.在源代码的生命周期里,组成一组模块的文件被赋于相同的标签。
创建标签:在工作目录里执行cvs tag 。
例: 为src创建标签:
cvs checkout src(/update亦可,用来更新本地的源代码)
cvs tag release-1-0(为当前最新源码加一个标签)
标签创建后, 就可以为其创建一个分支:
cvs rtag -b -r release-1-0 release-1-0-path print
-b :创建分支
-r release-1-0:-r参数用来标记那些包含指定的标签的文件
releas-1-0-patch:分支
print: 模块名
可以使用tag -b来创建分支,例如在工作目录中:
|
将会基于当前工作的分支分离一个分支,并将该分支命名为`rel-1-0- patches'。应该理解分支是在cvs的源码仓库中创建的,而不是当前工作目录,基于当前修正创建分支不会将当前工作拷贝自动转换为新的分支,需要手工来实现的。也可以通过使用rtag命令实现不涉及工作目录的分支:
|
`-r rel-1-0'指示创建的新分支应该以标记`rel-1-0'指定的修订为基础,而不是基于当前的工作主分支。这主要是用来从旧版本中创建一个分支(例如上面的例子)。
rtag -b指示创建分支(而不是仅仅创建标记)。应该注意的是`rel-1-0'包含的各个文件的修订号可能是不一样的。
所以该命令的效果是为工程tc创建一个新分支-名字为`rel-1-0-patches',以标记`rel-1-0'为基础。
访问分支
可以以两种方式访问分支:从源码仓库中检出分支代码,或者将当前的工作拷贝切换为分支。
从源码仓库中创检出新分支可以使用命令'checkout -r release-tag'命令:
|
将当前分支切换到分支命令:
|
或者
|
随后的提交等影响源码仓库的操作都仅仅对分支起作用,而不会影响主分支和其他分支。可以使用status命令来察看当前工作拷贝属于哪个分支。输出中察看'sticky tag'信息,这就是cvs显示当前工作拷贝是在哪个分支上:
|
不要被每个文件的分支号码不同(`1.7.2'和`1.4.2')所迷惑,分支是由分支标记来决定的这里都是' rel-1-0-patches'。这里的分支号仅仅表示当生成该分支时每个文件的修订号。
分支和修订号
通常情况下源码库中文件的修订号是以线性增大的:
+-----+ +-----+ +-----+ +-----+ +-----+
! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 !
+-----+ +-----+ +-----+ +-----+ +-----+
但是CVS并不局限于线性开发,源码树可能出现分支,每个分支是一个对立的开发线。对分支的修改会很容易地被加入到主分支中来。
每个分支都拥有一个分支号,由使用"."分隔的奇数个数组成的数串来表示。分支号是通过在产生分支处的修订号后添加一个整数来实现得到的。通过定义分支号使得从同一个分支点分出多个开发分支成为可能。
分支的修订号是通过在分支号后顺序添加数字后缀得到的,如下图所示:
分支号产生的详细细节其实是不必详细了解的,但是了解其工作原理是必要的:当CVS创建一个分支时,其选择一个最小的偶数作为分支号后缀。因此当希望从修订号6.4分出一个分支时,新分支的分支号为6.4.2。所有以0作为分支号后缀的分支号都被CVS自己保留使用(例如6.4.0)。
合并分支
分支开发一段时间以后往往需要将修订合并到主分支中来,可以通过`-j branchname'参数实现合并分支。使用该参数将分支和其父分支合并。
考虑下面的源码修订树:
分支1.2.2被定义为标记R1fix。下面的例子假设模块mod仅仅包含一个文件m.c:
$ cvs checkout mod #获取最新的1.4版本
$ cvs update -j R1fix m.c # 合并分支的所有更新到主分支
# 也就是1.2到1.2.2.2的修改合并到当前工作拷贝
$ cvs commit -m "Included R1fix" # 创建修订版本1.5.
合并时可能出现冲突情况,如果发生冲突,应该在提交以前手工处理冲突。
检出命令(checkout)命令同样支持参数 `-j branchname',上面的操作可以通过下面的命令实现;
|
一个需要注意的问题是"update -j tagname"同样能
参考资料:
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=606468