CVS 管理
--- 以本次建立的 CVS 系统为例
1. CVS 简介
CVS (Concurrent Versions System)是一个版本控制系统。使用它,你可以记录下你的源文件的历史。例如:修改软件时可能会产生一些bug,而且可能过了很久你才会察觉到它们的存在。有了CVS,你可以很容易地恢复旧版本,并从中看出到底是哪个修改导致了这个bug 。有时这是很有用的。当然了,你可能把所有版本的所有文件都保存了下来。但这会浪费大量的磁盘空间。而CVS用一种聪明的办法来保存一个文件的所有版本——仅仅保存不同版本之间的区别——在一个文件里。如果你是项目开发组的一员,CVS也会帮助你。除非极为小心,成员之间很容易互相覆盖文件。一些编辑器,如GNU Emacs,会保证同一时间内同一文件绝不会被两个人修改。不幸的是,如果有人用了另外的编辑器,这种保护就没用了。CVS用隔离开不同的开发者解决了这个问题。每个开发者在他自己的目录里工作,等每一个开发者都完成了他们自己的工作后,CVS会将它们合并到一起。CVS最初由Dick Grune在1986年12月以shell scripts的形式发布在comp.sources.unix的新闻组第6卷里。虽然当前的CVS中没什么代码来自于这些shell scripts,但许多CVS的冲突解决算法是从它们来的。1989年4月,Brian Berliner设计了CVS并编写了代码,之后Jeff Polk帮助Brian设计了CVS模块发行分支。
CVS 是一个C/S系统,多个开发人员通过一个中心版本控制系统来记录文件版本,从而达到保证文件同步的目的。工作模式如下:
CVS 服务器(文件版本库)
/ | \
(版 本 同 步)
/ | \
开发者1 开发者2 开发者3
2. 建立 CVS 服务器
1) 管理员
可以专门建立一个 CVS 管理员帐号,供 CVS 管理员使用,以后一切关于 CVS 管理的事情,都通过该帐号来完成。
这里,我们以 192.168.0.148 为 CVS 服务器的主机,在里面开设帐号 casic 为管理员帐号,帐号所在的组设为 casic ,凡是想加入 CVS 系统的用户,都必须加入 casic 组。
我们也会为每个开发人员开设帐号,以方便大家使用 CVS 系统。
2) 建立 CVS 服务器
以帐号 casic 登录 192.168.0.148 。
I. 创建 CVS 源代码仓库
创建目录 /home/casic/cvsroot 作为 CVS 源代码仓库。
II. 设置环境变量 CVSROOT
csh:
在 home 目录下的 .cshrc 文件中加入
setenv CVSROOT /home/casic/cvsroot
bash:
在 home 目录下的 .bashrc 文件中加入
export CVSROOT=/home/casic/cvsroot
具体的关于环境变量设置的文件请见附录给出的样例。
III. 初始化 CVS
运行命令:
cvs init
前提是刚才设置的环境变量已经起作用,如果没有起作用,使用命令
cvs –d /home/casic/cvsroot init
当然,尽快使刚才设置的环境变量起作用,以方便以后的操作。
初始化以后,会在 /home/casic/cvsroot 目录下出现一个子目录 CVSROOT ,里面放置了 CVS 服务器的配置文件。至于如何配置,下面再讲。
至此, CVS 服务器已经搭建好了,但只能在本地使用,想要远程使用,还得让服务器运行起来才行。
IV. 运行 CVS 服务器
使用 root 帐号登录,在 /etc/xinetd.d/ 目录下创建文件 cvspserver ,内容如下:
service cvspserver
{
port = 2401
socket_type = stream
protocol = tcp
wait = no
user = root
passenv = PATH
server = /usr/bin/cvs
server_args = -f --allow-root=/home/casic/cvsroot pserver
}
注意:
server : 后面为可执行程序 cvs 的全路径
server_args : 后面 /home/casic/cvsroot 为 CVS 源码库的路径
修改该文件权限:
chmod 644 cvspserver
然后重新启动 xinetd :
/etc/rc.d/init.d/xinetd restart
然后察看 cvs 服务器是否已经运行:
netstat -lnp|grep 2401
若出现以下信息,则服务器已经运行起来了。
tcp 0 0 0.0.0.0:2401 0.0.0.0:* LISTEN 7866/xinetd
3. 设置 CVS 服务器
以帐号 casic 登录。
这里我们只做极简单的改动,以使在 /home/casic/cvsroot/CVSROOT 目录下产生一个文件 commitlog 记录每次文件的修改。
基本命令操作请参照另一份文档《 CVS 常用命令》。我们可以直接对 CVS 仓库进行操作,但这样做容易出错且不容易恢复,所以不到万不得以,我们对 CVS 库的操作都应该通过 CVS 的命令进行。
这里,我们只修改文件 /home/casic/cvsroot/CVSROOT/loginfo ,首先提取它:
cvs co CVSROOT/loginfo
进入工作目录 CVSROOT ,对 loginfo 作如下修改:
a) 并在文件的开头添加一行:
# $Id$
b) 将其最后一行的行首的注释符删除
c) 在最下面添加两行:
ALL (echo ""; id; echo %{sVv}; date; cat) > $CVSROOT/CVSROOT/.temp_commitlog
ALL cat | $CVSROOT/CVSROOT/commit_mail %s
注意大小写,保存该文件,然后将其提交。提交以后再打开该文件,你会发现刚刚加入 的 $Id$ 已经被 cvs 自动扩展了,记录了文件名、版本号、修改时间和修改人。
在上面 loginfo 的修改中,我们使用了一个命令: $CVSROOT/CVSROOT/commit_mail ,这是一个用来发信的脚本,需要自己写。
该文件的内容如下:
$CVSROOT/CVSROOT/commit_mail:
#! /bin/csh
# $Id$
set tmplog="$CVSROOT/CVSROOT/.temp_commitlog"
cat $tmplog | grep "$CVSROOT/pub" >> /dev/null
if ( $status ) then
set maillist=`cat $CVSROOT/CVSROOT/cvs-maillist`
mail -s "CASIC CVS Mail: $1" $maillist < $tmplog
endif
这个脚本的作用是:在每次有人提交的时候,都会发信给所有使用 CVS 的人。当然,排除了 pub 这个目录的提交,即大家在 pub 里练习 cvs 命令的时候所作的提交不会发信给大家。
同时,在把 commit_mail 拷贝到目录 $CVSROOT/CVSROOT 的时候,也把它提交到 CVS 库里。
同时还要在 $CVSROOT/CVSROOT 里维护一个 CVS 邮件列表:文件 cvs-maillist ,里面是所有可以使用 CVS 的用户的列表。格式很简单:
casic
ynding
dyn
…
这样,简单的设置就结束了。
4. 导入源代码
下面要做的就是要将以前的源代码导入 CVS 库了。
在建立 CVS 服务器以前,我们已经积累了相当多的代码了,现在将这些代码存入 CVS 源码库。
1) 导入命令 cvs import
我们第一次提交源代码时使用命令 cvs import 。将要导入的源代码放到一个目录下,该目录下只有要导入的源代码文件和子目录。
进入该目录,运行命令:
cvs import –m “comment for project_name” project_name vendor_tag release_tag
其中: vendor_log 为开发商标志, release_tag 为版本标志, release_tag 很有用,下面会讲到怎么用它。
运行结束后,就将该目录下的文件、子目录都导入到 CVS 库了。
cvs import 命令并不改变你运行它的当前目录,也不把当前目录当作 CVS 的工作目录。导入以后,如果你想要修改文件,可以在其他目录里把它提取出来再修改。
2) 导入举例
以本次导入的 EP7312 项目为例。
CVS 主要是用来记录源代码的历史,所以在导入的时候,我们就不能一下子把最新的版本导入进去,得分层次地把源代码导进去,首先把最初始的从网上下载的没有改动的源代码导进去,然后将移植的补丁打进去,再把打过补丁的源代码导进去。
I. 导入初始未改动的源代码
建立目录 /home/casic/temp/ep7312 ,将 linux-2.4.13.tar.gz 解压缩到目录 /home/casic/temp/ep7312 下,移除 linux-2.4.13.tar.gz ,这样,在目录 /home/casic/temp/ep7312 下只有一个 linux 子目录。
进入目录 /home/casic/temp/ep7312 ,运行命令:
cvs import –m “Project EP7312” ep7312 EP7312 EP7312_1_0
这里源代码比较多,导入需要比较长的时间,耐性等待到导入结束。
这时可以查看一下目录 /home/casic/cvsroot ,可以发现里面多了一个目录 ep7312 ,该目录下就就刚才导入的源代码文件,不过文件名后面都有 “,v” 标记(目录没有),说明这些文件已经入库了。
II. 导入添加了补丁的源代码
回到刚才的目录,将补丁程序拷贝到目录 /home/casic/temp 下,然后进入目录 /home/casic/temp/ep7312/linux ,依次将补丁添加进来:
gunzip < ../../patch-2.4.13-ac4.gz | patch -p1 -t
gunzip < ../../patch-2.4.13-ac4-rmk1.gz | patch -p1 -t
gunzip < ../../patch-2.4.13-ac4-rmk1-codegen1.gz | patch -p1 –t
然后
cp edb7312 arch/arm/def-configs
这样,补丁就添加完了。
下面开始导入添加完补丁的代码,进入目录 /home/casic/temp/ep7312 ,运行命令:
cvs import –m “patches for EP7312” ep7312 EP7312 EP7312_1_1
注意 release_tag 的变化,下面就要利用 release_tag 来将这个分支合并到主干上。
III. 合并分支到主干
进入目录 /home/casic/temp ,建立目录 merge ,进入目录 merge ,运行命令:
cvs co –jEP7312_1_0 –jEP7312_1_1 ep7312 >& log
之所以要将 cvs co –jEP7312_1_0 –jEP7312_1_1 ep7312 的输出到文件 log ,是因为输出的信息下面我们要用到。
执行完成后,打开 log 文件,会看到很多文件前打了 R 标记,表示该文件被移除了,需要使用命令 cvs ci 来确认。下面再介绍一下其它标记的含义(对 cvs update 也一样):
U 提取的文件跟库里一样。
P 跟 U 差不多,不过提取的是一个补丁而不是整个文件。
A 文件被 cvs add 添加了,但没有用 cvs ci 确认。
R 文件被 cvs rm 删除了,但没有用 cvs ci 确认。
M 文件被改动了,可以用 cvs ci 提交了。
C 合并的时候存在冲突。
? 文件在工作目录里,但不在库里。
检查 log 的内容,看看有哪些标记,结果只有 U 和 R ,将标 R 的文件找出来,然后将其一一用命令 cvs ci 提交。
这样,合并就完成了。
3) 其他导入类似
目前已导入的 CVS 源码库的目录结构如下:
/home/casic/cvsroot/at91
CVSROOT
dev-tools
ep7312
misc-docs
pub
at91/bootloader
dev-docs
uClinux-dist
CVSROOT: 配置文件
dev-tools/cross-compiler-2.95.3-ep7312
cross-compiler-at91
ep7312/dev-docs
linux
romdisk
user
misc-docs: 存放其他各种文档。
pub: 供大家练习 cvs 命令
5. 设置客户端
上面的一切操作都是在 CVS 服务器本地机器上进行的。但实际情况下,很多开发人员的机器并不 CVS 服务器,需要在自己的机器上进行操作开发;而且如果大家都跑到 CVS 服务器上来操作开发的话, CVS 服务器也会不堪重负,无论是系统负荷还是硬盘空间。
下面就讲讲如何远程访问 CVS 服务器。
首先要在 CVS 服务器上开设用户帐号,并加入 casic 组,这里假设开设了帐号 ynding 。给帐号 ynding 设置环境变量 CVSROOT 的值为 /home/casic/cvsroot
这样,在 CVS 本地机上帐号 ynding 就可以访问 CVS 库了。
在远程机器上也建立帐号 ynding (当然也可以是其他帐号,开发时为了不混淆,取相同的帐号名为好),也给该 ynding 设置环境变量 CVSROOT :
setenv CVSROOT :pserver:ynding@192.168.0.148:/home/casic/cvsroot
或者
export CVSROOT=:pserver:ynding@192.168.0.148:/home/casic/cvsroot
设置完以后,并让环境变量起作用以后,就可以使用 CVS 服务器了。
先要登录到 CVS 服务器上:
cvs login
会提示输入密码,输入帐号 ynding 在 CVS 服务器上的密码,就能登录成功了。以后的对 CVS 服务器的操作跟在 CVS 服务器本地操作一样。完事以后,记得使用命令 cvs logout 退出 CVS 服务器。
6. 打标签,建分支
1) 确认项目里程碑
当项目开发的一定时间后,可以给所有文件指定一个阶段里程碑版本号,方便以后按照这个里程碑版本号导出项目,同时,也是多个项目分支开发的基础。
指定里程碑版本号就是给当前最新的版本打上一个标签,运行命令:
cvs tag release_1_0_project_name
这样,就给项目打上了一个标签,表示这就是 1.0 版本了,以后要提取这个 1.0 版本只要运行命令:
cvs co –r release_1_0_project_name project_name
就可以把 1.0 版本提取出来了。
这样,就可以进入 2.x 的开发了。运行命令:
cvs ci –r 2
就可以使所有的文件版本号变为 2.0 ,从而进入 2.x 的开发了。
注意:
项目里文件的版本号跟项目的发布版本没有直接关系,但所有文件的版本号跟发布版本一直有助于维护。所以,将所有文件的版本号都改为 2.x 不是必需的。
还有,在以标签提取出来的项目里,对文件的改动不能提交到 CVS 库里,会提示错误信息:这不是一个分支。
2) 建立分支
在开发 2.x 的时候,如果发现 1.0 版本有问题需要修复。这时一般并不在 2.x 里修改,一来是因为 2.x 还处于开发阶段,还不稳定,二来是在 2.x 里可能已经添加了很多新的功能,并不想让 1.0 的用户白白得到。
这时,我们可以为 1.0 建立一个分支,在这个分支了修改 bug 。这时,上面介绍的打的标签就起了作用,运行命令:
cvs rtag –b –r release_1_0_project_name release_1_0_project_name_patch project_name
这样就建立了一以 1.0 版本为基础的分支。在改分支里修改完 bug 以后,将修改提交到该分支里。
这时也可以在该分支里打一个标签:
cvs tag release_1_0_project_name_bug_fixed_1
当然了,也可以在当前工作目录下打标签,不管你提取的项目是以前的版本、补丁还是分支等等,直接在当前目录下运行命令:
cvs tag –b release_1_0_project_name_branch
这样就建立起一个分支了。提取分支和按发布版本提取的方法一样:
cvs co –r release_1_0_project_name_branch project_name
7. 分支操作
CVS 允许你修改代码到不同的开发线上,这就是分支( branch ) 。当你改变一个分支中的文件时,这些更改不会出现在主开发主干 (main trunk) 和其它分支中。
1) 访问分支
有两种方式可以进入分支:重新提取出一份或从当前工作目录切换过去。
A. 重新提取一份
运行命令:
cvs co –r release_1_0_project_name project_name
-r 后面跟的是标签名。
B. 切换
在当前工作目录下运行命令:
cvs update –r release_1_0_project_name project_name
这对现有拷贝是主干代码或是其他分支都是有效的,上面的命令把它转移到命名的分支。跟 cvs update 相似, cvs update –r 将合并你所做的任何改动,请注意有没有冲突出现。
一旦你的工作拷贝转移到一个特定的分支,它将一直保持在这个分支内,除非你做了其他操作。这意味着从这个工作拷贝的提交将添加到这个分支的新版本中,而不影响主干和其他分支。
想看一个工作拷贝是基于哪一个分支的,可以使用命令 cvs status 命令,在输出的结果中查看“ Sticky Tag ”项,如果为 none ,则处于主干中,如果不是,则处于该分支中。
例如,在 pub 目录下运行命令:
cvs st test.txt
结果:
File: test.txt Status: Locally Modified
Working revision: 1.4 Fri Aug 1 01:18:12 2003
Repository revision: 1.4 /home/casic/cvsroot/pub/test.txt,v
Sticky Tag: release_08_03_pub (revision: 1.4)
Sticky Date: (none)
Sticky Options: (none)
则处于分支 releae_08_03_pub 中。
使用 -v 选项还可以看出该文件处于哪些分之中,运行命令:
cvs st –v test.txt
结果:
File: test.txt Status: Locally Modified
Working revision: 1.4 Fri Aug 1 01:18:12 2003
Repository revision: 1.4 /home/casic/cvsroot/pub/test.txt,v
Sticky Tag: release_08_03_pub (revision: 1.4)
Sticky Date: (none)
Sticky Options: (none)
Existing Tags:
release_08_03_pub_patch_2 (branch: 1.4.4)
release_08_03_pub_patches (branch: 1.4.2)
release_08_03_pub (revision: 1.4)
注意结果:
如果在标签名后面的括号里的注释为 revision ,则是单纯的一个标签;如果是 branch ,则是一个分支。
2) 合并整个分支
你可以合并另一个分支上的修改到你的工作目录,只要在 update 命令中加 -j branchname 的标识。使用 -j branchname 将合并这个派生分支点与原版本的最新版之间的变更 ( 到你的工作目录 ) 。 -j 的意思是“ join ”。
例如,我们要将分支 release_1_0_project_name_branch 合并到主干中:
cvs co project_name
进入工作目录:
cvs update –jrelease_1_0_project_name_branch
注意有没有冲突发生,有的话解决以后再提交:
cvs ci –m “include release_1_0_project_name_branch”
也可以在提取的时候加上 -j 选项:
cvs co –jrelease_1_1_project_name_branch project_name
cvs ci –m “include release_1_0_project_name_branch”
效果和上面讲到的相同,当然也要注意处理冲突的问题。
3) 从一个分支多次合并
经过上面的合并以后,分支 release_1_0_project_name_branch 继续开发,经过一段时间后,又要合并到主干中。如果仍使用命令
cvs update –j release_1_0_project_name_branch
则将试图合并你已经合并过的东西,这可能导致一些不希望发生的事情。
因此,必须表达清楚你希望合并未被合并的内容,这样需要两个“ -j ”选项。 CVS 合并从第一个“ -j ”的版本到第二个“ -j ”的版本的变化。
例如:
cvs update –j1.2.2.2 -j release_1_0_project_name_branch
如果出现的问题是你必需手工指定版本号 1.2.2.2 ,一个更好的方法是使用:
cvs update –j release_1_0_project_name_branch:yesterday
-j release_1_0_project_name_branch ( 应该是在同一行 )
然而,最好的方法是在每一次合并以后给分支加一个标签,然后可以使用标签进行以后的合并:
cvs update -j release_1_0_project_name_branch_02
-j release_1_0_project_name_branch ( 应该在同一行 )
使用两个 ”-j revision” 选项, cvs update 和 cvs co 命令能合并任意两个不同的版本的差异到你目录:
cvs update –j1.5 –j1.3 test.txt
这个命令将把 1.5 版本恢复到 1.3 版本,所以一定要注意版本的顺序。
如果你在操作多个文件时使用这个选择项,你必须了解在不同的文件之间,版本的数字可能是完全不同的。你必须使用标笺( tag )而不是使用版本号来完成多个文件的操作。
使用两个 -j 操作也能恢复增加或删除的文件。例如,假定你有一个叫 file1 的文件在在于 1.1 版本中,然后你删除了它(因此增加了一个 dead 版本 1.2 )。现在你又打算恢复它,并用它原先的内容。下面是如何操作的例子:
cvs update -j 1.2 -j 1.1 file1
然后再提交一下:
cvs ci –m “restore file1” file1
5) 合并能添加和删除文件
如果你在合并时做的改变涉及到添加或删除一些文件, cvs update –j 和 cvs co -j 将反映这些变化。
示例请参考本文档开头导入源代码时合并两个分支的情形。
6) 合并与关键字
如果你要合并的文件中包含关键字,你会得到一大堆冲突,这是因为关键字与合并的版本关联。因此,你需要指定 -kk 在合并的命令行里面。这样只会替换关键字名而不包括里面的值。这个选项认为那些都是相同的,使合并避免产生假的冲突。
8. 添加 CVS 用户
1) 添加系统用户
在 CVS 服务器上添加一个系统用户,并加入 casic 组。
然后给该用户设置环境变量 CVSROOT 。
2) 修改 CVS 邮件列表
直接修改文件 /home/casic/cvsroot/cvs-maillist ,在里面添加新加入的用户名。
3) 修改 Sendmail 别名列表
修改文件 /etc/mail/aliases ,在里面添加新加入用户的邮箱别名。
9. 附录
1) bash
刚刚建立帐号时, home 目录下可能还没有环境的配置文件,如果想用 bash ,则要在自己的 home 目录下建立两个文件: .bash_profile 和 .bashrc
以帐号 dyn 为例,在 /home/dyn 目录下建立文件 .bash_profile 和 .bashrc ,文件的内容如下:
.bash_profile:
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH
unset USERNAME
.bashrc:
# .bashrc
# User specific aliases and functions
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
CVSROOT=:pserver:dyn@192.168.0.148:/home/casic/cvsroot; export CVSROOT
2) csh
刚刚建立帐号时, home 目录下可能还没有环境的配置文件,如果想用 csh ,则要在自己的 home 目录下建立一个文件: .cshrc
以帐号 ynding 为例,在 /home/ynding 目录下建立文件 .cshrc ,文件的内容如下:
.chsrc:
#!/usr/bin/csh
###set path###
set path=( . \
/usr/bin \
/usr/local/arm/2.95.3/bin \
/usr/local/arm-elf/bin \
/usr/local/bin \
/usr/bin \
/bin \
/usr/sbin \
/usr/local/sbin \
/usr/X11R6/bin \
/usr/X11R6/LessTif/Motif1.2/bin \
)
###ENVIRONMENTAL SETTING
setenv CVSROOT /home/casic/cvsroot
###END
umask 027