因为更改文件名并且把它们移动到另一个目录中不是经常发生的,因此你在开始一个新项 目时要做的第一件事是考虑你的文件组织。更改文件名或移动文件并非不可能,但增加了 理解上潜在的费解,并且CVS在更改名字的目录上特别的敏感。请参见7.4节[移动文件]。3 使用CVS开始一个项目 ======================= 因为更改文件名并且把它们移动到另一个目录中不是经常发生的,因此你在开始一个新项 目时要做的第一件事是考虑你的文件组织。更改文件名或移动文件并非不可能,但增加了 理解上潜在的费解,并且CVS在更改名字的目录上特别的敏感。请参见7.4节[移动文件]。 (* 译者注: 在Unix中改名和移动是相同的)。 下一步做的事取决于手中的情况。 3.1 建立文件 第一步是在仓库中建立文件。这可以使用多种不同的方法来完成。 -------------------------------------------------------------------- 3.1.1 建立一个目录树 当你开始使用CVS时,你可能已经有几个项目可以使用CVS进行管理了。在这种情况下, 最容易的方法就是使用: "import"命令。一个例子是最容易解释如何使用它的。假定你现 在有一些你想放到CVS中的文件在"wdir"中,并且你想让它们放在数据仓库中的如下目录: "$CVSROOT/yoyodyne/rdir" 你可以使用如下命令: $cd wdir $cvs inport -m "Inported Sources" yoyodyne/rdir yoyo start 如果你没有使用"-m"参数记录一个日志信息,CVS将调用一个编辑器(*译者注:通常是vi) 并且提示输入信息。"yoyo"字符串是开发者标笺,"start"是发行标笺。他们没有什么特别 的意义,仅仅是因为CVS的需要才放在这里。 请参见第13章[跟踪代码],得到更多的这方面信息。 你现在可以检查一下它是否有效了,然后可以删除你原来的代码目录。 $cd $mv dir dir.orig $cvs checkout yoyodyne/dir $diff -r dir.orig yoyodyne/dir $rm -r dir.orig. 为了避免偶然进入到你原来的目录中去编辑文件,删除原来的代码是个好主意。当然,在 你删除之前保留一份备份到其它地方也是明智之举。 "checkout"命令能使用模块名作为参数(正如前面所有例子)或是一个相对于$CVSROOT的路 径,如同上面的例子。你应当检查CVS目录中的权限情况是否合适,应当使它们属于某一个 特定的组。请参见2.2.2.节[文件权限]。 如果你想"import"的一些文件是二进制代码,你可以使用一些特殊的方法表明这些文件是否 是二进制文件。请参见C.2节[Wrappers]。 ------------------------------------------------------------------------------------ 3.1.2 从其它版本控制系统建立文件 如果你有一个其它版本控制系统维护的项目,例如RCS,你也许希望把这些文件放到CVS中, 并且要保留这些文件的历史。以下是一些讨论。 从RCS: 如果你使用RCS,找到RCS文件??通常一个文件名叫"foo.c"的文件会有"RCS/foo.c,v"的RCS文 件。(但它有可能在其它地方,请看RCS的文档以得到相关信息)。如果文件目录在CVS中不存 在,那在CVS中创建它。然后把这些文件拷贝到CVS的仓库目录中(在仓库中的名字必须是带 ",v"的原文件;这些文件直接放在CVS中的这个目录下,而非"RCS"子目录中)。这是在CVS中 一个为数不多的直接进入CVS仓库直接操作的情况,而没使用CVS命令。然后你就可以把它们 在新的目录下"checkout"了。 当你把RCS文件移动CVS中时,RCS文件应在未被锁定的状态,否则移动操作时CVS 将会出 现一些问题。 从其它版本控制工具 许多版本控制工具都可以输出"RCS"格式的标准文档。如果你的版本控制工具可以做到这一 点,直接输出RCS文件,然后按照上面的例子做就可以了。 如果你的版本工具不能输出RCS文件,那么你必需要写一个脚本文件来,每次取出一个版本 然后把它放到CVS中去。下面提到的"sccsarcs"脚本就是一个很好的例子。 从SCCS: 有一个"sccsarcs"的脚本文件可以做把SCCS的文件转化成RCS文件,这个文件放在CVS原代码 目录的"contrib"目录中。注意: 你必须在一台同时安装了RCS和SCCS的机器上运行它。并且,正如其它在"contrib."目录中的 其它脚本一样。(你的方法也许是变化多端的) (*译者注:我并未查看过CVS的contrib目录:-(,因此不知道这下面都有些什么)。 从PVCS: 在"contrb"中有一个叫"pves-to-rcs"的脚本可以转换PVCS到RCS文件。你必须在一台同时有 PVCS和RCS的机器上运行它。 请看脚本中的注释以得到更多细节。 3.1.3从无到有建立一个目录树 建立一个新的项目,最容易的方法是建立一个空的目录树,如下所示: $mkdir tc $mkdir tc/man $mkdir tc/testing 在这之后,你可以"import"这个(空)目录到仓库中去。 $cd tc $cvs import -m "created directory structure"yoyodyne/dir yoyo start 然后,当新增一个文件时,增加文件(或目录)到仓库中。请检查$CVSROOT中的权限是否正确。 ------------------------------------------------------------------------------------------- 3.2 定义模块 下一步是在"moduyes"文件中定义模块。这不是严格需要的,但模块能把相关的目录和文件容易 关联起来。下面的例子可以充分演示如何定义模块。 1. 得到模块文件的工作拷贝。 $cvs checkout CVSROOT/modules $cd CVSROOT 2. 编辑这个文件并写入定义模块的行。请参见2.4节[管理文件的介绍]。有一个简单介绍,参见C.1节 [模块文件]。有它的详细描述。你可以使用下面的行定义模块"tc": tc yoyodyne/tc 3. 提交你的更改到仓库 $cvs commit -m "Added tc module." modules 4. 发行模块文件 $cd $cvs release -d CVSROOT 4 ======= 5 分支与合并 ================== CVS允许你独立出一个派生的代码到一个分离的开发版本---分支。当你改变一个分支中的文 件时,这些更改不会出现在主开发版本和其它分支版本中。 在这之后你可以使用合并(merging)把这些变更从一个分支移动到另一个分支(或主开发版 本)。合并涉及到使用“cvs update -j”命令,合并这些变更到一个工作目录。你可以确认 (commit)这次版本,并且因此影响到拷贝这些变更到其它的分支。 ----------------------------------------------------------------------------------- 5.1 何时应当创建一个分支 假定tc.c发行版已完成。你正在继续开发tc.c,计划在2个月后发行1.1的版本。在不久以后你的 客户开始抱怨说代码有些问题,你检查了一下1.0的发行版(请参见4.4节[标笺])并且找到了这 个错误(这将会有一个小小的更正)。但是,这个当前的版本是处于一个不稳的状态,并且在下 一个月才能有希望稳定下来。这样就没有办法去发行一个最新的现有版本去更正问题。 这时就可以去创建基于这棵版本树1.0版的分支。你可以修改这棵树的分支而不影响到主干。当 修订完成时,你可以选定是否要把它同主干合并或继续保留在这个分支里。 ----------------------------------------------------------------------------------- 5.2 建立一个分支 你可以使用“tag -b”去建立一个分支。例如,假定你在工作于一个工作拷贝中: $cvs tag -b rel_1_0_patches 这将基于当前的拷贝分离出一个分支,并分配“rel_1_0_patches”的名字。 懂得分支是在CVS仓库中创建,而非在工作拷贝中创建的是非常重要的。正上面的例 子,创建一个基于当前版本的分支不会自动把当前的工作拷贝转到新的分支上。欲知 详情,请看5.3节[进入 一个分支]。你也可以不参考任何工作拷贝而建立一个分支。 你可以使用rtag命令: cvs rtag -b -r rel-1-0 rel-1-0-patches tc. “-r rel-1-0”说明这个分支是基于标志了rel-1-0的版本文件,它不是从最新的版本 开始分支.这对需要从老的版本进行分支很有用(例如:当修订一个过去的稳定版本时) 当使用“tag”标志,这个“-b”标志告诉rtag去创建一个分支(而非是这个版本的符号 连接)。注意标记“rel-1-0”可能对不同的文件有不同的版本数字。因此,这个命令的结果 是为tc模块建立了一个命名为“rel-1-0-patches”的新版本分支,它是基于标记为“rel-1-0” 的版本树。 ----------------------------------------------------------------------------------- 5.3 进入分支 你可以通过两种方式进入分支:重新checkout或是从现存的拷贝进入。重新checkout使用 checkout命令并带上“-r”标识,后面是这个分支的标笺(tag)名。(请看5.2[创建一个分支]): $cvs checkout -r rel-1-0-patches tc. 或者如果你已有了一个拷贝,你可以使用“update -r”命令转到这个分支。 $cvs update -r rel-1-0-patches tc. 或者使用另一个等同的命令: $cd tc $cvs update -r rel-1-0-patches 这对现有拷贝为主干代码或是其它分支都是有效的.上面的命令将把它转到命 名的分支。同“update”命令相类似。“update -r”合并你所做的任何改变,请注 意是否有冲突出现。 一但你的工作拷贝已经转向一个特定的分支。它将一直保持在这个分支内,除非你 又做了其它的操作。这意味着从这个工作拷贝checkin的变更将加到这个分支的新版 本中,而不影响到主干版本和其它分支代码。 想看一个工作拷贝是基于哪一个分支,可以使用“status”命令。在它们输出中查找 一个“sticky tag”的域(请参见4.9节["sticky tag"],第38页).那就是你的当前分支号。 $cvs status -v driver.c backend.c ==================================================================== File: driver.c Status: Up-to-date Version: 1.7 Sat Dec S 18:25:54 1992 RCS version: 1.7 /u/cvsroot/yoyodyne/tc/driver.c,v Sticky Tag: rel-1-0-patches (branch: 1.7.2) Sticky Date: (none) Stick Option: (none) Existing Tag: rel-1-0-patches (branck: 1.7.2) rel-1-0 (revision: 1.7) ==================================================================== File: backend.c status: Up-to-date Version: 1.4 Tue Dec 1 14:39:01 Rcs Version: 1.4 /u/cvsroot/yoyodyne/tc/ Sticky Tag: rel-1-0patches(branch:1.4.2) Sticky Date: (none) Sticky Option: (none) Existing Tag: Rel-1-0-patches (branch: 1.4.2) Rel-1-0 (revision: 1.4) Rel-0-4 (revision: 1.4) 请不要因为每个文件的分支是不同(“1.7.2”和1.4.2")而迷惑。分支的标笺(tag) 是相同的:"rel-1-0-patches",这些相同标笺的文件是相同分支的。在以上的例子中,分支建 立之前,"driver.c" 比 "backend.c"有更多的变更,因此它们的版本编号是不同的。请参见5.4节 [分支和主干版本号]去了解分支如何构建原理的细节。 -------------------------------------------------------------------------------------- 5.4 分支与主干版本 通常,一个文件的主干版本历史是一个增长线(请看4.1[主干版本]页): +-----+ +-----+ +-----+ +-----+ +-----+ ! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! +-----+ +-----+ +-----+ +-----+ +-----+ 然而,CVS并不局限于线性的开发。主干版本可以分为不同的分支,每一个分支可 以是一个独立的自我维护的开发线。而在一个分支中的变更可以很容易的转移到主干中。 每一个分支均有一个分支号,由一个“.”分离的十进制奇数组成,分支号的编排依 赖于它分离出的主线版本。使用分支号允许从一个特定版本分离出多个分支。 所有的分支版本都依赖于它的原始分离版本号。下面的例子将展示这一点。 +-------------+ 1.2.2.3.2 分支 -> +--! 1.2.2.3.2.1 ! ! +-------------+ ! +---------+ +---------+ +---------+ 1.2.2 分支-> +--! 1.2.2.1 !----! 1.2.2.2 !----! 1.2.2.2 ! ! +---------+ +---------+ +---------+ ! ! +-----+ +-----+ +-----+ +-----+ +-----+ ! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- 主干 +-----+ +-----+ +-----+ +-----+ +-----+ ! ! ! +---------+ +---------+ +---------+ 1.2.4 分支-> +--! 1.2.4.1 !----! 1.2.4.2 !----! 1.2.4.2 ! +---------+ +---------+ +---------+ 你是如何创建具体的分支号的细节通常不是你需要考虑的,但这里谈谈它如何工作。 当CVS建立一个分支号时,它先得到第一个未用的偶数,开始的数字是2,例如你 从6.4的主干版本创建分支时,分支号为6.4.2所有分支号码末位为0的号码用于CVS内 部,(例如6.4.0)。(请参见5.5节[内部分支号]44页)分支1.1.1有特别的含义,请看13章 [跟踪代码]。 ----------------------------------------------------------------------------------- 5.5 内部分支号码 这一节描述CVS的内部分支(magic branches) (* 译者注:magic branch 译为内部分支) 特性。在大多数情况下,你不用考虑内部分支号码,CVS将为你进行管理。然而,在 一些特定条件下,它将显现出来,因此理解它如何工作将是有用的。一般的,分支号 码将由奇数个 "."分隔的十进制整数组成。请看4.1节[版本号码]。然而那并非完全是这 样的,由于效率的原因,CVS有时插入一个额外的“0”在右末的第二个位置(1.2.4 变为1.2.0.4,8.9.10.11.12变为8.9.10.11.0.12等)。 CVS将会很好的将这些变换隐蔽在背后进行,但在一些地方,这种隐蔽并不完全: * 内部分支编号会出现在CVS的日志(log)文件中。 * 你不能够对 "cvs admin" 使用符号分支名。 你可以使用admin命令去为一个分支重新分配一个RCS希望的那样的符号名。如果 R4patches是一个分配给分支1.4.2(内部分支编号为1.4.0.2)的一个文件"numbers.c"的 命名,你可以使用如下命令: $cvs admin -NR4patches:1.4.2 numbers.c 它将只在至少一个版本已经提交到这个分支时才会有效。请非常小心不要把一个标 笺(tag)分配给了一个错误标识号(现在没有看到昨天的一个标笺是如何分配的)。 ---------------------------------------------------------------------------------- 5.6 合并一个整个分支 你可以合并一个分支到你的工作目录在“update”命令中“-j 分支号”的标识。使 用“-j 分支号”将合并这个派生分支点与原版本的最新版之间的变更到你的工作目录 “-j”的意思是“join”。 我们现在来考察下面这棵树: +-----+ +-----+ +-----+ +-----+ ! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 ! <- 主干 +-----+ +-----+ +-----+ +-----+ ! ! ! +---------+ +---------+ R1fix 分支-> +--! 1.2.2.1 !----! 1.2.2.2 ! +---------+ +---------+
分支1.2.2分配了一个Rifix的名字.下面的例子假定模块"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版 在合并时可能会发生冲突,如果这种情况发生,你可以在提交新版本之前解决它。请 参见10.3节[冲突的例子]。 如果你的原文件中包含关键字(请看第12章[关键字替代])。你可能会得到比严格意义 上的冲突更多的冲突信息。请参见5.10节[合并和关键字],去了解如何避免这个问题。 "checkout"命令也支持使用"-j"参数。下面的例子同上面所用的例子有相的效果。 $cvs checkout -j R1fix mod. $cvs commit -m "Included R1fix" --------------------------------------------------------------------------------- 5.7 从一个分支多次合并。 继续我们上面的例子。现在这棵树看起来是这样的:
+-----+ +-----+ +-----+ +-----+ +-----+ ! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- 主干 +-----+ +-----+ +-----+ +-----+ +-----+ ! * ! * ! +---------+ +---------+ R1fix 分支-> +--! 1.2.2.1 !----! 1.2.2.2 ! +---------+ +---------+ 正如上面所讨论的,分支1.2.2.2所引导的“*”号表示从Rifix分支到主干的合并。
现在我们继续开发Rifix分支: +-----+ +-----+ +-----+ +-----+ +-----+ ! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 ! <- 主干 +-----+ +-----+ +-----+ +-----+ +-----+ ! * ! * ! +---------+ +---------+ +---------+ R1fix 分支-> +--! 1.2.2.1 !----! 1.2.2.2 !----! 1.2.2.3 ! +---------+ +---------+ +---------+ 然后你可能会希望合并新的变更到主干中去。如果你仍使用“cvs update -j Fifix m.c" cvs将试图合并你已经合并过的东西,这可能写导致一些不希望发生的东西。 因此,你必须表达清楚你希望只合并未被合并的内容的意思。这样需要使用两个 “-j“参数。CVS合并从第一个“-j”的版本到第二个“-j”版本的变化。例如,在我们上面 的例子中: cvs update -j 1.2.2.2 -j R1fix m.c 如果出现的问题是你需要手工指定1.2.2.2的版本号,一个更好的方法是使用: cvs update -j R1fix:yesterday -j R1fix m.c 然而,更好的方式是在每一次合并后重新放一个标笺给Rifix分支,然后使用标 笺做后的合并: cvs update -j merged_from_Rifix_to_trunk -j R1fix m.c ---------------------------------------------------------------------------------- 5.8 合并两个任意版本之间的不同 使用两个“-j”标志,这个update(和checkout)命令能合并两个任意不同的版本 到你的工作目录。 $cvs update -j 1.3 backend.c 将把1.5版本恢复到1.3版本,所以一定要注意版本的顺序。 如果你在操作多个文件时使用这个选择项,你必须了解在不同的文件之间,版本的 数字可能是完全不同的。你必须使用标笺(tag)而不是使用版本号来完成多个文件 的操作。使用两个“-j”操作也能会恢复增加或删除的文件。例如,假定你有一个 叫“file1”的文件在在于1.1版本中,然后你在1.2版本中删除了它,下面是如何操作的例子: $cvs update -j 1.2 -j 1.1 file1 file1 $cvs commit -m test checking in file1; /tmp/cvs-sanity/cvsroot/first-dir/file1 file1,v new revision:1.3; previous revision:1.2 done $ ------------------------------------------------------------------------------------ 5.9 合并能添加和删除文件 如果你在合并时的改变涉及到添加或删除一些文件,“update -j”将反映这些变化。 例如: cvs update -A touch a b c cvs add a b c ; cvs ci -m "added" a b c cvs tag -b branchtag cvs update -r branchtag touch d ; cvs add d rm a ; cvs rm a cvs ci -m "added d , removed a" cvs update -A cvs update -j branchtag 在这些命令之后(注意要commit),文件'a'将被删除,而文件'd'将被加入到主干。 ------------------------------------------------------------------------------------- 5.10 合并和关键词 如果你合并的文件包含关键词(参见第12章[关键词替代],73页),你通常将会在 合并时得到 无数个冲突报告,因为在不同版本中非常不同。 因此,你常需要在合并时使用“-kk”(参见12.4节[替代模式],75页)选择项。使用 关键字名字,而非去扩展关键字的值的方法,这个功能选择项确保你合并的版本之间互相相 同,而避免了冲突。 例如:假设你有一个文件如下: +---------+ br1 -> +--! 1.1.2.1 ! ! +---------+ ! ! +-----+ +-----+ ! 1.1 !----! 1.2 ! +-----+ +-----+ 并且你的当前工作目录拷贝为主干(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"。 10 多个开发者同时工作 --------------------- 当多个开发者同时参与一个项目时,常常会发生冲突。一般经常发生的情况是两个人想 同时编辑一个文件的时候。它的解决方法之一是文件锁定或是使用保留式的checkout,这种 方法允许一个文件一次只允许一个人编辑。这是一些版本控制系统的唯一解决方式,包括 RCS和SCCS。现在在CVS通常使用保留式checkout的方法是使用"CVS admin-1"命令(参见A-6-1AB [admin选择项])。在下面将解释这不是一种好的智能的解决方式,当它是许多人喜欢使用的 一种方式。下面也将讲述可以使用适当的方法来避免两个人同时编辑一个文件,而非使用软件 的方式强迫达到这一点。 在CVS中默认的方法是"unreserved checkout"--非保留式的导出。在这种方法下,开发者 可以同时在他们的工作拷贝中编辑一个文件。第一个提交工作的没有一种自动的方法可以知道 另一个人在编辑文件。另一个人可能会在试图提交时得到一个错误信息。他们必须使用CVS命令 使他们的工作拷贝同仓库的内容保持更新。这个操作是自动的。 CVS可以支持facilitate多种不同的通信机制,而不会强迫去遵守某种规则,如"resered checkouts"那样。以下的部分描述这些不同的方式是如何工作的,和选择多种方式之间涉及到 的一些问题。 10.1 文件状态 基于你对导出的文件使用过的操作,和这些文件在仓库中的版本使用过的操作,我们可以 把一个文件分为几个状态。这个状态可以由"status"命令得到,它们是: up-to-date: 对于正在使用的这个分支,文件同仓库中的最后版本保持一致。 Locally Modified: 你修改过文件,但没有"commit"。 Locally added: 使用了"add"命令增加文件,但没有"commit" Locally Removed: 你使用了"remove"命令,但没有"commit" Needs checkout: 其他人提交了一个更新的版本。这个状态的名字有些误导,你应当使用"update"而非 "checkout"来更新新的版本。 Needs Patch: 象"Needs checkout"一样,但CVS服务将只给出Patch(补丁)文件,而非整个文件。而 给出Patch和给出整个文件的效果是相同的。 Needs Merge: 一些人提交了一个更新版本,而你却修改过这些文件。 File had conflicts on merge: 这同"Locally Modified"相象,只是"update"命令给出了一个冲突信息。如果你还没有 解决冲突,那么你需要解这个问题,解决冲突的方法参见10.3节[冲突的例子]. Unkown: CVS不知道关于这个文件的情况.例如,你创建了一个新文件,而没有使用"add"命令 为了帮助弄清楚文件的状态,"status"也报告工作版本(working vevision),这是这个文件是从哪个版本来的,另外还报告"仓库版本"(Repository vevision)。这是这个文件在仓库中的这个版本分支的最后版本。 "status"命令的选择项例在附录B[invoking cvs]。有关"sticky tag"和"sticky date"输出内容的信息,参见4.9节[sticky tags]。有关"sticky options"输出内容参见"-k"选择项, A.16.1节[update选择项]。 你应当把"update"和"status"命令放在一起来认识。你使用"update"使你的文件更新到最 新状态,你使用"status"命令来得到"update"命令将会做何种操作。(当然,仓库中的状态将可 能会在你运行update之前变化)。事实上,如果你想使用一个命令得到比使用"status"正式的状 态信息,你可以使用: $cvs -n -q -update 这里"-n"选择项表示不真正执行update,而只显示状态;"-q"选择项表示不打印每个目录的 名字。有关更多的关于"update"命令的住处参见附录B[使用CVS]。 10.2 使一个文件更新到最版本 当你想更新或是合并一个,使用update命令。对于一个不是最新版本的文件,这个命令大略等 同于"checkout"命令:最新版本从仓库中提出并放到工作目录中。 当你使用"update"命令时,你修改过的文件在任何情况下不会受到损害。如果在仓库中没有更 新的版本,"update"时你的代码没有任何影响。当你编辑过一个文件,并且仓库中有更新版本,那 么"update"将合并所有的变更到你的工作目录。 例如,想象一个你导出了一个1.4版的文件并且开始编辑它,在某一时候其他人提交了1.5版,然 后又提交了1.6版,如果你运行update命令,CVS将把1.4版到1.6版之间的变更放到你的文件中。 如果在1.4版和1.6版之间的改变太靠近于的你一些变更的话,那么一个"覆盖"("overlop")冲突 就发生了。在这种情况下将输出一个警告信息,然后结果保留的文件中包含了有冲突代码的两个版 本,由特别的符号所分隔开。请参见A.16节[更新],可以得到关于"update"命令的一个完全的描述。 12:03 | 固定链接 | 评论 (0) | 引用通告 (0) | 记录它 | CVS 固定链接 关闭 http://spaces.msn.com/members/spire/Blog/cns!1plakp3AigUhLG_JZqIujcRw!318.entry CVS使用经验谈 作者:龚天乙 CVS 是 Concurrent Version System(并行版本系统)的缩写,用于版本管理。在多人团队开发中的作用更加明显。CVS 的基本工作思路是这样的:在一台服务器上建立一个仓库,仓库里可以存放许多不同项目的源程序。由仓库管理员统一管理这些源程序。这样,就好象只有一个人在修改文件一样。避免了冲突。每个用户在使用仓库之前,首先要把仓库里的项目文件下载到本地。用户做的任何修改首先都是在本地进行,然后用 cvs 命令进行提交,由 cvs 仓库管理员统一 修改。这样就可以做到跟踪文件变化,冲突控制等等。 由于CVS是建立在在原先 Unix 体系里很成熟的 SCCS 和 RCS 的基础上,所以CVS多是Linux(UNIX)系统中所使用,本文中服务器端设置也是以Linux为例。 一、CVS服务器的安装 首先确认系统中是否安装CVS服务: [root@localhost /]# rpm -qa|grep cvs cvs-1.11.2-cvshome.7x.1 如果命令输出类似于上面的输出则说明系统已经安装有cvs,否则就需要从安装光盘中安装cvs的rpm包,或者到http://www.cvshome.org下载。 1、建立 CVSROOT 目录,因为这里涉及到用户对CVSROOT里的文件读写的权限问题,所以比较简单的方法是建立一个组,然后再建立一个属于该组的帐户,而且以后有读写权限的用户都要属于该组。假设我们建一个组叫cvs,用户名是cvsroot。建组和用户的命令如下 #groupadd cvs #adduser cvsroot 生成的用户宿主目录在/home/cvsroot(根据自己的系统调整) 2、用cvsroot 用户登陆,修改 /home/cvsroot (CVSROOT)的权限,赋与同组人有读写的权限: $chmod 771 . (或者770应该也可以) 注意:这一部分工作是按照文档说明做的,是否一定需要这样没有试验,我会在做试验后在以后版本的教程说得仔细一点。如果您有这方面的经验请提供给我,谢谢。 3、建立CVS仓库,(仍然是 cvsroot 用户),用下面命令: $cvs -d /home/cvsroot init 4、以root身份登陆,修改 /etc/inetd.conf(使用 xinetd 的系统没有此文件)和 /etc/services 如果用的是 inetd 的系统,在 /etc/inetd.conf 里加入: cvsserver stream tcp nowait root /usr/bin/cvs cvs -f --allow-root=/home/cvsroot pserver 说明:上面的行是单独一整行,/usr/bin/cvs 应该是你的cvs版本的命令路径,请根据自己的系统调整./home/cvsroot是你建立的CVSROOT的路径,也请根据上面建立目录的部分的内容做调整。 如果是使用 xinetd 的系统,需要在 /etc/xinetd.d/ 目录下创建文件 cvspserver(此名字可以自己定义),内容如下: # default: on # description: The cvs server sessions; service cvsserver { socket_type = stream wait = no user = root server = /usr/bin/cvs server_args = -f --allow-root=/cvsroot pserver log_on_failure += USERID only_from = 192.168.0.0/24 } 其中only_from是用来限制访问的,可以根据实际情况不要或者修改。修改该文件权限: # chmod 644 cvspserver 在/etc/services里加入: cvsserver 2401/tcp 说明:cvsserver 是任意的名称,但是不能和已有的服务重名,也要和上面修改 /etc/inetd.conf 那行的第一项一致。 5、添加可以使用 CVS 服务的用户到 cvs 组: 以 root 身份修改 /etc/group,把需要使用 CVS 的用户名加到 cvs 组里,比如我想让用户 laser 和gumpwu 能够使用 CVS 服务,那么修改以后的 /etc/group 应该有下面这样一行: cvs:x:105:laser,gumpwu 在你的系统上GID可能不是105,没有关系。主要是要把laser和gumpwu用逗号分隔开写在最后一个冒号后面。当然,象RedHat等分发版有类似linuxconf这样的工具的话,用工具做这件事会更简单些。 6、重起inetd使修改生效: #killall -HUP inetd 如果使用的是 xinetd 的系统: # /etc/rc.d/init.d/xined restart 然后察看cvs服务器是否已经运行: [root@localhost /]# netstat -lnp|grep 2401 tcp 0 0 0.0.0.0:2401 0.0.0.0:* LISTEN 1041/xinetd 则说明cvs服务器已经运行。 二、管理CVS服务器
服务器可以用了,现在大家最关心的就是如何管理服务器,比如,我想让一些人有读和/或写 CVS 仓库的权限,但是不想给它系统权限怎么办呢? 不难,在 cvs 管理员用户(在我这里是 cvsroot 用户)的家目录里有一个 CVSROOT 目录,这个目录里有三个配置文件,passwd, readers, writers,我们可以通过设置这三个文件来配置 CVS 服务器,下面分别介绍这几个文件的作用: passwd:cvs 用户的用户列表文件,它的格式很象 shadow 文件: {cvs 用户名}:[加密的口令]:[等效系统用户名] 如果你希望一个用户只是 cvs 用户,而不是系统用户,那么你就要设置这个文件,刚刚安装完之后这个文件可能不存在,你需要以 cvs 管理员用户手工创建,当然要按照上面格式,第二个字段是该用户的加密口令,就是用 crypt (3)加密的,你可以自己写一个程序来做加密,也可以用我介绍的偷懒的方法:先创建一个系统用户,名字和 cvs 用户一样,口令就是准备给它的 cvs 用户口令,创建完之后从 /etc/shadow 把该用户第二个字段拷贝过来,然后再把这个用户删除。这个方法对付数量少的用户比较方便,人一多就不合适了,而且还有冲突条件(race condition)的安全隐患,还要 root 权限,实在不怎么样。不过权益之计而已。写一个小程序并不难,可以到 linuxforum 的编程版搜索一下,有个朋友已经写了一个贴在上面了。 第三个字段就是等效系统用户名,实际上就是赋与一个 cvs 用户一个等效的系统用户的权限,看下面的例子你就明白它的功能了。 readers:有 cvs 读权限的用户列表文件。就是一个一维列表。在这个文件中的用户对 cvs只有读权限。 writers:有 cvs 写权限的用户的列表文件。和 readers 一样,是一个一维列表。在这个文件中的用户对 cvs 有写权限。 上面三个文件在缺省安装的时候可能都不存在,需要我们自己创建,好吧,现在还是让我们用一个例子来教学吧。假设我们有下面几个用户需要使用 cvs: laser, gumpwu, henry, betty, anonymous。 其中 laser 和 gumpwu 是系统用户,而 henry, betty, anonymous 我们都不想给系统用户权限,并且 betty 和 anonymous 都是只读用户,而且 anonymous 更是连口令都没有。那么好,我们先做一些准备工作,先创建一个 cvspub 用户,这个用户的责任是代表所有非系统用户的 cvs 用户读写 cvs 仓库。 #adduser ... 然后编辑 /etc/group,令 cvspub 用户在 cvs 组里,同时把其它有系统用户权限的用户加到 cvs 组里。(见上文) 然后编辑 cvs 管理员家目录里 CVSROOT/passwd 文件,加入下面几行: laser:$xxefajfka;faffa33:cvspub gumpwu:$ajfaal;323r0ofeeanv:cvspub henry:$fajkdpaieje:cvspub betty:fjkal;ffjieinfn/:cvspub anonymous::cvspub 注意:上面的第二个字段(分隔符为 :)是密文口令,你要用程序或者用我的土办法生成。 编辑 readers 文件,加入下面几行: anonymous betty 编辑 writers 文件,加入下面几行: laser gumpwu henry 注意:writers中的用户不能在readers中,要不然不能上传更新文件。 对于使用CVS的用户要修改它的环境变量,例如laser用户的环境变量,打开/home/laser(laser的宿主目录)下的.bash_profile文件,加入
CVSROOT=/home/cvsroot export CVSROOT 用laser登陆就可以建立CVS项目,如果要root使用,可以修改/etc/profile文件。 现在我们各项都设置好了,那么怎么用呢,我在这里写一个最简单的(估计也是最常用的)命令介绍:
首先,建立一个新的CVS项目,一般我们都已经有一些项目文件了,这样我们可以用下面步骤生成一个新的CVS项目: 进入到你的已有项目的目录,比如叫 cvstest: $cd cvstest 运行命令: $cvs import -m "this is a cvstest project" cvstest v_0_0_1 start 说明:import 是cvs的命令之一,表示向cvs仓库输入项目文件。 -m参数后面的字串是描述文本,随便写些有意义的东西,如果不加 -m 参数,那么cvs会自动运行一个编辑器(一般是vi,但是可以通过修改环境变量EDITOR来改成你喜欢用的编辑器。)让你输入信息,cvstest 是项目名称(实际上是仓库名,在CVS服务器上会存储在以这个名字命名的仓库里。) v_0_0_1是这个分支的总标记。没啥用(或曰不常用。) start 是每次 import 标识文件的输入层次的标记,没啥用。 这样我们就建立了一个CVS仓库了。 建立CVS仓库的文件夹应该是“干净”的文件夹,即只包括源码文件和描述的文件加,而不应该包括编译过的文件代码等! 三、使用CVS winCVS是一个很好的CVS客户端软件,在http://cnpack.cosoft.org.cn/down/wincvsdailyguide.pdf可以下载到这个软件的使用手册。这里不在赘述了。 四、用CVS管理项目 本人正在一加公司从事该公司ERP项目的开发,在没有使用CVS的时候,多次出现了由于不同的开发人员修改同一程序,而导致程序错误,解决版本控制问题迫在眉睫。 由于这个项目采用Linux平台下JAVA开发,使用的开发工具Jbulider是支持CVS进行项目管理的,作为主程序员,我决定采用CVS进行版本控制,首先参照上文在Linux服务器上建立了CVS服务,然后我把我本地的工程文件传至服务器。 例如:我的工程文件在F:\ERP下,我把ERP下的erp.jpx文件、defaultroot文件夹和src文件夹上传至服务器/usr/local/erp下,然后登陆Linux服务器,登陆的用户是CVS的用户,其环境变量要正确(我的用户名为admin) #cd /usr/local/erp #cvs import -m "this is a ERP project" erp v_0_0_1 start 这样名为erp的CVS仓库就建立了。 之后开发小组的成员可以用winCVS把该项目下载到本地: 打开winCVS 点击工具栏Create -> Create a new repository... 弹出窗口 在Grenral中 Enter the CVSROOT填写admin@192.168.1.9:/home/cvsroot 其中admin是cvs的用户,在本例中admin也是linux的系统用户,192.168.1.9是服务器的地址,/home/cvsroot是CVS的主目录,参考上文。 Authentication中选择"passwd file on the cvs server" Use version中选择cvs 1.10 (Standard) 其它项默认即可。 确认后,点工具栏Admin --> Login... 会提示输入密码,输入密码后,看看winCvs的状态栏。如果提示 *****CVS exited normally with code 0***** 表示登录正常。 点击工具栏Create --> Checkout module...弹出对话框,其中的Checkout settings项中 Enter the module name and path on the server 填写erp,即我们建立的名为erp的CVS仓库 Local folder to checkout to 选择要下载到本地的目录,我选了F:\myerp 其它项目可以默认,确定后就可以下载到本地了,在F:\myerp\下会有一个erp文件夹,其文件结构和F:\erp下的文件结构是一样的。 用Jbulider打开F:\myerp\erp\下的erp.jpx文件,这个工程文件就可以使用了。 在Jbuilder的工具栏Team --> Select Project VCS 弹出对话框,选择CVS 对于你要进行修改的文件,在Project View中点中该文件,然后点右键,探出快捷菜单,选择CVS --> CVS Edit "xxxx.java(文件名)" 第一次使用可能会提示CVS服务器的密码。 在修改之前还要选择CVS --> Update "xxxx.java(文件名)" 修改之后选择CVS --> Commit "xxxx.java(文件名)" 这样,修改的文件就保存到CVS服务器了,Update的目的是下载、比较文件。每次在修改之前都Update,保持最新版本。 CVS在项目管理使用中确实起到了良好的效果,仔细研究CVS的命令,可以更好的发挥CVS在版本控制上的能力。 我的QQ是20896,欢迎大家来交流,也可以到我的论坛上讨论 http://www.laoer.com 参考资料:《CVS 简单教程》 作者:何伟平 《CVS服务器快速指南》 作者:何伟平 12:02 | 固定链接 | 评论 (0) | 引用通告 (0) | 记录它 | CVS 固定链接 关闭 http://spaces.msn.com/members/spire/Blog/cns!1plakp3AigUhLG_JZqIujcRw!317.entry CVS 使用简介 CVS是RCS的前端工具,它是用于多用户并行开发的版本控制工具,它的最大特点 是使用了“copy-modify-merge”机制而不是“lock-modify-unlock”。通过使用 CVS生成一个存储文件的仓库(repository),存储在仓库中的每个目录叫做模块 (module),在修改时将相应的模块检出到工作目录中(working directory)生 成对应的目录,所有的修改都在工作目录中完成,修改完成后再提交到仓库中生 成新的版本号,并加以保存。 1. CVS初始化 ------------- (1) 创建CVSROOT根目录 编辑有关的环境变量,加入CVSROOT的定义(比如在 /etc/bashrc 文件中加入下 面两行): CVSROOT=/usr/local/cvsroot export CVSROOT 然后在相应位置开始创建CVSROOT $cd /usr/local/ $mkdir cvsroot $cvs –d /usr/local/cvsroot init 这时就会产生/usr/local/cvsroot/CVSROOT 目录,这下面放着有关CVS的配置文 件。同时/usr/local/cvsroot/也作为文件仓库存放所有的文件。 (2) 创建开发项目 如果从头开始一个新的项目,就需要创建一个单独的目录,并把所有要使用的文 件做一个有效的组织。而如果在开始使用源文件的目录之前就有了,则只需进入 该目录就行了。 $cd /work/tang $ls cvstest . .. c/ $cd cvstest 然后,就可以输入源文件目录: $cvs import –m “Create Source Dir” cvstest/c tang cvstest 这样会生成 $CVSROOT/cvstest/c 目录。 其中 -m 用来指定注释信息,如果后面 在命令行不指定注释信息,则会启动缺省编辑器(vi)要求输入注释信息。 tan g, cvstest分别标识了厂商和发行标识。 注意,使用import命令会把当前目录下的所有文件和目录(包括子目录)引入到 文件仓库中指定模块(目录)下。 2. 命令简介 ------------- (1) 检出源文件 cvs checkout [-r rev][-D date][-d dir][-j merg1] [-j merg2] modules -r 检出指定版本的模块 -D 检出指定日期的模块 -d 检出指定目录而不是模块 -j 合并当前版本和指定版本 使用下面的命令会检出刚才生成的模块,并在当前目录下生成与文件仓库中完全 一样的目录结构: $cvs checkout cvstest/c 对于目录结构比较复杂的模块可以在 $CVSROOT/CVSROOT/modules中加以指定: 1) $cvs checkout CVSROOT/modules 2) 在modules文件中加入下面一行: SOURCE cvstest/c 3) 然后执行: $cvs commit –m “Add SOURCE”
以后就可以使用下面的命令在当前路径下生成 cvstest/c 目录 $cvs checkout SOURCE 在当前路径下生成的这个目录就被称为工作目录,对源文件的所有修改都应该在 这个目录下完成,而绝对不允许去改动在 文件仓库中$CVSROOT 目录下的文件。 (2) 删除、增加、重命名文件和目录 cvs add [-k kflags][-m message] files... -k 指定以后该文件的缺省检出目录 -m 对文件的描述 上述命令会加入一个新的文件到文件仓库里,但直到使用了提交命令它才会真正 更新文件仓库。 cvs remove [options] files 上述命令会从文件仓库中删除文件,但也要到提交之后才有作用。 例1:增加文件 $cvs checkout SOURCE $cd cvstest/c $touch test.c $cvs add test.c $cvs commit –m “add test.c” 例2:删除文件 $cvs checkout SOURCE $cd cvstest/c $rm test.c $cvs remove test.c 使用 –f 选项能上面两步合做一步。 $cvs remove –f test.c 如果在提交之前想恢复刚才删除的文件,可以如下: $cvs add test.c 如果只执行了第一步删除(rm),则可以用下面的方法恢复: $cvs update test.c 对于重命名的文件,可以先删除再添加。 对于目录的修改(重命名),可能需要修改cvs 管理文件,一般应该遵循以下步 骤: 1) 确认所有有关的修改都已经提交; 2) 进入文件仓库中要修改的模块目录,对相应的目录进行修改(重命名或删除) $cd $CVSROOT/modules $mv old_dir new_dir 3) 如果有必要,修改管理文件,比如modules 文件 如果要删除目录,则应该先对目录中每个文件都进行了删除(包括使用cvs remo ve )处理之后再执行上面的第2步。 (3) 提交源文件 cvs commit [-Rl][-m mesg] files -R 连子目录一起提交 -l 只提交本地目录(不提交子目录) -m 注释信息 在检出源文件之后,在工作目录中对源文件进行的所有修改都必须在提交之后才 能对文件仓库中的源文件起作用,并且新的文件才能够被分配一个新的版本号。 (4) 释放工作目录 cvs release –d SOURCE 这个命令会删除工作目录 cvstest/c (建议在提交了修改的模块后执行这一步) , 它比使用 rm –rf cvstest 要好。 3. 多用户开发 --------------- 在多用户的情况下,如果不同用户修改的是同一个文件的不同部分,则使用下面 的命令就能进行版本合并(把检出的文件与当前的最新版本合并): $cvs update (1) 冲突解决 在有多个用户对同一个文件进行修改时,如果修改了其中的相同部分,而修改后 的内容如果有不同的话,出现冲突是不可避免的。如果在CVS 文件仓库中有一个 文件 test.c ,它的版本是 1.4, 用户A 先检出该文件进行修改,而稍后有用户 B 检出该文件进行修改,并提前提交成 1.5, 而在用户A再提交时就会出现冲突 (如果文件内容不同的话),这时CVS会提示需要手工解决。 文件仓库中的版本1.4: #include <stdio.h> main() { int i; for(i = 0; i < 100; i++) printf(“Count: %d\n”, i); } 用户B 1.5: #include <stdio.h> main() { int i; for(i = 0; i < 10; i++) printf(“Count: %d\n”, i); printf(“Over\n”); } 用户A : #include <stdio.h> main() { int i; for(i = 0; i < 50; i++) printf(“Count: %d\n”, i); return; } 提交时会提示有冲突,需要手工编辑,这时运行了$cvs update 之后再编辑test .c, 会看到: #include <stdio.h> main() { int i; <<<<<<< test.c for(i = 0; i < 50; i++) ======= for(i = 0; i < 10; i++) >>>>>>> 1.5 printf("Count: %d\n", i); <<<<<<< test.c return; ======= printf("Over\n"); >>>>>>> 1.5 } (2) 文件版本管理 cvs log [-lR][-r rev][-d date][-w login][files…] -l 不处理子目录 -R 对子目录做同样处理 -r 指定版本号 -d 指定时间 -w 指定登录名 使用上面的命令可以参看当前模块或指定文件的所有历史版本信息。 cvs annotate [-lR][-r rev|-D date] files -l 不处理子目录 -R 对子目录做同样处理 -r 指定版本号 使用上面的命令可以参看指定文件(检出之后)的所有修改信息。 例:$cvs annotate cvstest/c/test.c 输出: 版本 修改人 修改时间 源代码 1.1 (tang 18-Jan-00): #include <stdio.h> 1.1 (tang 18-Jan-00): #include <string.h> 1.1 (tang 18-Jan-00): 1.1 (tang 18-Jan-00): main() 1.1 (tang 18-Jan-00): { 1.1 (tang 18-Jan-00): int i = 0 ; 1.1 (tang 18-Jan-00): 1.1 (tang 18-Jan-00): for(i = 0; i < 20; i++) 1.1 (tang 18-Jan-00): printf("Count: %d\n", i); 1.1 (tang 18-Jan-00): 1.3 (tang 18-Jan-00): printf("222222\n"); 1.4 (tang 18-Jan-00): printf("333333\n"); 1.1 (tang 18-Jan-00): } 使用下面的命令可以生成相对于一个指定主版本的分支版本: cvs rtag –b –r rev_root rev_branch file_name -b 指定生成一个分支版本 -r 指定该分支的主干节点版本号 rev_root 主干版本号 rev_branch 分支版本号 file_name 指定文件,使用“.”表示当前目录下所有文件 使用上面的命令可以生成一个对应版本号的分支版本,由于CVS 版本号是用数字 表示的,而且在同一个模块下不同文件的版本完全可能是不同的,所以使用标识 会更方便。 例: $cvs rtag –b –r 1.2 tlb-1 SOURCE 以后要访问该分支版本,可以使用“-r” 选项 $cvs checkout –r tlb-1 SOURCE 从当前检出的版本切换到一个分支版本: $cvs update –r tlb-1 SOURCE 使用下面的命令可以看版本信息: cvs status [–vlR] files -v 显示所有信息 -l 不显示子目录信息 -R 显示子目录信息 cvs update –j rev module 把当前所做的修改与指定版本的文件进行合并。 主干 1.1 ? 1.2 ? 1.3 ? 1.4 ? 1.5 ? 1.6 ↓ 分支tlb-1 ? 1.2.2.1 ? 1.2.2.2 ? 1.2.2.3 如果要合并分支tlb-1上的版本: $cvs update –j 1.2.2.3 –j tlb-1 test.c 其中1.2.2.3可以通过tag命令生成一个容易记忆的标识。 如果要合并分支tlb-1到主干上1.2 : $cvs update –j tlb-1 test.c 如果要合并主干上的不同版本(注意顺序很重要,同时在指定版本之间的所有修 改将被丢弃): $cvs update –j 1.5 –j 1.2 test.c 如果在不同版本之间模块的文件有增减,则可以: $cvs update –A $cvs updata –jbranch_name 4. 在远程机器上使用CVS ---------------------- 通过网络使用CVS 有很多种方式,但在这里只介绍比较简单的一种:通过rsh 执 行cvs 命令。 1) 在远程机器的.rhosts中加入对本地机的访问许可: tom tang 2) 使用下面的命令检出模块ESMSTRG $cvs –d :ext:tang@esmpro:/work/cvsroot checkout SOURCE 其中, ext 指明了连接方式为 rsh, tang 指明了本地用户, esmpro 指明了远 地主机,/work/cvsroot 指明了在远地主机上的$CVSROOT路径,可以在本地设置 CVS_SERVER环境变量指明这个目录。 5. 参看帮助 ---------------- cvs –H command 可以参看指定命令的帮助信息。 12:02 | 固定链接 | 评论 (0) | 引用通告 (0) | 记录它 | CVS 固定链接 关闭 http://spaces.msn.com/members/spire/Blog/cns!1plakp3AigUhLG_JZqIujcRw!316.entry CVS:版本控制的开放标准 撰写/Jim Blandy 翻译/马维达 译者按:CVS,即并发版本系统(Concurrent Versions System),是占统治地位的版本控制系统。它具有开放源码、“网络透明”(Network-transparent)的特点,从个体开发者到大型的分布式开发队伍,都可以使用它来进行项目的版本控制: l 它的客户/服务器访问方法使得开发者可从任何有Internet连接的地方访问最新的代码。
l 它的非专有(unreserved)check-out版本控制模型避免了使用独占check-out模型时常见的人为冲突。 l 在大多数平台上都有它的客户工具。 许多流行的开放源码项目,像Mozilla、GIMP、XEMacs、KDE和GNOME,都使用了CVS。 (录自http://www.cvshome.org)
CVS的用途是什么? CVS根据一系列改动来维护源树的历史。它通过改动时间和改动者的用户名来记录每次改动。通常,改动者还提供一些文本,描述为什么要做出改动。根据这些信息,CVS可以帮助开发者回答这样的问题:
l 是谁做出的特定改动?
l 他们是什么时候做出改动的? l 他们为什么要做出改动? l 他们同时还做出了哪些其他改动? 怎样使用CVS?
在讨论太多含混的术语和概念之前,让我们先看一看基本的CVS命令。
设置你的仓库(repository)
CVS将每个人对特定项目的改动记录在称为仓库的目录树中。在使用CVS之前,你需要将CVSROOT环境变量设置为仓库的路径。无论是谁负责你的项目的配置管理,他们都必须知道这是什么;或许他们已经在某处为CVSROOT作了全局定义。
在任何情况下,在我们的系统上CVS仓库都是”/u/src/master”。这样,如果你的shell是csh或它的派生版本,你需要输入命令: setenv CVSROOT /u/src/master
如果你的shell是Bash或某种其他的Bourne shell变种,则输入:
CVSROOT=/u/src/master export CVSROOT
如果你忘了这样做,CVS将在你试图使用它时抱怨: $ cvs checkout httpc cvs checkout: No CVSROOT specified! Please use the `-d' option cvs [checkout aborted]: or set the CVSROOT environment variable.
检出(check out)工作目录 CVS并非是在普通的目录树上工作;你需要在CVS为你创建的目录中工作。就像你在把一本书从图书馆带回家阅读之前将它检出一样,你使用cvs checkout命令来在对一个目录树进行操作之前从CVS那里获取它。例如,假设你目前工作的项目名为httpc,一个平常的HTTP客户:
$ 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使用它来记录该目录中的每个文件的额外信息,以帮助确定你把文件检出后都对它做了什么改动。
对文件做出改动
一旦CVS创建了工作目录树,你可以通过平常的方式来编辑、编译和测试该目录中所包含的文件——它们就只是文件而已。
例如,假设我们尝试编译我们刚刚检出的包: $ make gcc -g -Wall -lnsl -lsocket httpc.c -o httpc httpc.c: In function `tcp_connection': httpc.c:48: warning: passing arg 2 of `connect' from incompatible pointer type
看起来“httpc.c”还没有被移植到这个操作系统。我们需要转换connect的一个参数。为修正此问题,48行必须从: if (connect (sock, &name, sizeof (name)) >= 0)
改变为
if (connect (sock, (struct sockaddr *) &name, sizeof (name)) >= 0)
现在它应该可以编译了:
$ make gcc -g -Wall -lnsl -lsocket httpc.c -o httpc $ httpc GET http://www.cyclic.com ... HTML text for Cyclic Software's home page follows ...
合并你的改动 因为每个开发者使用他们自己的工作目录,你对你的工作目录所做的改动并不会自动地对你的开发组中的其他开发者变得可见。不到你准备就绪,CVS不会公布你的改动。当你完成了对你的改动的测试时,你必须将它们提交(commit)给仓库,以使它们能为组的其他成员所用。我们将在下面描述cvs commit命令。
但是,如果另一个开发者已经改动了你所改动的同一文件、或同一行,该怎么办呢?谁的改动应该成功?一般而言,要自动回答此问题是不可能的;CVS无疑没有能力来作出那样的判断。 因而,在你提交你的改动之前,CVS要求你的源与其他组成员提交的任何改动保持同步。cvs update命令负责照管这个: $ 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将他们的改动与你的合并在了一起,并且没有发现任何文本上的冲突。数字“1.6”和“1.7”是修订版号(revision number),用于标识文件的历史中的特定点。注意CVS只是将改动合并进你的工作拷贝中;仓库和其他开发者的工作目录没有受到打扰。要由你来测试合并的文本,并确保它是有效的。 M httpc.c “M file”形式的行意味着该file已被你修改(Modified),并含有对其他开发者还不可见的改动。这些是你需要提交的改动。这样,“httpc.c”现在同时含有你的修改和其他用户的修改。 因为CVS已将其他人的改动合并进你的源中,最好确定程序还能工作:
$ make gcc -g -Wall -Wmissing-prototypes -lnsl -lsocket httpc.c -o httpc $ httpc GET http://www.cyclic.com ... HTML text for Cyclic Software's home page follows ...
提交你的改动 现在你已使你的源跟上了组的其余成员那里的最新情况、并对它们做了测试,你可以提交你的改动给仓库、并使它们对组的其余成员成为可见的。唯一被你修改过的文件是“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”的改动合并进他们的工作目录中。
检查改动
现在你可能很好奇,其他开发者都对“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”、“1.3.2.2”,甚或“1.3.2.2.4.5”。缺省地,修订版1.1是文件的第一版。每个后继修订版通过将最右边的数字加一来获得一个新号。 date: 1996/10/31 20:11:14; author: jimb; ... 这一行给出改动日期,以及提交它的人的用户名;行的余下部分不怎么有趣。 (tcp_connection): Cast ... 这是(相当明显)对改动进行描述的日志条目。 cvs log命令可以通过日期范围、或修订版号来选择日志条目;详细资料见CVS手册(manual)。
如果你实际上想要查看正在讨论的改动,你可以使用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.6 -r 1.8将显示Fred的改动和你的最近的改动。(通过向后指定修订版:-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使用类似的大块格式来描述删除。
像Unix diff命令一样,来自cvs diff的输出通常被称为补丁(patch),因为传统上开发者已经使用该格式来发布错误修正或小的新特性。在适当地让人能理解的同时,补丁也含有足够的信息,能让一个程序来将该补丁描述的改动应用到未被修改的文本文件。事实上,给定补丁作为输入,Unix patch命令所做的正是这个。 增加和删除文件
CVS像对待其他改动一样对待文件创建和删除,它在文件的历史中记录这样的事件。也就是说,CVS记录目录以及它们所包含的文件的历史。
CVS没有假定新创建的文件应被置于它的控制之下;在许多情况下这样的假定会出错。例如,我们不需要记录对目标文件和可执行文件的改动,因为它们的内容总是可以重新从源文件创建(我们希望如此)。相反,如果你创建了一个新文件,cvs update会用“?”字符标记它,直到你告诉CVS你想要对它做什么。 要把文件增加到项目中,你必须先创建该文件,然后使用cvs add命令来为它做上增加标记。于是,下一次对cvs commit的调用会把该文件增加到仓库中。例如,这里演示你可以怎样将README文件增加到httpc项目中:
|