【软件工程技术之程序版本控制】SVN的使用——基本知识,Server搭建与Client日常使用维护
作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/
本文综合修改自网上几篇Blog和自己手头的资料,原始出处均不详,如有版权问题请及时与我联系并提供原始文字链接。这里以Windows下客户端和服务器进行举例,Linux有对应,原理相同。
一、基本知识:
1.为什么要使用SVN?
· 程序员编写程序的过程中,每个程序都会生成很多不同的版本.
· 这就需要程序员能有效的管理代码,在需要的时候可以迅速,准确取出相应的版本
· 任何需要管理频繁信息改变的地方都需要它,这就是Subversion的舞台
2. Subversion是什么?
· Sub Version(简称SVN)是版本管理工具
· Subversion是一个自由/开源版本控制系统
· 一组文件存放在中心版本库, 记录每一次文件和目录的修改
· Subversion可以通过网络访问它的版本库
· 任意数量的客户端可以连接到版本库,读写这些文件.
· 通过写,别人可以看到这些信息,通过读数据,可以看到别人的修改
3.Subversion的特性有哪些?
· 版本化的目录:Subversion实现了一个可以跟踪目录树更改的“虚拟”版本化文件系统
· 真实的版本历史:可以新增一个具有干净历史的文件
· 原子提交:可以让用户构建一个要提交修改的逻辑块,防止部分修改提交到版本库
· 版本化的元数据:每一个文件或目录都有一套属性—键和它们的值
· 可选的网络层:在版本库访问方面有一个抽象概念,利于人们去实现新的网络机制
· 一致的数据操作:文件是建立在二进制文件区别算法基础上的
· 有效率的分支和标签:建立分支与标签时只是拷贝整个工程,使用了一种类似于硬链接的机制
· 可修改性:由一系列良好的共享C库实现,具有定义良好的API
4.Subversion的原理是什么?
锁定-解锁
Subversion主要采用拷贝-修改-合并模型,配合锁定-解锁模型管理数据的共享
5.Subversion的基本工作流程是什么?
二、SVN服务器的搭建与配置:
1.启动:安装好VisualSVN Server后,运行VisualSVN Server Manger,下面是启动界面:
2.添加一个代码库【Repository】:如下图:
按上图所示,创建新的代码库,在下图所示的文本框中输入代码库名称:
注意:上图中的CheckBox如果选中,则在代码库StartKit下面会创建 trunk、branches、tags三个子目录;不选中,则只创建空的代码库StartKit。
点击OK按钮,代码库就创建成功了。
3.安全性设置:
在左侧的Users上点击右键:
输入上面的信息,点击OK,我们就创建一个用户了。按照上面的过程,分别添加用户 Developer1、tester1、manager1,好了,我们开始添加这些用户到我们刚才创建的项目里:
点击上图中的"Add..."按钮,在下图中选择我们刚才添加的用户,点击OK按钮:
说明:大家可能注意到了下图中的Groups,是的,你也可以先创建组,把用户添加到各个组中,然后对组进行授权,操作比较简单,在此略过。
按照下图所示,分别对用户【或组】进行授权:
点击"确定"按钮,上面的用户就具有了访问代码库的不同权限。
二、SVN客户端的使用方法——TortoiseSVN的基本使用方法
1.导入源代码到SVN服务器
假如我们要把项目StartKit的源代码签入到SVN Server上的代码库中里,首先右键点击StartKit文件夹,这时候的右键菜单如下图所示:
点击Import,弹出下面的窗体,其中http://zt.net.henu.edu.cn/ 是服务器名,svn是代码仓库的根目录,StartKit是我们在SVN Server上建立的一个代码库:
说明:左下角的CheckBox,在第一次签入源代码时没有用,但是,在以后你提交代码的时候是非常有用的。
点击OK按钮,会弹出下面的窗体,要求输入凭据:
在上面的窗体中输入用户名和密码,点击OK按钮:
如上图所示,好了,源代码已经成功导入SVN服务器了。这时候团队成员就可以从SVN服务器上的导出源代码到自己的机器了。
2迁出源代码到本机
就是从版本库中取出某个目录的拷贝到本机上某个目录的操作,叫做CheckOut,这个操作是工作的基础。
在本机创建文件夹,右键点击Checkout,弹出如下图的窗体:
在上图中URL of Repository:下的文本框中输入svn server中的代码库的地址,其他默认,点击OK按钮,就开始迁出源代码了。
说明:上图中的Checkout Depth,有4个选项,分别是迁出全部、只签出下一级子目录和文件、只导出文件、只签出空项目,默认的是第一项。上面的例子中,我们也可以使用web的方式访问代码库,在浏览器中输入http://zt.net.henu.edu.cn/svn/StartKit/
这时候也会弹出对话框,要求输入用户名和密码,通过验证后即可浏览代码库中的内容。
打开目录,可以看到如下图的文件夹结构:
一旦你对文件或文件夹做了任何修改,那么文件或文件夹的显示图片机会发生变化。下图中我修改了其中的二个文件:
看一下不同状态所对应的图片:
3.提交修改过的文件到SVN服务器
例如,修改了位于Model文件中的二个文件ImageInfo.cs和NewsInfo.cs
注意:提交源代码到服务器时,一定确保本机的代码是最新版本,否则可能提交失败,或者造成版本冲突。
在Model文件夹上点击右键或在Model文件下的空白处点击右键,点击SVN Commit…弹出下面的窗体:
点击OK按钮后,弹出如下图的窗体:
commit的功能不仅仅是上传,他会和服务器上面的文件进行对比,假如你更新了某个文件而服务器上面也有人更新了这个文件,并且是在你checkout之后做的更新,那么它会尝试将你的更新和他人的更新进行融合(merge),假如自动 merge不成功,那么报告conflict,你必须自己来手动merge,也就是把你的更新和别人的更新无冲突的写在一起。commit的时候,最好填写Log信息,这样保证别人可以看到你的更新究竟做了写什么。这就相当于上传文件并且说明自己做了那些修改,多人合作的时候log非常重要。
4.添加新文件到SVN服务器
假设我们在Model文件下添加一个新的类文件UserInfo.cs,在文件UserInfo.cs上点击右键,点击TortoiseSVN=>>Add,弹出如下图的窗体:
选中UserInfo.cs文件,点击OK按钮,这样并没有将这个文件提交到SVN服务器,只是将这个文件标记为源代码库库中的文件,并将其状态置为修改状态。之后,我们要再SVN Commit这个文件一次,才可以将其真正提交到SVN服务器上的代码库中。添加文件夹的步骤也是一样的。
5.更新本机代码与SVN服务器上最新的版本一致
这个也很简单,只要在需要更新的文件夹上点击右键或在该文件下的空白处点击右键,点击SVN Update,就可以了。一般在提交修改之前,必须运行一下update操作来合并别人作出的新更改。
注意:更新操作可能会因为版本冲突而失败,这是可以使用合并【Merge】或其他方法解决;也可能因为锁定【Get Lock】而失败,这是需要先解锁【Release Lock】。 如果本地的代码已经被修改,和commit一样会先进行merge,不成功的话就会报告conflict 。另外需要注意的是,假如别人删除了某个文件,那么更新之后你在本地的也会被删除。
6.重命名文件或文件夹,并将修改提交到SVN服务器
只要在需要重命名的文件或文件夹上点击右键,点击TortiseSVN=>Rename…,在弹出的窗体中输入新名称,点击OK按钮,就可以了。此方法也不是直接重命名,而是将该文件或文件夹的名称标记为重命名后名称,也需要我们使用SVN Commit提交到SVN服务器后才真正重命名。
7.删除文件或文件夹,并将修改提交到SVN服务器
最简单就是,你直接删除文件或文件夹,然后使用SVN Commit提交更新到SVN服务器。另外一种方法是在你要删除的文件或文件夹上点击右键=>>TortoiseSVN=>> Delete删除,此方法也不是直接删除,而是将该文件或文件夹的状态置为删除,也需要我们使用SVN Commit提交到SVN服务器后才真正删除。
8. 如何在同一个在一个工程的各个分支或者主干之间切换
使用tortoise SVN—>switch ,在URL中输入branch或trunk的url地址。
9.比较两个版本之间的差别
· 如果你想看到你的本地副本有哪些更加,只用在资源管理器中右键菜单下选TortoiseSVN→ 比较差异。
· 如果你想查看主干程序(假如你在分支上开发)有哪些修改或者是某一分支(假如你在主干上开发)有哪些修改,你可以使用右键菜单。在你点击文件的同时按住Shift键,然后选择TortoiseSVN→ URL比较。在弹出的对话框中,将特别显示将与你本地版本做比较的版本的URL地址。
10. 如何为一个SVN主工程建立分支或tag
分支的基本概念:开发的一条线独立于另一条线,如果回顾历史,可以发现两条线分享共同的历史,一个分支总是从一个备份开始的,从那里开始,发展自己独有的历史。
SVN分支的管理实际上就是把不同的分支用不同的文件保存,因此你在取得新版本的时候会发现,不同分支的最新文件也会被获取下来。创建tag操作,相当于把当前的代码版本复制一份到其他地方,然后以这个地方为出发点进行新的开发,与原来位置的版本互不干扰。
· 选择你要产生分支的文件,点击鼠标右键,选择[分支/标记...]
· 在[至URL(T)]输入框中将文件重命名为你的分支文件名,输入便于区分的日志信息,点击确认。
· 在SVN仓库中会复制一个你所指定的文件,文件名称就是你所命名的,但是在你的本地目录上看不到新建的分支文件名,要使你的文件更新作用到你的分支上,你必须选择文件,点击鼠标右键,选择[切换...],选择你重命名的文件,点击确定即可。这样你的本地文件就和分支文件关联上了,不要奇怪,这时本地目录上看到的文件名仍然为旧的文件名。
11.冲突解决
当文件发生冲突时,SVN会额外创建3个不受版本控制的文件,同时被冲突文件如果能够合并,会在被冲突文件内部留下冲突记录。例如,冲突的文件为plugin.c,BASE版本是1458,HEAD为1459,会产生3个临时文件plugin.c.mine,plugin.c.r1458,plugin.c.r1459,
解决思路:A. 手动修改被冲突文件 B. 放弃自己的更改。实际中,解决办法很灵活,一般需要与他人商量
注意:由于这3个文件是在Update后才创建的,而Update之后,工作拷贝的BASE目录已经变成更新后的版本了,所以放弃自己的更改会回到新版本。如果不是用Revert的方法解决冲突的话,由于那3个临时文件留在那里,会使Subversion认为冲突没有解决,所以要运行resolved告诉SVN冲突解决或者删除临时文件。
三、Linux下SVN客户端的使用:
1.Checkout:
语法:checkout(co) URL[@REV]... [PATH]
可以指明Checkout的版本号,默认CheckOut操作是针对HEAD版本进行的,大多数情况下我们需要HEAD版本,但如果需要历史版本,可以用-r(--revision)参数或者是用“@版本号”的形式
例:
· … -r 1452 会检出1452版,如果存在的话
· … -r {“2007-05-05”} 会检出最接近这个日期的版本
· …/trunk@1452 效果同第1个例子
递归与不递归,-N:不递归(仅针对顶层目录),否则目录递归(默认,常用)
· 例1:svn co svn://218.94.9.38/svnrepos/skizcorp/trunk 在当前目录建立一个trunk目录,里面是工作拷贝
· 例2:svn co svn://localhost/torm I:/PROJECTS/torm 会在I:/PROJECTS/目录下创建torm目录,里面存放工作拷贝
2.Update:
语法: update(up) [PATH...]
把版本库的修改同步到本地的过程是Update。
· 例1:up 直接把工作拷贝更新到最新版(HEAD版)
· 例2:up -r 2007 更新到2007版
· 例3:up doc/design 只更新doc/design下的文件
-r和-N参数仍然有用
Update会修改被更新目录的BASE版本号。某个文件的BASE版本是指存放在管理目录.svn中的该文件拷贝的版本,Revert会使该文件回到BASE版本
做Update操作时,SVN会打印出受影响文件的状态,有以下几种:
· A Added
· D Deleted
· U Updated
· C Conflict
· G Merged
注意:若提示C,表示冲突,冲突可以用status命令加-u参数来预测
3.Revert:
Revert是指放弃对某个文件的修改,把该文件的内容回复和BASE版本相同,也就是,把该文件的状态回复到未修改状态
语法:revert 文件/路径
例子:
· 例1 revert abc.c 丢弃对abc.c的所有修改
· 例2 revert src/edu/nju/pojo 放弃对此目录下所有文件的修改
四、注意事项:
1.实际上,从你把源代码迁签入SVN服务器开始,每一个版本的数据和文件,就算是你已经删除了的,也都可以随时迁出。
2.向SVN服务器提交源代码的时候,一定不要提交bin、obj 等文件夹,否则会很麻烦。但是web项目的bin目录除外,但是web项目的bin目录中的引用其他项目而生成的dll不需要提交。
3.对于第三方库或程序集,不要简单从他们的安装位置引用,而是在你的解决方案下,添加一个Library的目录,把需要的程序集或者库文件复制到这里,然后从Library目录引用。
4.关于checkout和export的区别,当你要发布或编译的时候,最好采用export,它不会引入svn的附加文件,这样文件结构显得比较干净。而当你需要修改和提交的时候,用checkout,它会在你本地建立一个工作区,Checkout到某处的代码,将会被 TortoiseSVN监视,里面的文件可以享受各种SVN的服务。
5.假如你需要给带有绿色对勾文件改名或者移动它的位置,请不要使用windows的功能,右键点击它们,TortoiseSVN都有相应的操作。想象这些文件已经不在是你本地的东西,你的一举一动都必须让Tortoise知道:
· 把一个文件加入SVN版本控制,用add命令
· 从版本控制中移除,用delete(rm, remove)命令
· 移动或者重命名,用move(rename)命令
· 拷贝,用copy命令
· 创建目录,用mkdir命令
· ……
6.假如修改了某个文件但是你后悔了,可以右键点击它选择Revert,它将变回上次checkout时候的情况,或者Revert整个工程到任意一个从前的版本。
7.使用Commit时注意: 如有多个文件需要同时提交,同时文件在不同的目录下,必须找到这些文件的最上层目录上点击 Commit,TortoiseSVN会搜索被点击目录以及该目录下所有的文件,并将修改变动的文件罗列在列表中。 仔细查看列表中的文件,确定哪些文件时需要更新的,如果不需要更新某个已经变化了的文件,只需要在该文件上点击右键,选择还原操作;如遇到文件冲突(冲突:要提交的文件已被其他人改动并提交到版本库中)要启用解决冲突功能。
8.对于branches、tags、trunk这三个目录,并不是subversion必需的,而是被总结的一种良好的团队开发习惯,其使用方法为:
· 开发者提交所有的新特性到主干。 每日的修改提交到/trunk:新特性,bug修正和其他。
· 这个主干被拷贝到“发布”分支。 当小组认为软件已经做好发布的准备(如,版本1.0)然后/trunk会被拷贝到/branches/1.0。
· 项目组继续并行工作,一个小组开始对分支进行严酷的测试,同时另一个小组在/trunk继续新的工作(如,准备2.0),如果一个bug在任何一个位置被发现,错误修正需要来回运送。然而这个过程有时候也会结束,例如分支已经为发布前的最终测试“停滞”了。
· 分支已经作了标签并且发布,当测试结束,/branches/1.0作为引用快照已经拷贝到/tags/1.0.0,这个标签被打包发布给客户。
· 分支多次维护。当继续在/trunk上为版本2.0工作,bug修正继续从/trunk运送到/branches/1.0,如果积累了足够的 bug修正,管理部门决定发布1.0.1版本:拷贝/branches/1.0到/tags/1.0.1,标签被打包发布。
· 一般建立最初的repository时,就建好这三个目录,把所有代码放入/trunk中,如:要将project1目录下的代码导入 repository,project1的结构就是:project1/branches,project1/tags,project1 /trunk,project1/trunk/food.c,project1/trunk/egg.pc……,然后将project1目录导入 repository,建立最初的资料库。然后export回project1,作为本地工作目录。
9.检验修改:
· 通过status命令可以检查工作拷贝的状态
· 通过diff命令可以检查更改的内容
10. 关于SVN的开发模式(转载)
Subversion有一个很标准的目录结构,是这样的。
比如项目是proj,svn地址为svn://proj/,那么标准的svn布局是
svn://proj/ | +-trunk +-branches +-tags
这是一个标准的布局,trunk为主开发目录,branches为分支开发目录,tags为tag存档目录(不允许修改)。但是具体这几个目录应该如何使用,svn并没有明确的规范,更多的还是用户自己的习惯。
对于这几个开发目录,一般的使用方法有两种。我更多的是从软件产品的角度出发(比如freebsd),因为互联网的开发模式是完全不一样的。
第一种方法,使用trunk作为主要的开发目录。
一般的,我们的所有的开发都是基于trunk进行开发,当一个版本/release开发告一段落(开发、测试、文档、制作安装程序、打包等)结束后,代码处于冻结状态(人为规定,可以通过hook来进行管理)。此时应该基于当前冻结的代码库,打tag。当下一个版本/阶段的开发任务开始,继续在trunk进行开发。
此时,如果发现了上一个已发行版本(Released Version)有一些bug,或者一些很急迫的功能要求,而正在开发的版本(Developing Version)无法满足时间要求,这时候就需要在上一个版本上进行修改了。应该基于发行版对应的tag,做相应的分支(branch)进行开发。
例如,刚刚发布1.0,正在开发2.0,此时要在1.0的基础上进行bug修正。
按照时间的顺序
1. 1.0开发完毕,代码冻结
2. 基于已经冻结的trunk,为release1.0打tag
此时的目录结构为
svn://proj/
+trunk/ (freeze)
+branches/
+tags/
+tag_release_1.0 (copy from trunk)
3. 2.0开始开发,trunk此时为2.0的开发版
4. 发现1.0有bug,需要修改,基于1.0的tag做branch
此时的目录结构为
svn://proj/
+trunk/ ( dev 2.0 )
+branches/
+dev_1.0_bugfix (copy from tag/release_1.0)
+tags/
+release_1.0 (copy from trunk)
5. 在1.0 bugfix branch进行1.0 bugfix开发,在trunk进行2.0开发
6. 在1.0 bugfix 完成之后,基于dev_1.0_bugfix的branch做release等
7. 根据需要选择性的把dev_1.0_bugfix这个分支merge回trunk(什么时候进行这步操作,要根据具体情况)
这是一种很标准的开发模式,很多的公司都是采用这种模式进行开发的。trunk永远是开发的主要目录。
第二种方法,在每一个release的branch中进行各自的开发,trunk只做发布使用。
这种开发模式当中,trunk是不承担具体开发任务的,一个版本/阶段的开发任务在开始的时候,根据已经release的版本做新的开发分支,并且基于这个分支进行开发。还是举上面的例子,这里面的时序关系是。
1. 1.0开发,做dev1.0的branch
此时的目录结构
svn://proj/
+trunk/ (不担负开发任务 )
+branches/
+dev_1.0 (copy from trunk)
+tags/
2. 1.0开发完成,merge dev1.0到trunk
此时的目录结构
svn://proj/
+trunk/ (merge from branch dev_1.0)
+branches/
+dev_1.0 (开发任务结束,freeze)
+tags/
3. 根据trunk做1.0的tag
此时的目录结构
svn://proj/
+trunk/ (merge from branch dev_1.0)
+branches/
+dev_1.0 (开发任务结束,freeze)
+tags/
+tag_release_1.0 (copy from trunk)
4. 1.0开发,做dev2.0分支
此时的目录结构
svn://proj/
+trunk/
+branches/
+dev_1.0 (开发任务结束,freeze)
+dev_2.0 (进行2.0开发)
+tags/
+tag_release_1.0 (copy from trunk)
5. 1.0有bug,直接在dev1.0的分支上修复
此时的目录结构
svn://proj/
+trunk/
+branches/
+dev_1.0 (1.0bugfix)
+dev_2.0 (进行2.0开发)
+tags/
+tag_release_1.0 (copy from trunk)
6. 选择性的进行代码merge
这其实是一种分散式的开发,当各个部分相对独立一些(功能性的),可以开多个dev的分支进行开发,这样各人/组都不会相互影响。比如dev_2.0_search和dev_2.0_cache等。但是这样merge起来就是一个很痛苦的事情。
这里要注意一下的,第六步进行选择性的merge,是可以当2.0开发结束后一起把dev_1.0(bugfix用)和dev_2.0(新版本开发用)merge回trunk。或者先把dev_1.0 merge到dev_2.0,进行测试等之后再merge回trunk。
这两种方法各有利弊,第一种方法是可以得到一个比较纯的dev_2.0的开发分支,而第二种方法则更加的保险,因为要测试嘛。
以上呢,就是我说的两种开发模式了,具体哪种好,并没有定论。这里大致的说一下各自的优缺点
第一种开发模式(trunk进行主要开发,集中式):
优点:管理简单
缺点:当开发的模块比较多,开发人数/小团队比较多的时候,很容易产生冲突而影响对方的开发。因为所有的改动都有可能触碰对方的改动
第二重开发模式(分支进行主要开发,分散式):
优点:各自开发独立,不容易相互影响。
缺点:管理复杂,merge的时候很麻烦,容易死人。