天天看点

《版本控制之道:使用subversion》读书笔记

[align=center][img]http://images.china-pub.com/ebook30001-35000/34229/zcover.jpg?2010-4-1%2014:27:48[/img][/align]

svn也是平时开发中用到最多的一个工具. 一些基本的操作用起来也是没有问题的, 看这本书算是对已有svn知识的一个扩展, 学到了很多以前不是很了解的知识, 以前我们都是通过小乌龟客户端来用, 这里知道了很多通过命令行方式来使用svn, 比较有价值的是如何实现多分支开发以及合并, 貌似跟我们目前的开发模式正好相反. 个人觉得书里面的开发模式要比我们要好一些, 我们采用在分支开发, 合并到主干发布, 书里面是主干开发, 分支发布, 这样在发布后期冻结发布分支代码, 转到主干并行开发更有效. 不知道我们的scm有没有看这本书呢?

要决定在版本仓库中放什么不放什么有一个简单的测试, 只要问问"如果我们没有这个东西的最新版本, 我们是不是可以构建, 测试, 并交付我们的程序?" 如果回答是不能, 那么它就应该被放在项目仓库中.

checkout和export的区别在于, 当你export的时候, 你得到的不是一个工作拷贝, 而是一个项目仓库文件的快照, 即没有.svn这样的版本控制文件, 这个在某些情况下是比较有用的, 比如打包.

extrenals 就是把另外一个subversion项目仓库的位置包含到你项目的任何目录中.

subversion采用整体版本号方式, 整个项目仓库从版本号0开始, 签入一个改变之后, 项目仓库的版本号增加1, 以此类推.

subversion的项目仓库版本号有点像马克笔, 在每次提交的时候给项目仓库中的所有文件画一条线.

subversion的版本号并不是用来表明某个文件或者某些文件变动了多少. 不管版本号是怎么分配的, 因为一个改动可能是一个修改了文件每一行的改动, 如果你想知道到底有多少改动, 或许更好的方式是使用版本控制系统浏览历史的功能, 直接去观察改动本身.

svn --version 查看当前机器安装svn的情况

svn import -m "xxx" . file:///home/svn-repos/proj/trunk 表示把一些文件导入到项目仓库中, -m 选项 使得你可以给本次导入操作关联一条消息.

svn status Day.txt 获得指定文件的状态, 如果现实为M, 表示Subversion认出这个文件已经在本地被修改过了.

要签出一个指定版本7的项目

svn co -r 7 svn://svn-repos/proj/trunk

使用svn update的时候, 有一些提示, 分别表示是意义:

A 代表仓库中新文件, 已经添加到你的工作拷贝中了

U 表示你的工作拷贝中文件过期了, svn已经把你工作拷贝的文件更新为最新版本.

D 代表该文件已经从仓库中删除了, svn已经把它从你的工作拷贝中删除了.

G 表示在你的工作拷贝中的文件已经过期了, 而你本地还做了修改, svn已成功了将仓库中的版本和你本地的修改合并到了一起.

C 表示你工作拷贝中的文件已经过期了, 而你本地也做了修改, svn尝试合并, 但是遇到了冲突

svn还可以给文件关联元数据, 也就是属性, 比如java文件可以有一个关联的"审阅者"属性, 告诉你谁最后审阅过这个文件

svn propset checked-by "Mike" Number.txt 表示把Number.txt的checked-by属性的值设置为Mike

关键字展开

如果你给文件设置了svn:keywords 属性 然后 你的文件中使用了下面的关键字, 那么在提交的时候, svn会做替换为相关的内容. 这个功能一般不建议使用

$Date$ 最后一次提交到项目仓库的时间

$Rev$ 最后一次提交到仓库的版本号

$Author$ 最后一个提交文件的用户名

$URL$ 文件在仓库中的完整路径

$Id$ 展开Wie其他关键字的简短摘要

svn propedit svn:ignore .

表示编辑当前目录下的svn:ignore属性, 但是这里需要绑定一下editor, 否则会出现如下提示:

svn: 没有设置 SVN_EDITOR,VISUAL 或 EDITOR 环境变量,运行时的配置参数中也没有 “editor-cmd” 选项

svn会存储所有的文件, 无论他们是文本, 图片, 编译过的代码, 或者电影, 在项目仓库中使用的都是二进制格式.

svn copy的用法

svn copy Number.txt Data.txt

svn commit -m "copy ...."

在你的本地工作目录copy一个文件或者目录会创建拷贝, 并且把它们列到要添加到项目仓库中的文件列表中. 这里因为svn记住了文件的历史, 当询问Data.txt的历史也会给出Number.txt的历史.

svn move用来重命名文件

svn move Time.java Clock.java

实际上是先添加然后删除, 现在已经提供了rename命令

如果要执行基于项目仓库的重命名, 使用两个类似最开始签出时用到的那样的URL, 比如:

svn move -m "rename ..." \

svn://repos/proj/trunk/util \

svn://repos/proj/trunk/common

比较两个版本(19和20)的差异:

svn diff -r19:20 Clock.java

让svn比较你的工作拷贝和项目仓库中的最新版本, 使用HEAD关键字:

svn diff -r HEAD Clock.java

有时在你开始改动一个文件之前希望能够看到它最近的改动, 可以使用PREV符号版本做到这一点:

svn diff -r PREV:BASE Clock.java

下面的命令创建一个称为mychanges.patch的文件, 包含了对在mydir极其以下文件的所有改动:

mydir> svn diff > machanges.patch

而把这个文件发给维护者, 他只要使用patch命令就能把这个补丁应用到他的源码中去.

mydir> patch -p0 -i mychanges.patch

这里的参数-p0 表示patch在应用它之前, 从文件名种略过零层目录. 如果你包含这个选项, path会包含说它找不到正确的目录

-i表示path使用mychanges.patch作为输入.

解决冲突

如果你决定扔掉改动, 使用项目仓库中的版本, 你要做的就是使用svn revert命令:

svn revert Number.txt

svn update Number.txt

如果决定保留你的改动并且丢掉项目仓库中的那些东西, 拷贝你的那个以.mine结尾的版本文件, 并且告诉svn你已经修正好冲突了

cp Number.txt.mine Number.txt

svn resolved Number.txt

在commit log中写"把timeout改为42" 是没有意义的, 因为只要用一下diff就能看出来, 应该用日志消息来回答为什么要这么做?

如果改动是为了完成一个bug报告, 把跟踪号写到日志消息中. 问题的描述已经在bug数据库中了, 无须在这里再重复.

显示某个版本到某个版本的log:

svn log -r 11920:11900

如果要查看更详细的信息, 可以加上-v(verbose)选项获得更多信息:

svn log -r 24 -v Clock.java

svn blame命令显示一个或者多个文件的内容. 对于每个文件中的每一行, 它会显示改动了那行的最后的版本, 以及做这个改动的人.

丢弃掉对代码的改动, 如果改动是在本地未签入的工作代码中, 那么使用svn revert就能把改动扔掉. 如果已经提交, 要扔掉改动, svn也提供了多种办法.

先使用svn update

然后找出要移除的确切版本, 然后使用svn log查看要扔掉改动文件的版本情况, 如果要移除对版本27的改动, 使用:

svn merge -r 27:26 Contacts.java

这样我们可以让svn计算Contacts.java版本27和26之间的差异, 并把改动应用到工作拷贝中

svn status可以得到你工作目录中文件的信息, 如果指定--show-updates选项的话, svn会与服务器通信显示额外的信息, 如果想避免RSI的话可以使用-u.

文件加锁

svn 1.2引入了可选的文件加锁, 它可以帮助避免由不可合并的文件导入的问题.

任何文件都可以被设置为加锁模式, 这样的话, 在编辑之前就需要先开锁. 设置加锁模式是通过它设置它的needs-lock属性, 任何加锁的文件签出到工作目录中的时候都是只读的.

我们可以使用svn lock命令来获得文件的锁. svn客户端会与服务器通信确保文件不是已经被锁住了, 在获得一个"令牌锁"之后在工作拷贝中把文件标记为可读可写.

一般如果是二进制文件的话, 最好采用加锁的模式, 这个也是采用锁的必要所在.

给一个电子表格文件加锁:

svn propset svn:needs-lock true myexcel.els

svn co -m "lock it"

当我开始编辑myexcel.xls文件的时候, 必须先锁住:

svn lock myexcel.xls -m "i lock it"

在锁住文件加注释可以很好的告诉其他人增进交流沟通.

当你提交一个文件或者目录的时候, svn会自动解开任何你拥有的锁.

svn unlock命令可以用来解开别人对文件的锁, 当不拥有这个锁的时候, 还需要传入--force选项.

这样当原来拥有锁的人尝试提交改动的时候, svn会让他不再拥有对应的锁令牌, 如果要提交, 必须再次锁定该文件才能提交.

关于标签

标签是给一组文件取的符号名称, 每个都有一个特定的版本号, 你可以把标签看成你项目仓库的一个切片, 标记了其中的每一件东西.

标签对于跟踪项目生命周期中的重要事件是相当有用的. 你不在为了给客户发布一个版本记住需要使用版本16的Calendar.java, 版本23的Schedule.java以及版本12的Contacts.java, 相反你可以让标签来帮你记住这些东西, 你可以认为我们可以只使用版本号或者日期就能签出所有的代码来构建一个发布版本. 你可以通过标签签出项目仓库中的不同版本号的文件来获得一个混合版本的工作拷贝.

标签只是你项目仓库特定版本的一个拷贝, 所以没有什么可以阻止人们将改动签入到标签目录, 但是大部分时间最好还是将标签当作只读. 如果你实在要改动标签中的内容, 那么实际上标签就变成分支了.

一种开发模式

在开发进行到快要发布的阶段, 把发布的代码移到移到发布分支中, 发布相关人员在该分支上开发, 项目其余人员继续在主干开发, 当发布本身完成, 用发布号给发布分支打标签(标签只是发布分支在特定时刻的一个简单拷贝), 发布分支可以随后合并回主干.

创建发布分支:

svn mdir -m "create branch" svn://repos/proj/branches

svn copy -m "create relaase branch for 1.0" svn://repos/proj/trunk svn://repos/proj/branch/RB-1.0

把工作拷贝切换到发布分支

svn switch svn://repos/proj/branches/RB-1.0

经过发布开之后, 准备发布了, 给分支打上标签

svn mdir -m "create tag" svn://repos/proj/tags

svn copy -m "tag release 1.0.0" svn://repos/proj/breanches/RB-1.0 svn://repos/proj/tags/REL-1.0.0

现在已经到1.0.4版本了, 要修复一个1.0.0版本上的bug(前提不想使用1.0.5版本), 先签出1.0.0标签

svn co svn://repos/proj/tags/REL-1.0.0 client-fix

然后用后续版本中修复该bug的代码更新client-fix中的相应代码, 这里是common下的代码需要更新

cd client-fix

svn switch svn://repos/proj/breanch/RB-1.0/common common

测试通过之后, 在client-fix处再打一个标签

svn copy -m "tag client 1.0.0 fix" client-fix svn://repos/proj/tags/REL-1.0.0-clientfix

在发布分支中修复简单bug

签出包含bug的代码到本地工作拷贝

编写测试重现bug, 并修正该bug

把改动提交到项目仓库, 并记住新的版本号, 最好将版本号记录到bug跟踪系统中, 方便每个人以后都能找到他.

使用新的版本号把改动合并到其他受影响的分支(可能包括主干)

for example:

rb1.0>svn commit -m "fix bug 3065"

Committed revision 38

这里得到了修正代码的版本38

切换到主干代码

rb1.0>cd ../proj

proj>svn update

proj>svn merge -r37:38 svn:/repos/proj/branches/RB-1.0

U ...

proj> svn commit -m "Merge r38 (fix bug 3065)"

修复复杂bug

为有bug的代码分支创建新的分支

给分支打上标签, 标记bug修复开始

编写测试重现bug, 修改代码让新的测试通过

提交改动到项目仓库

修复完成, 再打上一个标签, 标记bug 结束

使用两个标签来吧修正代码合并到所有其他受影响的分支

for example:

work> svn copy -m "create bugfix branch" svn://repos/proj/branch/RB-1.0 svn://repos/proj/branch/BUG-10512

work> svn copy -m "create bugfix tag start" svn://repos/proj/branch/BUG-10512 svn://repos/proj/branch/PRE-10512

checkout BUG-10512分支做修改

work> svn checkout svn://repos/proj/branch/BUG-10512

bug修复结束, 提交代码, 打上新的标签

work>svn copy -m "create fix bug finish" svn://repos/proj/branch/BUG-10512 svn://repos/proj/branch/POST-10512

将bug修复代码合并到发布分支

work> cd rb1.0

rb1.0>svn update

rb1.0>svn merge svn://repos/proj/tags/PRE-10512 svn://repos/proj/tags/POST-10512

测试一把, 没有问题, 提交代码

rb1.0> svn commit -m"marge fix bug 10512"

使用svn merge将修改代码合并到其他分支或主干

获取分支从创建到当前修改的所有版本和修改

svn log --stop-on-copy

将当前分支的修改合并到其他分支

svn merge -r xx:HEAD svn://repos/proj/branches/my-branch

导入文件到svn仓库

svn import -m "xxx import" svn://repos/proj/trunck

一些有用的命令

在遇到冲突的时候废弃你的改动

svn revert file..

svn update file..

在遇到冲突的时候废弃别人的改动

cp file.mine file

svn resovled file

继续阅读