svn简易使用
博客已经搬家,请访问如下地址:http://www.czhphp.com
Subversion是一个免费的开源的版本管理系统,它是作为CVS(Concurrent Versions System)的取代品出现的。本文简单介绍了Subversion在Fedora上的安装过程及其基本概念和使用方法。您可以到O'Reilly出版的开源书籍Version Control with Subversion的主页在线阅读(中、英文版本都有),以获取更多信息。
1 在Fedora上安装Subversion
[aaronwong@localhost ~]$ sudo yum -y install subversion [aaronwong@localhost ~]$ rpm -ql subversion //上面的命令可查询subversion软件包在系统上所安装的文件列表 [aaronwong@localhost ~]$ sudo yum -y install mod_dav_svn //mod_dav_svn不是必须安装的,它是Apache HTTP Server的一个插件,你本地仓库(repository)的文件必须通过它才能在网络上与别人共享。 //subversion的组件列表点此查看。 [aaronwong@localhost ~]$ svn --version svn,版本 1.4.3 (r23084) 编译于 Mar 23 2007,09:29:55 版权所有 (C) 2000-2007 CollabNet。 Subversion 是开放源代码软件,请参阅 http://subversion.tigris.org/ 此产品包含由 CollabNet (http://www.Collab.Net/)开发的软件。 可使用以下的仓库存取 (RA) 模块: * ra_dav : 通过WebDAV(DeltaV)协议访问仓库的模块。 - 处理“http”方案 - 处理“https”方案 * ra_svn : 使用svn网络协议访问仓库的模块。 - 处理“svn”方案 * ra_local : 访问本地磁盘的仓库模块。 - 处理“file”方案 |
2 使用Subversion管理本地project
作为程序开发人员,我们没有必要了解Subversion的所有特性的方方面面,我们的目的是使用它来对我们的project进行方便的简单的版本管理,因此,强烈推荐阅读Subversion Quick-Start Guid和Basic Usage。
以下是笔者参照上述Guide进行一个简单的本地project的版本管理的示例。假定工程名为hello。
(1)建立本地工程hello的subversion仓库
Subversion把工程的各个版本的数据集中放在一个仓库(repository)中。假定我们要建立一个本地工程,叫做hello,为了使用subversion对它进行版本管理,首先要为该工程建立一个仓库。
[aaronwong@localhost ~]$ svnadmin create .subversion/repos/hello //subversion安装后会生成一个~/.subversion目录,这里,我们将工程hello的数据仓库建立在~/.subversion/repos/hello目录。 [aaronwong@localhost ~]$ ls -p .subversion/repos/hello/ conf/ dav/ db/ format hooks/ locks/ README.txt |
(2)按照subversion的要求组建本地工程hello的工作目录
假定工程hello的顶层目录为~/projects/hello(这里~代表/home/aaronwong/),则应如下组建工程的工作目录:
~/projects/hello/branches ~/projects/hello/tags ~/projects/hello/trunk/ hello.c |
//trunk目录是实际上的工程顶层目录,工程中的所有文件和文件夹都在该目录下组织。 [aaronwong@localhost ~]$ cd projects/hello/ [aaronwong@localhost hello]$ ls -p branches/ tags/ trunk/ [aaronwong@localhost hello]$ cat trunk/hello.c //This is a original version. #include <stdio.h> int main() { printf("Hello world!\n"); } |
(3)将本地工程hello导入本地的Subversion的工程仓库
由于是首次导入,因此要加信息-m "initial import"。
[aaronwong@localhost trunk]$ svn import ~/projects/hello/ file:///home/aaronwong/.subversion/repos/hello/ -m "initial improt" 新增 /home/aaronwong/projects/hello/trunk 新增 /home/aaronwong/projects/hello/trunk/hello.c 新增 /home/aaronwong/projects/hello/branches 新增 /home/aaronwong/projects/hello/tags 提交后的版本为 1。 |
注意,完成导入后,原目录~/projects/hello并不转换为“工作副本(working copy)”,而且该项目已经转由该仓库接管,即该仓库中已经包含了首次导入的工程的所有信息,与源目录~/project/hello再无任何关系,我们完全可以删除这一目录而不必担心丢失工程项目数据。注意,如果源目录并不是一个“工作副本”,那么就无法用svn进行管理,在其中所作的任何变动都无法提交到仓库。
要用subversion对工程进行版本管理,那么工程项目的开发必须在一个“工作副本”中进行,即首先要从仓库获取一个“工作副本”。
(4)工程开发过程中的版本管理与控制
使用subversion对工程进行版本管理的一般流程如下:
a)建立工程的数据仓库,并导入工程的最初版本(前面已经完成);
b)从仓库获取一个“工作副本”(svn checkout,可以获取最新版本也可以获取以前的某个版本),在这个“工作副本”中进行工程开发,修改完毕将变动提交到仓库。
下面简单介绍b)步骤。
由于工程hello已经导入到仓库,因此原目录可以删除。我们删除原目录,并从仓库获取工程hello的一个“工作副本”(working copy);当然,如果你当心这样做会造成数据丢失,完全可以重新建立一个目录,将“工作副本”保存到那里。
[aaronwong@localhost projects]$ rm -rf hello/ [aaronwong@localhost projects]$ svn checkout file:///home/aaronwong/.subversion/repos/hello/trunk hello A hello/hello.c 取出版本 1。 //注意,我们用红色标出了"trunk",如果不指定这一目录,则会取出除工程源文件外的其他不必要的目录如branches和tags。 [aaronwong@localhost projects]$ ls -a hello/ . .. hello.c .svn //可以看到“工作副本”下有一个.svn隐藏目录,其中就包含了subversion用來进行版本管理的信息。 |
下面可以对工程hello的内容进行编辑和修改。注意,如果要在工程中增加或删除某一文件或目录(包括复制和移动),必须使用svn add/delete/mkdir/copy/move等相关命令进行标记。
[aaronwong@localhost hello]$ pwd /home/aaronwong/projects/hello [aaronwong@localhost hello]$ vim hello.c [aaronwong@localhost hello]$ cat hello.c //This is the second version. #include <stdio.h> int main() { printf("Hello world!\n"); return; } [aaronwong@localhost hello]$ mkdir doc [aaronwong@localhost hello]$ vim doc/readme.txt [aaronwong@localhost hello]$ svn add doc A doc A doc/readme.txt //说明:如果svn add的对象是一个目录,则该目录及其子目录和其下的文件都会被添加到工程。 |
对工程编辑完毕,你可以检查一下该次编辑对工程(实际上是对你的"工作副本")做了哪些改动。
[aaronwong@localhost hello]$ svn status M hello.c A doc A doc/readme.txt [aaronwong@localhost hello]$ svn diff Index: hello.c =================================================================== --- hello.c (版本 1) +++ hello.c (工作副本) @@ -1,10 +1,10 @@ -//This is a original version. +//This is the second version. #include <stdio.h> int main() { printf("Hello world!\n"); - + return; } Index: doc/readme.txt =================================================================== --- doc/readme.txt (版本 0) +++ doc/readme.txt (版本 0) @@ -0,0 +1,2 @@ +This is an example for subversion tutorial. + //可以看到,svn diff提供了更详细的改动信息,并且很容易的将该命令的输出重定向为一个patch补丁。 |
检查确认无误后,便可提交此次更改,同时要附加此次更改的说明注释信息。
[aaronwong@localhost hello]$ svn commit -m "documents added." 新增 doc 新增 doc/readme.txt 正在发送 hello.c 传输文件数据.. 提交后的版本为 2。 |
现在工程仓库中已经保存了上面所提交的版本2的工程的所有信息,因此,上面的“工作副本”也可以被删除:当然,如果下次你还要继续使用这个“工作副本”进行工作,则可以保留这个副本,不过需要注意的是,如果你是在一个开发团队中,开发团队的任一成员都可能在你不知情的情况下更新了工程版本,因此,在团队开发中,进入已有的“工作副本”进行编辑前,应该先使用"svn update"命令将当前“工作副本”更新到仓库中的最新版本。
3 使用svn获取开源项目源代码
这实际上是获取一个“工作副本”的过程。例如我需要下载ffmpeg的最新源代码,就可以使用svn checkout命令来完成:
[aaronwong@localhost ~]$ svn checkout svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg
A ffmpeg/configure
A ffmpeg/Doxyfile
A ffmpeg/ffmpeg.c
A ffmpeg/vhook
A ffmpeg/vhook/imlib2.c
A ffmpeg/vhook/drawtext.c
A ffmpeg/vhook/fish.c
A ffmpeg/vhook/null.c
......
Subversion有许多特性、选项和华而不实的高级功能,但日常的工作中你只使用其中的一小部分,有一些只在特殊情况才会使用,在这一节里,我们会介绍许多你在日常工作中常见的命令。
当你在一个团队的项目里工作时,你希望更新你的工作拷贝得到所有其他人这段时间作出的修改,使用svn update让你的工作拷贝与最新的版本同步。
$ svn updateU foo.c
U bar.c
Updated to revision 2.
这种情况下,其他人在你上次更新之后提交了对foo.c
和bar.c
的修改,因此Subversion更新你的工作拷贝来引入这些更改。
让我们认真检查svn update的输出,当服务器发送修改到你的工作拷贝,一个字母显示在每一个项目之前,来让你知道Subversion对你的工作拷贝做了什么操作:
修改文件,可以使用文本编辑器、字处理软件、图形程序或任何你常用的工具,Subverion处理二进制文件像同文本文件一样—效率也一样。
这些是常用的可以修改目录树结构的子命令(我们会在后面包括svn import和svn mkdir)。
- svn add foo
- svn delete foo
-
预定将文件、目录或者符号链
foo
从版本库中删除掉,如果foo是文件,它马上从工作拷贝中删除,如果是目录,不会被删除,但是Subversion准备好删除了,当你提交你的修改,foo
就会在你的工作拷贝和版本库中被删除。[2] - svn copy foo bar
-
建立一个新的项目
bar
作为foo
的复制品,当在下次提交时会将bar
添加到版本库,这种拷贝历史会记录下来(按照来自foo
的方式记录),svn copy并不建立中介目录。 - svn move foo bar
-
这个命令与与运行svn copy foo bar; svn delete foo完全相同,
bar
作为foo
的拷贝准备添加,foo
已经预定要被删除,svn move不建立中介的目录。
相对于其他命令,你会更多地使用这个svn status命令。
在这种格式下,svn status打印五列字符,紧跟一些空格,接着是文件或者目录名。第一列告诉一个文件的状态或它的内容,返回代码解释如下:
A item
C item
D item
M item
R item
-
文件、目录或是符号链
item
预定将要替换版本库中的item
,这意味着这个对象首先要被删除,另外一个同名的对象将要被添加,所有的操作发生在一个修订版本。 X item
? item
-
文件、目录或是符号链
item
不在版本控制之下,你可以通过使用svn status的--quiet
(-q
)参数或父目录的svn:ignore
属性忽略这个问题,关于忽略文件的使用,见“svn:ignore
”一节。 ! item
-
文件、目录或是符号链
item
在版本控制之下,但是已经丢失或者不完整,这可能因为使用非Subversion命令删除造成的,如果是一个目录,有可能是检出或是更新时的中断造成的,使用svn update可以重新从版本库获得文件或者目录,也可以使用svn revert file恢复原来的文件。 ~ item
-
文件、目录或是符号链
item
在版本库已经存在,但你的工作拷贝中的是另一个。举一个例子,你删除了一个版本库的文件,新建了一个在原来的位置,而且整个过程中没有使用svn delete或是svn add。 I item
-
文件、目录或是符号链
item
不在版本控制下,Subversion已经配置好了会在svn add、svn import和svn status命令忽略这个文件,关于忽略文件,见“svn:ignore
”一节。注意,这个符号只会在使用svn status的参数--no-ignore
时才会出现—否则这个文件会被忽略且不会显示!
第二列说明文件或目录的属性的状态(更多细节可以看“属性”一节),如果一个M
出现在第二列,说明属性被修改了,否则显示空白。
第三列只显示空白或者L
,L
表示Subversion已经在.svn
工作区域锁定了这个项目,当你的svn commit正在运行的时候—也许正在输入log信息,运行svn status你可以看到L
标记,如果这时候Subversion并没有运行,可以推测Subversion发生中断并且已经锁定,你必须运行svn cleanup来清除锁定(本节后面将有更多论述)。
第四列只会显示空白或+
,+
的意思是一个有附加历史信息的文件或目录预定添加或者修改到版本库,通常出现在svn move或是svn copy时,如果是看到A +
就是说要包含历史的增加,它可以是一个文件或是拷贝的根目录。+
表示它是即将包含历史增加到版本库的目录的一部分,也就是说他的父目录要拷贝,它只是跟着一起的。 M +
表示将要包含历史的增加,并且已经更改了。当你提交时,首先会随父目录进行包含历史的增加,然后本地的修改提交到更改后的版本。
第五列只显示空白或是S
,表示这个目录或文件已经转到了一个分支下了(使用svn switch)。
如果你传递一个路径给svn status,它只给你这个项目的信息:
$ svn status stuff/fish.c
D stuff/fish.c
svn status也有一个--verbose
(-v
)选项,它可以显示工作拷贝中的所有项目,即使没有改变过:
$ svn status --verbose
M 44 23 sally README
44 30 sally INSTALL
M 44 20 harry bar.c
44 18 ira stuff
44 35 harry stuff/trout.c
D 44 19 ira stuff/fish.c
44 21 sally stuff/things
A 0 ? ? stuff/things/bloo.h
44 36 harry stuff/things/gloo.c
这是svn status的“加长形式”,第一列保持相同,第二列显示一个工作版本号,第三和第四列显示最后一次修改的版本号和修改人。
上面所有的svn status调用并没有联系版本库,只是与.svn
中的元数据进行比较的结果,最后,是--show-updates
(-u
)参数,它将会联系版本库为已经过时的数据添加新信息:
$ svn status --show-updates --verbose
M * 44 23 sally README
M 44 20 harry bar.c
* 44 35 harry stuff/trout.c
D 44 19 ira stuff/fish.c
A 0 ? ? stuff/things/bloo.h
Status against revision: 46
注意这两个星号:如果你现在执行svn update,你的README
和trout.c
会被更新,这告诉你许多有用的信息—你可以在提交之前,需要使用更新操作得到文件README
的更新,或者说文件已经过时,版本库会拒绝了你的提交。(后面还有更多关于此主题)。
另一种检查修改的方式是svn diff命令,你可以通过不带参数的svn diff精确的找出你所做的修改,这会输出统一区别格式:[3]
$ svn diffIndex: bar.c
===================================================================
--- bar.c (revision 3)
+++ bar.c (working copy)
@@ -1,7 +1,12 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <stdio.h>
int main(void) {
- printf("Sixty-four slices of American Cheese...\n");
+ printf("Sixty-five slices of American Cheese...\n");
return 0;
}
Index: README
===================================================================
--- README (revision 3)
+++ README (working copy)
@@ -193,3 +193,4 @@
+Note to self: pick up laundry.
Index: stuff/fish.c
===================================================================
--- stuff/fish.c (revision 1)
+++ stuff/fish.c (working copy)
-Welcome to the file known as 'fish'.
-Information on fish will be here soon.
Index: stuff/things/bloo.h
===================================================================
--- stuff/things/bloo.h (revision 8)
+++ stuff/things/bloo.h (working copy)
+Here is a new file to describe
+things about bloo.
svn diff命令通过比较你的文件和.svn
的“原始”文件来输出信息,预定要增加的文件会显示所有增加的文本,要删除的文件会显示所有要删除的文本。
输出的格式为统一区别格式(unified diff format),删除的行前面加一个-
,添加的行前面有一个+
,svn diff命令也打印文件名和打补丁需要的信息,所以你可以通过重定向一个区别文件来生成“补丁”:
举个例子,你可以把补丁文件发送邮件到其他开发者,在提交之前审核和测试。
假设你通过上面的diff输出发现你不小心用编辑器在README
中输入了一些字符。
Reverted 'README'
Subversion把文件恢复到未修改的状态,叫做.svn
目录的“原始”拷贝,应该知道svn revert可以恢复任何预定要做的操作,举个例子,你不再想添加一个文件:
? foo
$ svn add foo
A foo
$ svn revert foo
Reverted 'foo'
$ svn status foo
? foo
$ svn status README
README
$ svn delete README
D README
$ svn revert README
Reverted 'README'
$ svn status README
README
我们可以使用svn status -u来预测冲突,当你运行svn update一些有趣的事情发生了:
$ svn update
U INSTALL
G README
C bar.c
Updated to revision 46.
U
和G
没必要关心,文件干净的接受了版本库的变化,文件标示为U
表明本地没有修改,文件已经根据版本库更新。G
标示合并,标示本地已经修改过,与版本库没有重迭的地方,已经合并。
但是C
表示冲突,说明服务器上的改动同你的改动冲突了,你需要自己手工去解决。
-
如果Subversion认为这个文件是可合并的,它会置入冲突标记—特殊的横线分开冲突的“两面”—在文件里可视化的描述重叠的部分(Subversion使用
svn:mime-type
属性来决定一个文件是否可以使用上下文的,以行为基础合并,更多信息可以看“svn:mime-type
”一节)。 -
对于每一个冲突的文件,Subversion放置三个额外的未版本化文件到你的工作拷贝:
filename.mine
-
你更新前的文件,没有冲突标志,只是你最新更改的内容。(如果Subversion认为这个文件不可以合并,
.mine
文件不会创建,因为它和工作文件相同。) filename.rOLDREV
-
这是你的做更新操作以前的
BASE
版本文件,就是你在上次更新之后未作更改的版本。 filename.rNEWREV
-
这是你的Subversion客户端从服务器刚刚收到的版本,这个文件对应版本库的
HEAD
版本。
这里
OLDREV
是你的.svn
目录中的修订版本号,NEWREV
是版本库中HEAD
的版本号。
举一个例子,Sally修改了sandwich.txt
,Harry刚刚改变了他的本地拷贝中的这个文件并且提交到服务器,Sally在提交之前更新它的工作拷贝得到了冲突:
$ svn update
C sandwich.txt
Updated to revision 2.
$ ls -1
sandwich.txt
sandwich.txt.mine
sandwich.txt.r1
sandwich.txt.r2
在这种情况下,Subversion不会允许你提交sandwich.txt
,直到你的三个临时文件被删掉。
$ svn commit --message "Add a few more things"
svn: Commit failed (details follow):
svn: Aborting commit: '/home/sally/svn-work/sandwich.txt' remains in conflict
如果你遇到冲突,三件事你可以选择:
-
“手动”合并冲突文本(检查和修改文件中的冲突标志)。
-
用某一个临时文件覆盖你的工作文件。
-
运行svn revert <filename>来放弃所有的修改。
一旦你解决了冲突,你需要通过命令svn resolved让Subversion知道,这样就会删除三个临时文件,Subversion就不会认为这个文件是在冲突状态了。[4]
$ svn resolved sandwich.txt
Resolved conflicted state of 'sandwich.txt'
第一次尝试解决冲突让人感觉很害怕,但经过一点训练,它简单的像是骑着车子下坡。
$ cat sandwich.txtTop piece of bread
Mayonnaise
Lettuce
Tomato
Provolone
<<<<<<< .mine
Salami
Mortadella
Prosciutto
=======
Sauerkraut
Grilled Chicken
>>>>>>> .r2
Creole Mustard
Bottom piece of bread
小于号、等于号和大于号串是冲突标记,并不是冲突的数据,你一定要确定这些内容在下次提交之前得到删除,前两组标志中间的内容是你在冲突区所做的修改:
<<<<<<< .mineSalami
Mortadella
Prosciutto
=======
=======
Sauerkraut
Grilled Chicken
>>>>>>> .r2
通常你并不希望只是删除冲突标志和Sally的修改—当她收到三明治时,会非常的吃惊。所以你应该走到她的办公室或是拿起电话告诉Sally,你没办法从从意大利熟食店得到想要的泡菜。[5]一旦你们确认了提交内容后,修改文件并且删除冲突标志。
Top piece of breadMayonnaise
Lettuce
Tomato
Provolone
Salami
Mortadella
Prosciutto
Creole Mustard
Bottom piece of bread
现在运行svn resolved,你已经准备好提交了:
$ svn resolved sandwich.txt$ svn commit -m "Go ahead and use my sandwich, discarding Sally's edits."
记住,如果你修改冲突时感到混乱,你可以参考subversion生成的三个文件—包括你未作更新的文件。你也可以使用第三方的合并工具检验这三个文件。
如果你只是希望取消你的修改,你可以仅仅拷贝Subversion为你生成的文件替换你的工作拷贝:
$ svn updateC sandwich.txt
Updated to revision 2.
$ ls sandwich.*
sandwich.txt sandwich.txt.mine sandwich.txt.r2 sandwich.txt.r1
$ cp sandwich.txt.r2 sandwich.txt
$ svn resolved sandwich.txt
最后!你的修改结束了,你合并了服务器上所有的修改,你准备好提交修改到版本库。
svn commit命令发送所有的修改到版本库,当你提交修改时,你需要提供一些描述修改的日志信息,你的信息会附到这个修订版本上,如果信息很简短,你可以在命令行中使用--message
(-m
)选项:
Sending sandwich.txt
Transmitting file data .
Committed revision 3.
然而,如果你把写日志信息当作工作的一部分,你也许会希望通过告诉Subversion一个文件名得到日志信息,使用--file
选项:
Sending sandwich.txt
Transmitting file data .
Committed revision 4.
如果你没有指定--message
或者--file
选项,Subversion会自动地启动你最喜欢的编辑器(见“config”一节的editor-cmd
部分)来编辑日志信息。
提示
如果你使用编辑器撰写日志信息时希望取消提交,你可以直接关掉编辑器,不要保存,如果你已经做过保存,只要简单的删掉所有的文本并再次保存。
$ svn commitWaiting for Emacs...Done
Log message unchanged or not specified
a)bort, c)ontinue, e)dit
a
$
版本库不知道也不关心你的修改作为一个整体是否有意义,它只检查是否有其他人修改了同一个文件,如果别人已经这样做了,你的整个提交会失败,并且提示你一个或多个文件已经过时了:
$ svn commit --message "Add another rule"Sending rules.txt
svn: Commit failed (details follow):
svn: Out of date: 'rules.txt' in transaction 'g'
此刻,你需要运行svn update来处理所有的合并和冲突,然后再尝试提交。
我们已经覆盖了Subversion基本的工作周期,还有许多其它特性可以管理你得版本库和工作拷贝,但是只使用前面介绍的命令你就可以很轻松的工作了。
[2] 当然没有任何东西是在版本库里被删除了—只是在版本库的HEAD
里消失了,你可以通过检出(或者更新你的工作拷贝)你做出删除操作的前一个修订版本来找回所有的东西。
[3] Subversion使用内置区别引擎,缺省情况下输出为统一区别格式。如果你期望不同的输出格式,你可以使用--diff-cmd
指定外置的区别程序,并且通过--extensions
传递其他参数,举个例子,察看本地文件foo.c
的区别,同时忽略空格修改,你可以运行svn diff --diff-cmd /usr/bin/diff --extensions '-bc' foo.c。
[4] 你也可以手工的删除这三个临时文件,但是当Subversion会给你做时你会自己去做吗?我们是这样想的。
[5] 如果你向他们询问,他们非常有理