笔记之SubVersion
一、获取与安装
SubVersion(以下简称SVN)是一款开源的版本控制系统,与著名的CVS类似,并大有取代之势,当前版本1.4.3,可以从http://subversion.tigris.org/project_packages.html下载。我下载的是Windows版,下载文件svn-1.4.3-setup.exe后,双击,按照提示进行安装。安装时,安装程序会自动将环境配置好。
二、常用操作
创建SVN版本库的命令:
svnadmin create e:/svn
SVN会在e:/svn文件夹下创建一个版本库,版本库用来保存提交到SVN中的文件,并记录相应版本信息。如果e:/svn文件夹没有创建,SVN会自动创建。
导入与拆出文件。要将文件夹导入SVN版本库,可以执行命令:
svn import F:/project1 file:///e:/svn/project -m "firstProject"
其中-m是import子命令的参数,用于记录操作日志,当前步骤的操作日志为“firstProject”,其它svn命令如无特殊说明,-m参数也是同样含义。此时SVN会将F:/project1文件夹导入到刚刚创建的SVN版本库file:///e:/svn中的project目录下。下面执行命令:
svn checkout file:///e:/svn/project F:/project1
此时SVN版本库就被关联到F:/project1文件夹了。
不过将svn checkout操作简单地说成关联是不正确的,这一步操作应当称为拆出文件,其作用是将保存在SVN版本库中的文件取出,存放到指定文件夹下以供编辑。例如建立一个文件夹F:/project2,然后执行命令:
svn checkout file:///e:/svn/project F:/project2
此时SVN版本库也会被取到F:/project2中。下面将利用F:/project1和F:/project2文件夹来模拟两个用户的操作。
添加文件和文件夹。下面在F:/project1文件夹下新建文件1.txt,这是一个文本文件,然后在命令行中定位到该文件夹下,执行命令:
svn add 1.txt
svn commit -m ""
此时1.txt文件就会被添加到SVN版本库中了。这里注意,commit子命令是用来向SVN版本库提交修改内容的,以后每次文件有所变化都应用该命令向SVN执行提交。
与CVS不同,在SVN中是不区分文本文件和二进制文件的,例如在F:/project1文件夹下新建文件2.doc,然后执行命令:
svn add 2.doc
svn commit -m ""
跟之前的命令一样,此时2.doc也被添加到SVN版本库中了。
下面在F:/project1文件夹下再新建一个文件夹dir,然后执行命令:
svn add dir
svn commit -m ""
此时文件夹dir同样会被添加到SVN版本库中。
更新文件和文件夹。下面在命令行中定位到F:/project2文件夹下,此时的F:/project2是个空文件夹,没有任何内容。执行命令:
svn update
文件1.txt、2.doc和文件夹dir都被下载到F:/project2中了。其实svn update的作用是更新本地文件,记得以后每次修改文件之前,都执行一次该命令。
提交与更新文件。下面打开F:/project1中的1.txt文件,修改、保存并关闭,然后在命令中定位到该文件夹下,执行命令:
svn commit -m "edit 1.txt"
此时对1.txt所作的修改会被提交到SVN版本库中。如果想放弃修改,则可以在提交之前执行命令svn revert 1.txt,此时1.txt会被恢复成修改之前的内容。
下面再打开F:/project2中的1.txt文件,发现内容没有改变,关闭,在命令行中定位到该文件夹下,执行命令:
svn update
再次打开F:/project2/1.txt文件,会发现内容已经发生了变化,与F:/project1/1.txt文件一样。
处置修改冲突。下面分别修改F:/project1/1.txt和F:/project2/1.txt文件,注意保证两者内容的不一致,然后首先用svn commit -m ""提交F:/project1/1.txt,再提交F:/project2/1.txt,此时SVN会提示有冲突。这是因为我们对同一个文件作了不同的修改所致,这种情况在日常使用中会经常发生。要解决这个问题,请先在命令行中定位到F:/project2下,执行命令svn update,此时SVN将更新1.txt文件,把有冲突的部分以格式:
<<<<<<< .mine
F:/project2/1.txt文件冲突部分内容
=======
F:/project1/1.txt文件冲突部分内容
>>>>>>> .r4
标记出来,并生成1.txt.mine、1.txt.r3和1.txt.r4文件,以分别记录更新之前的F:/project2/1.txt、修改之前的F:/project2/1.txt和新近修改的F:/project1/1.txt。与CVS不同,SVN提供了三种解决问题的办法:第一种办法需要重新修改F:/project2/1.txt,然后执行命令:
svn resolved 1.txt
svn commit -m ""
其中resolved子命令用来告诉SVN指定文件的冲突已经解决。至此问题解决。关于1.txt.mine、1.txt.r3和1.txt.r4等文件,SVN会在执行svn resolved 1.txt命令时自动删除的。
第二种办法是在SVN自动生成的1.txt.mine、1.txt.r3和1.txt.r4等文件中进行选择,用其中之一直接覆盖1.txt文件,然后执行命令:
svn resolved 1.txt
svn commit -m ""
最后一种办法是放弃修改,或者称为恢复,即执行命令svn revert,此时F:/project2/1.txt变得与F:/project1/1.txt文件内容相同。注意,执行revert子命令时是不需要再执行commit子命令的。
文件和文件夹的查看与比较。先在命令行中定位到F:/project1下,执行svn update命令,然后陆续执行以下命令并观察其效果:
svn status -v 查看版本库中所有文件和文件夹的状态
svn status -v 1.txt 查看1.txt文件的状态
svn status -v dir 查看dir文件夹的状态
svn log 查看版本库中所有文件和文件夹的日志
svn log 1.txt 查看1.txt文件的日志
svn log dir 查看dir文件夹的日志
svn diff 1.txt 若保存在本地的1.txt文件被修改,则与保存在.svn文件夹中的原始拷贝比较差异
svn diff -r4 1.txt 用保存在本地的1.txt文件与保存在SVN版本库中的1.txt文件的特定版本比较差异
svn diff -r4:5 1.txt 比较SVN版本库中1.txt文件两个版本间的差异
svn cat -r4 1.txt 查看1.txt特定版本的内容
svn list[ file:///e:/svn] 查看仓库中的目录内容
文件和文件夹的删除。先在命令行中定位到F:/project1下,然后执行命令:
svn delete 2.doc
svn commit -m ""
此时F:/project1/2.doc文件就被从SVN版本库中删除了。如果想删除文件夹,以F:/project1/dir为例,可以执行命令:
svn delete dir
svn commit -m ""
与删除文件的操作一样。
文件和文件夹的恢复。在SVN中,文件的恢复可以分为两种情况,一种是删除文件但未提交,以F:/project1/1.txt文件为例,先在命令行中定位到F:/project1下,然后执行命令svn delete 1.txt,此时使用命令svn revert 1.txt,1.txt就被恢复了。其实revert子命令是用来放弃修改的,之前在“处置修改冲突”部分也曾提到过该命令。
另一种是删除文件且已提交,以之前删除的F:/project1/2.doc文件为例,执行命令:
svn copy -r 7 file:///e:/svn/project/2.doc 2.doc
svn commit -m ""
其中-r参数的值6是2.doc删除前的最后一个版本,此时F:/project1文件夹下便多了个2.doc文件。不过在SVN中,恢复文件的办法不止一途,还可以使用命令:
svn merge -r 10:7 file:///e:/svn/project
svn commit -m ""
此时dir文件夹也被恢复到F:/project1下了。与copy子命令相比,merge子命令是对文件进行回滚,而copy子命令则是对文件进行复制。关于这两个命令,下边会有更详细的介绍。不过现在我们可以发现一件事,即在SVN中文件和文件夹的处理方式是一样的,或者说在SVN中文件夹也是一种文件。
文件与文件夹的重命名与移动。先在命令行中定位到F:/project1下,执行命令:
svn move 2.doc 3.doc
svn commit -m ""
此时2.doc就被改名为3.doc了。下面再执行命令:
svn move 3.doc dir/3.doc
svn commit -m ""
此时3.doc又被移动到dir下了。由于在SVN中文件夹也被视为一种文件,处理方式与文件一样,因此这里就不再赘述了。
分支(branch)。假定软件K的发行版1.0已完成,而你正在继续开发下一版本,并计划在一段时间后发行版本1.1。可是不久你的客户就开始抱怨说K 1.0有问题,于是你检出了1.0的发行版,找到了这个错误。但是,当前代码的版本正处在一个不稳的状态,并且在一段时间之后才有希望稳定下来,这样就没法基于最新源代码去发行一个修复错误的版本了。这种情况下可以去为所有构成K 1.0发行版的文件创建版本树的一个分支。然后你可以修改这分支而不影响到主干。当修订完成时,你可以选定是否要把它同主干合并或继续保留在这个分支里。
创建分支。先在命令行中定位到F:/project1下,执行命令:
svn copy F:/project1/dir F:/project1/dir2
svn commit -m ""
此时SVN会在本地创建F:/project1/dir的副本F:/project1/dir2,两者内容一致,然后提交内容到SVN版本库中以为F:/project1/dir创建分支。这种作法与之前恢复已提交的删除文件时所使用的copy子命令类似,两者都是在本地创建指定文件或文件夹的副本,然后提交到版本库中,只是后者是为SVN版本库中的内容创建副本。其实SubVersion中所谓的分支,就是特定文件或文件夹的副本,与以往的版本管理工具相比,SubVersion中分支不是逻辑存在的,而是物理存在的。
此外,SVN提供了更简便的方式来建立分支:
svn copy file:///e:/svn/project file:///e:/svn/new_project -m ""
或
svn copy F:/YPJCCK/VersionControl/svn/project1/ file:///e:/svn/new_project -m ""
两者都会在SVN版本库中创建分支并直接提交,其差异在于前者是以SVN版本库中的内容为基础,而后者是以本地内容为基础。
此外,SubVersion中还有一个概念,标签(tag)。标签是一个文件在一段时间里的快照,在SubVersion中每次提交的修订版本都是一个快照。SubVersion中标签已与分支的概念相同。
获取分支。先在命令行中定位到F:/project2下,执行命令:
svn update dir2
此时dir2会被下载到F:/project2中。如果想下载之前的版本也可以利用该命令,例如:
svn update -r 10
此外,我们还可以转换工作拷贝对应的分支,在F:/project2下执行命令:
svn switch file:///e:/svn/new_project
然后执行命令svn update,此时F:/project2就被更新成new_project分支了。
在不同分支下修改文件。下面分别修改F:/project1/1.txt和F:/project2/1.txt文件,注意保证两者内容的不一致,然后使用svn commit -m ""进行提交。这两个文件均会被正确提交,而SVN也并未因为两者内容不一致提示版本冲突,只是提示新增了两个版本,这说明之前创建分支已经成功。
合并分支。在命令行中定位到F:/project1下,执行命令:
svn merge -r 17:Head file:///e:/svn/new_project
咦,这条命令看起来似乎有点眼熟,没错,前边介绍文件恢复时曾介绍过,不过当时是从高版本回退到低版本,而现在则是将分支合并到主干。其实merge子命令的真正作用是比较两个指定版本的区别,然后将结果应用到当前工作目录。这里Head代表最新版本,17则是之前修改分支时产生的版本。由于之前分别修改了F:/project1/1.txt和F:/project2/1.txt,并且刻意保证了两者内容的不同,因此在将new_project分支合并到主分支时,SVN会提示冲突。关于冲突的处置,与之前介绍的处置修改冲突方法一样,这里不再赘述。修改过1.txt文件后,执行命令:
svn resolved 1.txt
svn commit -m ""
合并完成。
此外,merge子命令还有四种形式(按照帮助的说法应该是两种):
svn merge F:/project1@16 F:/project2@18(本地副本的比较)
svn merge file:///e:/svn/project@16 file:///e:/svn/new_project@18(版本库内容的比较)
svn merge F:/project1@16 file:///e:/svn/new_project@18(本地副本与版本库内容的比较)
svn merge file:///e:/svn/project@16 F:/project2@18(版本库内容与本地副本的比较)
其作用也是将指定版本比较后,把结果应用到当前工作目录。这里@之后的数值代表版本,其中版本库路径后是可以省略的,缺省为Head,本地副本则必须指定。
属性操作。属性用来存放特别信息,可以为文件保存更多信息,是对工作拷贝的有益补充。
下面在命令行中定位到F:/project1下,执行命令:
svn propset test1 'test' 1.txt
svn propset test2 -F 2.txt 1.txt
前者为1.txt文件设置属性test1,属性值为test;后者为1.txt文件设置属性test2,并指定本地已存在文件2.txt的内容作为属性值。注意,通过-F参数,可以为属性指定任何文件作为值,包括二进制文件。
下面执行命令:
svn proplist 1.txt
svn propget test1 1.txt
前者用于查看1.txt文件有哪些属性;后者用于查看test1属性的值。
执行命令:
svn propedit test1 1.txt --editor-cmd notepad
指定使用notepad(Windows中的记事本,别告诉我你不知道)编辑1.txt文件的test1属性。修改后,关闭记事本即可。
现在我不想要test2属性了,执行命令:
svn propdel test2 1.txt
再次使用svn proplist 1.txt命令查看,test2属性已删除。
注意,经过以上一系列操作,1.txt文件已经被修改,因此需要使用svn commit -m ""命令进行提交。
此外,SVN自身也提供了一些属性,这些属性以svn开头,例如svn:executable、svn:mime-type等。关于这些属性,请参考其他文档,这里不介绍。
导出。在命令行中执行命令:
svn export file:///E:/svn/project F:/project
或
svn export F:/project1 F:/project
此时版本库或工作副本中的内容都被导出到F:/project文件夹中。注意,F:/project会自动生成,并且该文件夹中的内容比较干净,不包含版本控制信息。
生成补丁。在命令行中定位到F:/project1下,执行以下任意一条命令:
svn diff -r10:19 1.txt > test.diff(在1.txt的两个指定版本间比较)
svn diff -r10 1.txt > test.diff(在1.txt的当前版本与指定版本间比较)
svn diff -r10 > test.diff(在当前模块的当前版本与指定版本间比较)
svn diff file:///e:/svn/project file:///e:/svn/new_project > test.diff(在两个路径间比较)
会生成输出信息到文件test.diff中。这些版本间的差异信息被称为补丁,而test.diff文件则称为补丁文件。注意,当不写“> test.diff”时,例如svn diff -r10:19 1.txt,信息会直接输出到屏幕。
三、子命令别名
在SVN中,一些子命令是有别名的,通过别名可以简化操作,例如commit子命令的别名为ci,这样就可以将命令:
svn commit -m ""
简化为:
svn ci -m ""
下面列出了前面用到的一些子命令的别名:
checkout:co
commit:ci
copy:cp
delete:del,remove,rm
diff:di
help:?,h
list:ls
move:mv,rename,ren
propdel:pdel,pd
propedit:pedit,pe
propget:pget,pg
proplist:plist,pl
propset:pset,ps
status:stat,st
switch:sw
update:up
四、update子命令的输出
在执行update或commit子命令的过程中,会看到一些标志性的字母,那么这些字母表示些什么呢?
A 在工作副本中添加文件并将这一操作更新到CVS仓库
D 在工作副本中删除文件并将这一操作更新到CVS仓库
U 更新工作副本中不存在或者没有修改过的文件
C 有冲突
G 合并成功
注意,SVN的update子命令可能会有两列输出,两列输出时第一列表示的是文件本身,第二列表示的是文件属性。