#////////////////////////////////////////////////////////////////////// # 使用CVS进行版本管理 # 2000-6-30 # CVS 1.10.7 # # # # Per Cederqvist et al 著 # 廖斌 译 # 感谢黄箐的输入和排版工作 # copyrigh: GPL # #////////////////////////////////////////////////////////////////////// 快捷内容索引 1. 概述.........................................................1 2. 代码仓库.....................................................7 3. 使用CVS开始一个项目..........................................29 4. 主干版本(Revision).........................................33 5. 分支与合并...................................................41 6. Recarsive behavior...........................................49 7. 增加,删除,更名文件和目录...................................51 8. 回顾开发历史.................................................57 9. 放置二进制文件...............................................59 10 多个开发者的同时工作........................................61 11 主干版本管理.............................................71 12 关键字替代................................................73 13 跟踪第三方代码...............................................77 14 你的系统如何同CVS交互........................................81 15 特殊文件.....................................................83 16 附录 1) CVS命令导向..................................................85 2) CVS命令快速参考............................................115 3) 管理文件的参考习册.........................................127 4) 影响CVS的所有环境变量......................................141 5) CVS各版本之间的兼容性......................................143 6) 遇到的问题.................................................145 7) Credits....................................................153 8) 对CVS和这本手册的总是处理..................................155 目录 ...........................................................157 1 概论 这一章为从未用过CVS的人写的,也许以前也从未用过任何版本控制工具。 1.1 什么是CVS? CVS是个版本控制系统,使用它你可以记录你原代码文件的历史。 例如,当软件修改时有时会产生问题(*bugs这里被译为问题),并且你可能在做这 次修改后很长时间不会发现这些问题。使用CVS,你可以容易地回顾老的代码版本去 发现哪一次的修改导致这些问题。有时候这样会非常有帮助。 你可能会保留你每一次的代码版本,这可能会浪费你很多的代码空间。CVS使用一 种聪明的办法保存你的多个版本在一个文件中。它仅仅保留版本间的不同内容。如果 你是一个项目中的一组成员之一,CVS也能够帮助你。除非你特别仔细,你很容易覆盖其 他人的工 作。一些编辑器,例如GNUEmacs,试图去判定一个文件是否被两人同时修改。 不幸的是,如果一个人使用其它的编辑器时,这个安全方式将不再有效。CVS使用让不同 开发者独立工作的方式解决了这个问题。每一个开发者的工作都在他自己的目录内,并且 CVS将 在每个开发者的工作完成后进行合并工作。 CVS是由Dick Grune作为Shell脚本的一个分支而创建的,1986年10月,在它的第6个发行卷 时,它被投递到新闻组comp.soures.unix。然而现在的CVS冲突算法中没有任何代码是从这 些脚本中来的。 在1989年3月,Brian Berlinor设计并编写了CVS的代码。Jett.Polk在以后帮助 Brian 完成了CVS 模型的设计和商业版本支持。 你可以通过不同的方式得到CVS,包括在Internet上自由的下载。如果你想下载CVS和其它CVS 文章以得到更多的信息,请看: http://www.cyxlic.com http://www.loria.fr/~molli/cvs-index.html. 有一个叫 info-cvs 的邮件列表是有关CVS的内容。订阅或取消订阅这个列表请发邮件到 info-cvs-request@gnu.org 如果你更喜欢Usenet新闻组,CVS的讨论组为 Comp.software.confg.mgmt. 在将来也许会建一个 Comp.software.confg.mgmt.cvs 的新闻组, 但也许这会是Comp.software.confg.mgmt在有太多讨论之后的事情吧。 你也许想订阅bug-cvs的邮件列表,这在附录H[BUGS]中有更多的信息。订阅它请发Email到 bug-cvs-reqnest@gnu.org. 1.2 CVS不能做的事 (未译) 2 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"命令的一个完全的描述。 ----CVS用后感,欢迎大家来补充----- CVS希望提供给用户的功能是尽可能完整且方便的记录项目的开发轨迹, 我只是个人使用了一段时间的CVS, 所以以下只是单用户的感受: 1. 什么时候需要commit in? 使用CVS的目的是为了记录项目开发的功能性添加, 删除, 更新的轨迹. 即我们只有在_完成_了对项目的功能性的修改后才 需要提交我们所做的修改. 而不是说我把修改提交到CVS库里面是因为怕丢失我今天或近几天所做的修改(实际上, 只要你的 硬件不是水货中的极品, 在GNU系统上进行开发是完全不用为此*心的). 举个例子: 我想为项目1修改功能X(包含了添加和删除). 期间需要修改文件A的函数foo(), 文件B的函数bar(). 我们不应该修改完foo()后就提交一次, 修改bar()后又提交一次. 而是应该等我们修改完foo() & bar(), make, debug, 觉得 对X的功能修改满意后再commit in. 可能我在提交的时候会忘记对哪些文件做了哪些修改了, 但只要记得使用cvs status和 cvs diff, 问题应该容易解决. 2. commit log应该怎么写? 初学者在commit in写log的时候往往是在项目根目录下进行commit in, 然后写本次修改的记录. 这样就会造成修改过的文件 具有相同的日志记录, 从而带来两点不利之处: 没有针对性且日志记录很多, 不便于查看. 所以我建议的方法是针对不同的修改, 写单独的log. 换句话说, 就是对单个文件或具有类似修改的某组文件分别提交, 写不同的日志. 在上面的例子中, 假如我们为功能X修改了文件A之函数foo(), A_1之foo(), A_2之foo(), A_3之foo(), B之bar(), 我们提交的方法应该是cvs ci A A_1 A_2 A_3 && cvs ci B. 如此一来, 我们将很难对功能X的修改进行全局性的把握, 因为文件A, A_1, B的revision编号很有可能是不一样的. 解决这个问题的方法可以是使用ChangLog文件, GNU系统上面很多工具可以很方便的将CVS文件之log针对性的提取到ChangLog 文档里面, 例如我们在上面可以使用cvs ci A A_1 A_2 A_3 && cvs ci B后再将刚刚输入的所有log信息复制到ChangLog内. 所以通常情况下提交的最后一步是cvs ci -m"" ChangLog^-^ 3. 我是不是可以取出我的任意一次commit in? 是的, 曾经有段时间一直认为只能取出打了标记的历史版本, 其实CVS为项目记录了每次commit in的动作, 用户可以通过 revision内部编号取出任意某个文件的任意历史版本(e.g. cvs co -r1.3 src/server.c). 在用户想放弃对某些文件至上提交以来的所有修改时, 再提取一次上次版本覆盖当前工作文件就行了, 不需要用户额外去备份. 4. 我是不是可以修改CVS库中以前的日志记录? 是的, 可以通过cvs admin -m 1.2:"`cat newlog.txt`" server.c来对以前的log message进行更新. (这个命令找得我好苦!) 最后一点, 建议使用GNU Emacs进行项目开发却还在终端下使用CVS的用户看看VC(version control NOT Vi$ual C++)和PCL-CVS的使用手册, enjoy! |