CVS

Table of Contents

1. CVS 简介

CVS is a version control system, an important component of Source Configuration Management (SCM). Using it, you can record the history of sources files, and documents. It fills a similar role to the free software RCS, PRCS, and Aegis packages.

官方网站:http://savannah.nongnu.org/projects/cvs
最好的参考书籍:Version Management with CVS(The Cederqvist)

2. CVS 基本命令

2.1. 命令基本格式

cvs [cvs-options] command [command-options-and-arguments]

The commands you "need to know" include:

  • init
  • import
  • checkout
  • add
  • commit
  • update
  • diff
  • tag
  • remove

2.2. 查看命令帮助

cvs --help
cvs -H            #和cvs -help相同
cvs --help-options     #查看所有支持的选项。
cvs --help-commands    #查看所有支持的命令。
cvs -H checkout        #查看某个子命令(如cvs checkout)的帮助。

2.3. 连接服务器(CVSROOT 设置)

首先介绍一下 cvs 文件仓库的概念。在 cvs 服务器上建立文件仓库,称为 Repository。cvs 有两种访问仓库的方式,pserver 和 ext(stand for external)。
在 pserver 方式下我们需要首先使用 cvs login 指令登录,并在完成 cvs 操作后使用 cvs logout 指令注销登录(cvs 自己处理连接的细节以及管理安全);而在 ext 方式下可使用由 CVS_RSH 环境变量设定的第三方远程登录工具(如 ssh)进行连接。psverver 方式的设置相对比较简单,这里只介绍 pserver 方式。

cvs 使用类似于 URL 的字符串来标记服务器,称为 CVSROOT。形式如下:

[:method:][[user][:password]@]hostname[:[port]]/path/to/repository

在使用 cvs 前,必须先设置 CVSROOT 变量。

export CVSROOT=":pserver:user@xxx.xxx.com:2401/home/export"

现在可以登录,进行其它操作了:

cvs login  #会提示输入密码
……
cvs logout  #不再需要操作了,可退出,结束cvs交互过程

注:如果不设定 CVSROOT,每条命令中用-d 选项指定 CVSROOT 也可以进行操作,只是比较麻烦,如:

cvs -d ":pserver:user@xxx.xxx.com:2401/home/export" login
cvs -d ":pserver:user@xxx.xxx.com:2401/home/export" checkout something

2.4. 列出仓库中的所有 modules

cvs ls

如果服务器提示不支持 ls(CVSNT 服务器才支持 ls),则可以使用下面命令

cvs checkout -c

其中-c 的含义是"cat" the module database.

2.5. 签出仓库中的指定目录(checkout)

cvs checkout 后跟要签出的目录名即可:

cvs checkout test/dir1
cvs co  test/dir1         #同上
cvs get  test/dir1        #同上

2.6. 查看签出文件的修改记录(log)

cvs log filename
cvs log dirname    #将查看指定目录下所有文件的修改记录

2.7. 查看签出文件的状态(status)

cvs status filename
cvs status dirname

如果本地文件是仓库中最新的,会显示 Status: Up-to-date
如果在本地修改过文件,则会显示 Status: Locally Modified
如果本地文件不是仓库中最新的,则会显示 Status: Needs Patch
如果本地文件在自动 merge 时出现了冲突,则会显示 Status: File had conflicts on merge

2.8. 比较本地文件和仓库中文件(diff)

cvs diff filename

注意:上面命令并不是拿本地的文件和服务器上最新的做比较。如果你前天更新了 filename(版本号为 1.2),但昨天其它同事更改并提交了(版本号为 1.3),但你昨天并没有更新本地文件,今天你修改后运行 cvs diff filename,实际是拿你本地的文件和服务器上 1.2 的版本做比较。

比较指定版本号的文件差异

cvs diff -r1.1 -r1.2 filename

比较版本 1.1 和 1.2 的不同。

2.9. 更新本地文件(update)

cvs update files   #更新指定文件
cvs update         #更新当前文件夹,但默认不会创建任何新目录
cvs update -d      #更新当前文件夹,服务器上有新目录也会更新下载过来

更新命令执行时,输出信息中每个文件前面有个字母标记,其含义如下:
U 完整的文件从服务器上更新到了本地(当文件为二进制文件或者本地不存在的文件)。
P 文件的部分内容从服务器更新到了本地(文件为文本文件时,cvs 将只传送本地文件和位于服务器上的文件相比较不同的部分,以减少带宽的使用)。
A 这个文件目前只存在于本地目录中,你虽然使用 cvs add 命令向 cvs 服务器添加了文件,但还没有使用 cvs commit 命令确认添加操作。
R 这个文件你已经不存在于本地了,而且你也使用了 cvs remove 命令从 cvs 服务器上删除了这个文件,但还没有使用 cvs commit 命令确认删除操作。
M 这里有两种含义:一是你的本地文件修改了,但还没有用 cvs commit 命令提交到 cvs 服务器上;二是你的本地文件修改了,cvs 服务器上的文件被其他人修改了,现在你们的修改已经成功的合并了。
C 你的本地文件修改了,cvs 服务器上的文件被其他人修改了,但现在你们的修改发生的冲突,无法自动合并。
? 这个文件只存在于本地。

2.10. 放弃本地修改(update -C)

cvs update -C file

2.11. 添加本地文件到仓库(add)

cvs add file1.txt

注:cvs add 命令向 cvs 服务器添加文件,但还要使用 cvs commit 命令确认添加操作。

2.11.1. 添加目录及其子目录

cvs add 命令既可添加文件又可添加目录,用 cvs add 添加一个目录时,只会增加目录本身,该目录下的文件及子目录不会增加。

用下面方法可以递归地添加目录:

find . -type d \! -name CVS -exec cvs add '{}' \;
find . \( -type d -name CVS -prune \) -o \( -type f -exec cvs add '{}' \; \)

参考:http://stackoverflow.com/questions/5071/how-to-add-cvs-directories-recursively

注:除上面方法外,还可以使用 cvs import 命令导入目录到 cvs 服务器。

2.12. 提交修改(commit)

cvs commit files
cvs ci files         #同上

上面命令执行时会进入编辑器,让你输入注释。
提交更改时同时指定注释:

cvs commit -m "add your comments" files

2.13. 增加和删除标记(tag/rtag)

cvs tag  xyz  .      #给当前目录下所有文件增加标记xyz
cvs tag -d xyz .     #去掉当前目录下所有文件上的标记xyz

2.13.1. tag 和 rtag 的不同

Difference between tag and rtag is that tag command adds a tag to checked out version of files whereas rtag uses the most recent versions in the repository. These can have different effects, e.g. if someone checks in new stuff while you are working.

参考:https://netbeans.org/community/sources/branches.html

2.14. 创建分支

用 tag 或 rtag 的'-b'选项可以创建分支。

如,从当前主干上创建一个名为 brach1 的分支:

cvs tag -b branch1 .

2.15. 查看某文件已经存在的分支(status -v)

cvs status -v filename1 可以查看文件 filename1 上已经存在的分支。如:

$ cvs status -v driver.c
===================================================================
File: driver.c          Status: Up-to-date

    Version:            1.7     Sat Dec  5 18:25:54 1992
    RCS Version:        1.7     /usr/local/cvsroot/yoyodyne/tc/driver.c,v
    Sticky Tag:         release-1-0-patches (branch: 1.7.2)
    Sticky Date:        (none)
    Sticky Options:     (none)

    Existing Tags:
        release-1-0-patches             (branch: 1.7.2)
        release-1-0                     (revision: 1.7)

3. CVS 技巧

3.1. 检查哪些文件需要更新?哪里文件在本地被修改?

用 cvs status 命令可以达到这个目的,但它的输出太多,不太直观。

A 'dummy' update will give you this information.

cvs -qn update

上面命令将把当前目录及子目录中所有文件的状态直观表示出来。选项的含义为:
-q Cause CVS to be somewhat quiet.
-n Do not execute anything that will change the disk.

3.2. 让 cvs 忽略某些文件

cvs 默认会忽略一些常见临时文件。
如果要显示指定 cvs 忽略特定文件,可以在~/.cvsignore 中指定。

参考:
Version Management with CVS(The Cederqvist) C.5 Ignoring files via cvsignore

3.3. merge 实例:取消上一次的修改

假设文件 filename1 的上一次修改导致了其他问题的产生,需要取消上一次修改。怎么做呢?当然我们可以手动把所做的改动一行行回退,再提交一个新版本。这种方法漏掉改动,极易出错。利用 cvs 的 merge 功能可以完成这个任务。

假设文件 filename1 最新的版本号为 1.6(这是一个有问题的版本),我们需要恢复 filename1 为上一个版本(如 1.5)。操作步骤如下:

cvs update -j 1.6 -j 1.5 filename1
cvs commit filename1

说明 1: -j 1.6 -j 1.5 这个顺序不能颠倒。
说明 2:如果要回退两个版本,可以运行 cvs update -j 1.6 -j 1.4 filename1; cvs commit filename1

3.4. merge 实例:把分支上改动移植到主干

假设 cvs 分支 branch1 上对文件 filename1 进行了修改,用下面操作可以把这个修改移植到主干代码上:

$ cvs checkout filename1
$ cvs update -j branch1 filename1
$ cvs commit filename1

注:如果有冲突,会提示"conflicts during merge"

3.5. merge 实例:把主干改动反向移植到分支

假设 cvs 主干上文件 filename1 进行了修改,如何把这个修改也移植到分支 branch1 上呢?

首先,找到文件 filename1 在主干上的最新版本号,假设为 1.5。
然后,用下面操作即可:

$ cvs checkout -r branch1 filename1
$ cvs update -j 1.5 filename1
$ cvs commit filename1

注:如果有冲突,会提示"conflicts during merge"

3.6. 获取指定版本的文件

If you wish to get a particular version of the file (say you've added a bad change and want to scrap it), you can do this:

cvs update -r1.78 textctrl.cpp  #获取版本为1.78的textctrl.cpp

This sets the 'sticky' tag, which means that all your subsequent updates will retrieve that revision until you update with

cvs update -A textctrl.cpp  #更新textctrl.cpp到最新版本

参考:
http://www.linuxquestions.org/questions/linux-newbie-8/use-cvs-to-get-a-particular-version-328535/

3.7. 查看某用户的所有代码改动

要查看某用户(如 jack)的所有改动,可以用下面的命令:

$ cvs -q log -N -S -wjack .

说明:
-N Do not list tags.
-S Do not print name/header if no revisions selected.
-w 和用户名 jack 之间不能有空格,因为这里-w 是 log 命令的子选项,而 cvs 命令本身也有-w 选项。

参考:http://stackoverflow.com/questions/354599/how-to-find-all-commitsfilescomments-by-a-person-in-cvs

3.8. 查看某一天的提交的代码改动

要想查看 2013-11-20 这一天提交的代码改动,可用下面命令:

$ cvs log -N -S -d '2013-11-20<2013-11-21' .

要想查看 2013-11-20 到目录为止的代码改动,可用下面命令:

$ cvs log -N -S -d '2013-11-20<' .

3.9. 导入目录到 cvs 仓库(import)

import 导入当前目录到 cvs repository。

假设当前文件夹为 dir1,想导入到 test/project 中。
第一步,进入到该目录,清除不需要提交的文件。
第二步,执行 cvs import 命令。如:

cvs import -m "test log message" test/project/dir1 vendorTag releaseTag

vendorTag 和 releaseTag 是标记名称,可以随便写。
第三步,用 cvs co 获取新导入的项目。如:

cvs co test/project/dir1

3.10. 删除整个文件夹

删除文件夹,有两个任务:删除文件里所有文件,删除空文件夹。

第一个任务就是删除文件。
第二个任务无法完成,除非去 cvs 服务器上直接删除。

参考:
http://stackoverflow.com/questions/204991/remove-empty-directory-from-cvs
http://www.network-theory.co.uk/docs/cvsmanual/Removingdirectories.html

3.11. 把分支代码 merge 到主干

如:release1 是分支名,使用 update 的-j 参数(意为 join)可进行自动 merge。

cvs update -j release1

如果没有冲突,就可以提交 cvs commit 了。

参考:
https://kb.wisc.edu/middleware/page.php?id=4087

4. 配置 cvs 服务器

服务器安装启动步骤:
第一步,安装 xinetd,Ubuntu 中可这样安装 apt-get install xinetd
第二步,在/etc/xinetd.d 目录下创建一个配置文件,名为 cvspserver,文件内容为:
service cvspserver{ socket_type = stream protocol = tcp wait = no user = root server = /usr/bin/cvs server_args = -f --allow-root=/home/cvsroot pserver disable = no}
第三步,重启 xinetd 服务。
第四步,用 netstat 测试是否正常启动。netstat -lnp |grep 2401 如果发现类似下面内容,则已启动 CVS 服务。tcp 0 0 0.0.0.0:2401 0.0.0.0:* LISTEN xxxxxx/xinetd

5. 其它 CVS 技巧

5.1. attic 目录

attic 目录由 cvs 自动维护,用户无需关心。

File is stored in the attic if and only if the head revision on the trunk has state dead. A dead state means that file has been removed, or never added, for that revision.
如:删除的文件会放入到 attic 目录中,在某个分支中加入的文件也在 attic 目录中。

5.2. -kb 选项

如果要增加的文件为二进制文件,则应该加上-kb 选项。
cvs 设计目标主要是处理文本文件,这样可以按修改的行保存每个修订版本,而不需要为每个版本都保存一个完整的备份。但如果用 cvs 来对二进制文件(如 doc 等)进行版本控制,则那些处理文本文件的特性应该取消。
在添加非文本文件时指定-kb 选项,告诉 cvs 把这个文件当作二进制文件处理,对每一个版本,cvs 都将保存整个完整的文件!

cvs add -kb file.doc

5.3. branch number 最后一位总为偶数

For obscure reasons CVS always gives branches even numbers, starting at 2.
如:在版本 1.2 中拉两个分支出来,新分支号分别会为 1.2.2 和 1.2.4;在版本 1.2.2.3 上拉一个分支出来,新的分支为 1.2.2.3.2。

                                                     +-------------+
                          Branch 1.2.2.3.2 ->        ! 1.2.2.3.2.1 !
                                                   / +-------------+
                                                  /
                                                 /
                 +---------+    +---------+    +---------+    +---------+
Branch 1.2.2 -> _! 1.2.2.1 !----! 1.2.2.2 !----! 1.2.2.3 !----! 1.2.2.4 !
               / +---------+    +---------+    +---------+    +---------+
              /
             /
+-----+    +-----+    +-----+    +-----+    +-----+
! 1.1 !----! 1.2 !----! 1.3 !----! 1.4 !----! 1.5 !      <- The main trunk
+-----+    +-----+    +-----+    +-----+    +-----+
                !
                !
                !   +---------+    +---------+    +---------+
Branch 1.2.4 -> +---! 1.2.4.1 !----! 1.2.4.2 !----! 1.2.4.3 !
                    +---------+    +---------+    +---------+

参考:https://ftp.gnu.org/old-gnu/Manuals/cvs-1.9/html_chapter/cvs_3.html#SEC8

Author: cig01

Created: <2012-09-22 Sat>

Last updated: <2018-02-09 Fri>

Creator: Emacs 27.1 (Org mode 9.4)