CVS基本使用方法

CVS基本使用方法
CVS基本使用方法

CVS 使用手册

(仅供内部交流使用)

一、基本概念

CVS是一个可以多人协同工作的文件版本管理工具,常用于源代码的管理,也可以用于其他基于文本结构的文件的历史版本管理。

CVS是基于文件进行管理,并不保存目录的变更信息。保存每一个文件的每一次修改的信息,能够方便的查找到每一个文件的不同时刻的版本的内容。可以方便查找旧有版本进行比较或者回退。通过TAG记录版本的统一基准。通过分支支持多版本的开发与合并。对第三方代码的跟踪提供了支持。

CVS使用中心源代码库和个人工作空间的方式。使用CVS进行管理的项目有一个中心的源代码库(Repository),然后开发人员checkout到本地的个人工作空间中进行修改,修改完毕之后将新的版本commit到中心源代码库中,其他开发人员可以将这个新的版本update 到他们本地的个人工作空间中。

CVS在多人协同上没有采用加锁机制(lock-modify-unlock),而是使用冲突合并机制(copy-modify-merge)。所有人可以同时修改同一个文件,后提交的人需要将别人已提交的更新合并到本地,解决不同修改之间的冲突,再提交新版本。

CVS所管理的文件,每一个版本都有一个唯一的版本号,通常使用1.1、1.2的方式递增。而分支的版本号则增加两位,如1.3.1.4、1.3.1.5等。为了方便记忆,可以通过TAG的操作,为某一时刻的不同文件添加同一个版本名称。另外,可以通过某个特定的时间点的方式来指定所需的版本。

二、代码修改

2.1. 准备工作

CVS可以通过本地路径、pserver、ssh tunnel的方式来访问,这里主要讨论pserver方式。

为了避免每次输入cvs命令都加–d :pserver:user@host:/path 的参数,可以设置CVSROOT变量,这个设置可以写入~/.bashrc 或~/.bash_profile 中,在执行CVS其他命令前,要先执行一下cvs login命令:

$ export CVSROOT=:pserver:username@192.168.100.2:/cvsroot/proj

$ cvs login

(另外,CVS服务器建立提供服务前,首先要初始化,使用$ cvs init 命令(略)。)

2.2. 创建新项目

通过CVS来管理项目时,首先要使用cvs import命令将原始版本放入CVS中。创建新项目时要注意下列事项:

(1)在原始版本所在的目录里面执行import命令。import前先去掉一些可以从其他文件中生成的中间文件。

(2)import后要备份原始版本,然后checkout新版本,以后所有的修改都在checkout 的版本上进行,import动作并不会把当前目录转换成checkout的版本。

(3)import后要将checkout的版本和原始版本做一下diff命令(exclude CVS目录),检查是否有不恰当ignore的内容。小心一些以点开头的隐藏文件。

(4)检查是否有二进制文件被误作文本文件对待,使用cvs admin命令纠正。

$ pwd

/work/myproj

$ cvs import –m “why import this” myproj seals v_old_1_2_3

$ cd .. ; mv myproj myproj.ori

$ cvs checkout myproj

$ diff –r myproj.ori myproj | more

$ tar czvf myproj.ori.bak.xxxxxx.tar.gz myproj.ori ; rm –rf myproj.ori $ cvs admin –kb mysub/mysubfile.dat

import命令的两个最后参数分别表示vendor和release_tag。一般vendor使用seals,release_tag使用init_xxxxxxxxxx即可。如果是使用第三方代码,则需要小心使用,并且使用readme.txt文件记录好每次import和merge信息,具体见【跟踪第三方源代码】一节。

import后的项目不能通过cvs命令删除,如果发现import出错,需要删除后重新import,则删除动作需要由管理员直接在代码库中进行删除。

2.3. 查看源代码

使用一个已经存在的CVS项目时,首先将其checkout到本地。根据Seals团队习惯,在CVS项目的根目录中,有PROG_VERSION.def文件存放项目的版本,有readme.txt文件存放介绍内容,有Changelog.txt文件存放版本的重大更新,习惯在Changelog.txt文件的开头写上此CVS项目所依赖到的其他项目。

$ cvs checkout myproj

$ cat PROG_VERSION.def ; cat Changelog.txt ; cat readme*

开发过程中,应该多执行一下update操作,及时获取其他开发者的更新,以减少可能存在的开发冲突。update的时候,文件名左边的字母的意思为:

“M”表示本地有修改未commit,

“P”“U”表示有其他人修改的新版本,已经更新到本地,

“C”本地有修改,并且与其他开发者者的改动有冲突。

update常用参数:

-d表示将新增加的目录也更新到本地(缺省是不更新);

-P表示删除本地的空目录。

-A 命令表示去掉粘着的tag(具体见【TAG和分支】一章)

$ cvs update –d –P

cvs update命令可以使用–r 参数更新到指定TAG和分支上,然后这个TAG和分支会附在这些文件上(cvs status命令可以看到),后续的update将会保持这个TAG或分支不变,不会更新到最新代码,直到update时使用–A 参数为止,参考【TAG和分支】一章。

使用cvs diff命令可以检查当前版本与CVS中的版本的差异,也可以比较指定两个版本之间的差异:

$ cvs diff –r 1.44 myfile.cpp

$ cvs diff –D “3 hours ago” myfile.cpp

$ cvs diff –r 1.42 –r 1.43 myfile.cpp

除了比较文件版本的差异,使用CVS还可以查看各个文件的具体修改日志,当前文件的状态,以及文件的各行的修改情况:

$ cvs log myfile.cpp

$ cvs status –v myfile.cpp

$ cvs annotate myfile.cpp > tmpfile.txt

另外还可以使用第三方工具可以生成统计信息和分析信息,方便查看。

2.4. 修改源代码

源代码修改时的一些注意事项:

(1)修改过程中,多执行一下cvs update操作,以便尽早发现冲突并解决。

(2)commit的代码必须是可以编译通过的,而且最好不要有会影响其他模块运行的重大功能倒退,以免影响其他模块的开发。

(3)在满足上述条件的同时,尽可能多commit,有阶段性成果就可以commit一下,以便其他开发人员尽早进行update,减少冲突。

(4)commit之前使用cvs update和cvs diff来检查一下需要提交的代码,commit 之后使用cvs update来检查提交的结果。特别要注意检查是否有新增加的文件

或者目录忘记使用cvs add添加到CVS中,这是非常容易遗漏的。

(5)commit的时候一定要写log信息。log信息应该简练,包含有效信息,同一任务的多个修改可以考虑使用同一个log信息,方便一些工具进行收集和汇总。

(6)CVS只是工具,不能代替团队的交流。团队开发过程中应该加强前期的讨论交流,减少后期的代码冲突合并。

$ cvs update –d -P

$ cvs diff –r 1.44 myfile.cpp

$ ./do_my_file_edit

$ rm nouse.h nouse.cpp

$ cvs remove nouse.h nouse.cpp (这两步可以合并成 cvs remove –f xxx)$ cvs add newfile.cpp newfile.h

$ cvs diff

$ cvs update –d –P

$ cvs commit

一般要避免commit错误的代码,commit前最好先diff。如果需要将部分代码退回到某个版本,要加强沟通,讨论后再执行。主要有下列两种方法:(取回前一个版本,但不更改服务器中数据,需要commit。)

$ cvs –Q update –p –r 1.3 hello.c > hello.c

或:

$ cvs update –j 1.4 –j 1.3 hello.c

cvs中增加目录直接cvs add,不需要commit,但目录里面的文件不会自动添加,需要手动逐个add后再commit。如果目录层次比较多,除了逐层add目录再add文件外,也可以使用import命令(参考【跟踪第三方源代码】一节)。

$ cvs import –m “new dir” myproj/newdir seals v_init_newdir

删除文件要先删除本地文件,再执行cvs remove命令,或者直接cvs remove –f来操作。删除目录直接删除里面的文件即可,目录本身不需要删除,然后update使用-P参数可以去除空目录。

CVS不支持文件和目录的改名操作。可以考虑使用先remove再add的方法,一方面比较麻烦,另一方面log信息不能延续。所以文件和目录命名时要谨慎。

2.5. 跟踪第三方源代码

如果要使用到第三方源代码,可以通过多次import然后merge来进行。此时需要注意import时候的后两个参数(其实前一个是branch标记,后一个是tag标记),第一个参数要

保持一致,一般使用厂商名,第二个参数一般使用该第三方源代码的版本号。

一般来说,如果不是使用到第三方代码,那么只会import一次,后两个参数随便也无所谓。另外,使用第三方源代码时,一般也不会同时跟踪两个分支,这里暂不讨论。

假设这样一个场景,我们使用了GNU的gcc的源代码,一方面对gcc的代码进行修改,另一方面又在跟进gcc代码的更新,具体过程如下:

首先我们import了gcc 3.4.0作为最原始的代码,然后在这些代码基础上进行修改:

$ tar gcc-3.4.0.tar.gz ; cd gcc-3.4.0

$ cvs import –m “gcc 3.4.0” myproj/gcc GNU v_gcc_3_4_0

$ cd .. ; cvs checkout myproj ; cd myproj ; ./edit_commit ;

$ cd gcc ; ./edit_commit ; cd .. ; cd ..

然后我们import了gcc 3.4.2的代码,并且将gcc 3.4.2的改动和我们的改动合并起来,合并之后的效果等价于我们直接在gcc 3.4.2的基础上进行改动。合并完之后,我们还可以在此基础上继续做新的改动:

$ tar gcc-3.4.2.tar.gz ; cd gcc-3.4.2

$ cvs import –m “gcc 3.4.2” myproj/gcc GNU v_gcc_3_4_2

$ cd .. ; mkdir tmp_aa ; cd tmp_aa

$ cvs checkout –j v_gcc_3_4_0 –j v_gcc_3_4_2 myproj/gcc

$ cd myproj/gcc ; ./solve_conflict_commit ; cd ../..

$ cd .. ; rm –rf tmp_aa

$ cd myproj ; cvs update –d –P ; ./edit_commit ;

$ cd gcc ; ./edit_commit ; cd .. ; cd ..

再来一个新的3.4.4版本的时候,也可以重复这个过程,先import,然后再合并,然后可以做新的修改:

$ tar gcc-3.4.4.tar.gz ; cd gcc-3.4.4

$ cvs import –m “gcc 3.4.4” myproj/gcc GNU v_gcc_3_4_4

$ cd .. ; mkdir tmp_bb ; cd tmp_bb

$ cvs checkout –j v_gcc_3_4_4 –j v_gcc_3_4_4 myproj/gcc

$ cd myproj/gcc ; ./solve_conflict_commit ; cd ../..

$ cd .. ; rm –rf tmp_bb

$ cd myproj ; cvs update –d –P ; ./edit_commit ;

$ cd gcc ; ./edit_commit ; cd .. ; cd ..

三、T AG和分支

3.1. TAG的使用

TAG的功能是保存一个各个文件的即时的同一时间的快照。不使用TAG也可以根据某个时间点获取快照,但这个时间点比较难以确定,而且TAG也可以赋予更多的意义。

TAG的使用一般有两种情况,一种情况是在发布RELEASE版本时,留一个快照记录,日后能够随时找回某个版本的代码,也可以方便做不同发布版本之间的比较。所有的RELEASE都从特定的TAG中checkout出来,使整个发布过程是可重复的。

另一种情况是进行比较大的改动前,或者merge分支前,做一个快照,以便进行修改的比较和必要时的版本回退。

一般来说,发行版的标记使用REL_xx_xx_xx 格式,分支本身使用branch_xx_xx_xx 个格式,分支上的标记使用REL_branch_xx_xx_xx_xx 的格式,其他一些标记使用小写字母加下划线,宁可稍微长一点也要说明具体标记的信息。当然,能够在readme.txt或者Changelog.txt中说明更好。TAG最好能够与根据开发进度和开发计划一起,事先规划好,打TAG之前要充分协商,防止打错。如果打错了,一般不删除TAG,而是建一个新TAG,同时调整说明TAG的意义。

制作TAG时可以使用rag命令对当前目录打标记,或者使用rtag命令,强制对整个项目做标记。但后者不会检查当前目录是否有未commit的内容,如果用tag命令而且加上–c 参数,就会先检查是否有本地文件已修改但未commit。不过从习惯上,根据Release Engneering流程,需要所有人都commit好代码,测试过才打tag的。即使自己tag时使用–c 检查,也不知道别人是否已经commit好。

$ cvs rtag REL_02_04_06 myproj

$ cvs update –r REL_02_04_06

$ cvs status xxxxxx.cpp

$ cvs update –A

在update或者checkout时候,可以使用–r 参数指定TAG。注意,这个tag会粘在这些文件上,后续的update或checkout会保持这个TAG,不更新到最新版本,直到调用cvs update –A 去掉这个粘着的TAG为止。特别小心如果只有一两个文件被update粘在某个TAG 上,那么很容易被忽视,此时普通的update并不会获取这些文件的最新版本,这种情况有时候也是有用的。

如果某个文件被粘在某个TAG上,那么这个文件是不能被commit的,除非使用update –A (或者update –r到某个分支??未测试过)去掉这个粘住的TAG。

所以从习惯上,最好不要在当前工作环境中使用update –r 来获取某个版本,而是在一个专门的release目录下,或者某个临时目录,获取指定的TAG的代码来进行编译发布,对于这些目录下的代码,不要做任何的修改。

3.2. 分支的使用

分支的使用主要的目的是并行开发。多个不同类型的改动可以同时commit但又不互相影响,同时可以方便的进行代码比较和合并。

一个典型的例子,项目A发布了1.2版本,正在开发1.3版本,此时1.2版本出现bug,但1.3版本又未完成。这时可以在1.2基础上建立一个新的分支,在上面进行1.2的bug的修正,修正过程中对1.2分支的改动并不会影响1.3的继续开发。1.2分支修改好后,除了升级发布的1.2外,还可以很方便的merge到1.3中,当然如果有代码冲突那是无法避免的,需要手动解决。到了1.3发布时,除了1.3计划的内容外,1.2上的bug的修补也在这个版本中了。

另一个例子是,如果要开发某个功能,开发过程中有一段时间不能正常运行,为了避免影响主干上的其他开发,可以在一个单独分支上进行,等到有阶段性效果时,再merge到主干上。如果不开分支,直接在自己工作目录上进行,等到有阶段性效果时再commit也是可以的,但这样中间过程就不能commit,不能利用CVS的好处。分支的另一个好处是万一试验失败,这个分支可以放弃,只要不merge就不会影响主干的正常运作。

使用分支,最重要是做好规划,明确分支的目标和版本发布计划,明确merge的方向。尽量不要使分支偏离主干太久,否则merge时代码冲突会很难修补。

创建分支时,使用带-b参数的cvs tag命令,创建分支时必须在某个tag基础上,否则会给merge带来麻烦。

创建分支后,可以使用update –r命令来将本地更新到并粘着在此分支上,之后所有的修改都将commit到此分支上。更好的方法,是每一个分支都通过checkout –r参数在不同的目录下进行开发,尽量不要用update –r将本地目录迁移到其他分支上,特别是本地目录有未提交的内容时,容易造成混乱。

对分支上的修改与主干上一样,但注意commit时写log的时候,CVS会明确告诉你当前commit的内容是粘在某个TAG(即分支)上的。

$ cvs rtag REL_02_04_06 myproj

$ cvs rtag –b –r REL_02_04_06 branch_02_04_xx myproj

$ cd .. ; mkdir branch_02_04_xx ; cd branch_02_04_xx

$ cvs checkout –r branch_02_04_xx myproj

$ cd myproj ; ./edit_commit

$ cvs update

$ cvs diff –r REL_02_04_06

在把分支merge到主干之前,要先将分支和主干上都打上相应的TAG,一方面一旦出现问题可以方便回退,另一方面也方便merge前后的比较,有助于解决merge时代码冲突。而且分支上的TAG也方便分支的多次merge到主干上。

$ cvs rtag –r branch_02_04_xx REL_branch_02_04_07 myproj

$ cd ../main_trunk/myproj (回到主干所在的目录)

$ cvs rtag before_merge_branch_02_04_07 myproj

$ cvs update –j REL_02_04_06 –j REL_branch_02_04_07

$ ./solve_conflict_commit

$ cvs rtag after_merge_branch_02_04_07

如果分支上又有新的改动,则只需要将新改动的部分merge回主干中:

$ cd ../branch_02_04_xx/myproj (回到分支所在的目录)

$ ./edit_commit

$ cvs rtag –r branch_02_04_xx REL_branch_02_04_08 myproj

$ cd ../main_trunk/myproj (回到主干所在的目录)

$ cvs rtag before_merge_branch_02_04_08 myproj

$ cvs update –j REL_branch_02_04_07 –j REL_branch_02_04_08

$ ./solve_conflict_commit

$ cvs rtag after_merge_branch_02_04_08

总之,使用分支之前,一定要规划好,确定整个分支的生命周期,以及merge的计划。尽可能少使用update –r来切换分支。同时commit时候要注意是否在指定分支上。在整个分支的开发过程中要做好TAG和merge的工作。

四、其他常见问题

(大家补充????)

4.1. tips from chenyongbao

一些tips:

1. 使用"cvs diff"时加"-u"参数,输出结果可读性更好,再加上vim的语法高亮功能,看

diff的内容很方便:

$ cvs diff -u xxx.cpp | vim -

2. "cvs rm"有个参数"-f", 在删除文件时,比如"cvs rm -f xxx.cpp",不用再执行系统

的rm命令。在删除多个文件时方便些。

3. alias cvs='cvs -q'可以让世界更清净些:)

4. "cvs annotate "命令能看某个文件中每一行是何时由何人修改,当时的文件版本

是什么。类似的有rannotate命令。

5. 在本地目录下放一个.cvsignore文件,能避免每次"cvs up"时出现一堆问号。

6. 200.5系统上有个cvslastchange脚本,看某个文件最后一次修改的内容很方便。比如

刚刚执行了"cvs up"命令,有一些以"U"和"P"开头的输出,那么就用cvslastchange

看看某人在这些文件上做了什么修改。cvslastchange是kdesdk-3.1-3的一部分。

7. 想取出某个特定TAG的版本来编译,用"cvs export"更安全些,万一做了修改,也能避免

commit。

相关主题
相关文档
最新文档