实景讲解Subversion在项目中的应用(Eric.li@cereson.com)一、概述Subversion是一个目前正被很多公司采用的代码版本工具,通过代码的集中版本控制,可以在开发过程中避免很多沟通的成本。而养成一种良好使用版本控制的习惯将会给我们的开发带来更多的便利。因Subversion对tag和branch并没有做明确的区分,因此,除非项目管理需要,下文将不特别区分tag和branch的使用。二、建立项目每个项目的开启,都会有项目经理、架构师或项目主要负责人员新建一个项目的框架,并上载到Subversion服务器作为整个项目开发人员的共同输入。——下图就是一个新建的项目onionbulb,我们在Eclipse中打开它,我们可以看到目前该项目的状态完全是一个本地项目,没有任何集中的版本信息。(我们采用Eclipse+PyDev作为开发环境,采用其他开发环境也会有类似的场景)下面,我们将使用开发环境将该项目上载到Subversion服务器。在项目浏览器中选中项目,点击鼠标右键,选择Team→ShareProject,将会开启一系列窗口设置所需的Subversion服务器信息。逐个填写版本仓库类型SVN,仓库地址,存储路径,备注等信息。这里推荐采用在版本控制中被广泛采用的trunk,tags,branches三路径并列的目录结构,因为我们是初次载入,因此,我们采用的存储路径类似:svn://127.0.0.1/onionbulb/trunk完成这一步,即可在服务器上建立一个项目空间,并形成onionbulb/trunk这样的目录结构。不过,目前所有代码文件并没有上载到版本服务器上。我们可以使用Subversion浏览器之类的工具查看目前版本服务器上的信息。它显示如下图信息:目前它还是一个空的目录结构,先不管它。当然,你也可以马上在这里将tags,branches目录建立好,形成如下这样的目录结构。下面,我们就可以将代码上载到服务器上了。不过,在上载代码文件之前,建议设置一系列的svn:ignore来忽略部分我们不希望出现在版本服务器上的文件,比如数据文件,中间文件,临时文件等等。参考系统提示,可以按照项目的需要,选择多种忽略模式。对每个项目,设置一个好的忽略集合,可以很好的避免在日复一日的开发之后,版本服务器变成一个巨大的垃圾桶,什么文件都有。现在,可以提交所有代码了。从项目右键菜单选择Team→Commit...即可打开提交窗口,选择文件进行提交。提交完文件后,再回到项目视图,我们可以看到所有已经提交到版本服务器的文件、目录的图标都多了一个小圆柱体,而被忽略的data目录则什么标记都没有。三、独立开发项目建好了,现在一个开发者john加入了这个开发项目,他首先打开SVNRepositoryExploring视图。找到onionbulb项目。为了让开发人员之间相互尽可能不相互打扰,我们推荐每个开发人员都建立自己的开发branch,在一个功能的开发过程中,这个开发人员的开发工作都在自己的branch上完成,只要在这个branch建立之前这个项目是运行正常的,在这个功能开发的持续过程中,只有该开发人员的代码在改变,在影响项目,从而将开发组员相互之间的影响降到最低。在SVNRepositoryexploring视图中选择项目所在目录,点击右键,选择Branch/Tag,在branches目录中建立个人branch,并且选择从HEAD拷贝仓库中的文件。这样,我们就建立了一个开发者个人的branch。这时,整个代码版本库结构如下:branch做好之后,john就可以将代码同步到本地的开发环境了。在Eclipse中,选择Import,选择CheckoutProjectsfromSVN。在checkout的过程中,选择刚才建立的branch。Import结束后,我们就有了如下所示的项目结构。注意,这里项目名边上的版本仓库路径显示的是onionbulb/branches/john。而不是前文看到的onionbulb/trunk。本地开发环境建立好之后,john就可以进入正常开发流程。添加代码,提交到版本服务器。在经过一段时间的开发,john已经完成了一个功能模块login,而且所有代码本地测试验证通过,提交到了john自己的branch。现在怎么让同组的其他开发人员都使用这个模块呢?要完成这个功能,我们就必须使用版本控制中经常讲到的分支合并功能了。因为是要将john这个分支合并到trunk中去,所以我们先要将当前开发环境切换到trunk分支。选择onionbulb项目,点击鼠标右键,选择Team→SwitchtoanotherBranch/Tag/Revision,在ToURL中选择trunk目录,点击确认即可将开发环境切换到trunk。这时新开发的login模块的文件在本地都被删除了,没关系,它们都很安全的存在在版本服务器上的john分支中呢。下面,马上开始合并了,点击鼠标右键,选择Team→Merge...功能。在弹出的对话框中,选择merge的类型。因为我们是要将分支合并到主干,所以我们选择Reintegrateabranch选项。在Mergefrom中选择我们刚才使用的john分支。选择冲突合并策略,并确认。之后,本地环境出现了login模块的文件,并且标记为新增文件。再次commit所有新增文件到主干。注意,在这次commit的时候建议注明是从什么版本合并到主干的。如Mergedjohnbranchchangesr7:9intothetrunk这样,john开发的login模块就成功的合并到了trunk上,可以供所有人员使用了。四、合作开发刚才的场景是john一个人单独开发的场景,在我们正常的开发过程中,一般会多人合作开发项目,那么遇到这种环境,该怎么使用Subversion呢?现在我们再引入一个开发人员jim。他在john开发完login模块之后加入开发组。因此,他也从trunk新建了一个自己的分支onionbulb/branches/jim。这个时候,这个分支的代码包含了项目框架代码和login模块代码两个部分。按照第三节的步骤,jim开始开发一个新的模块passwd,并且成功完成所有功能,正确合并到了trunk中。而同时,john目前自己的分支也包含了这两个部分的代码,接着又开始开发一个新的模块account。在john的开发过程中,完全不知道jim的开发进度,也不关心。但他的开发速度慢于jim,因此在他向trunk合并代码的时候,trunk中的代码包含了框架代码、login模块、passwd模块;而他自己的分支上则包含了框架代码、login模块、account模块。下面,john需要将他的代码安全的合并到trunk上,并且不影响jim已经提交成功的代码。同样的,john切换到了trunk。可以看到,trunk中没有account模块,而有passwd模块。同样,选择Team→Merge功能,选择Reintegrateabranch选项,得到如下结果:接下来,同样的committrunk中新增的account模块。这样,trunk中就有了框架代码、login模块、account模块、passwd模块。而一旦john切换到自己的分支,他就会发现自己这里没有passwd模块,如下图。如果john接下来的开发中不会使用到passwd模块,那么没有任何问题,john什么都不需要做,只需要在自己的分支上继续开发就成。而如果在接下来的开发中,john需要使用passwd模块,那怎么办?其实,Subversion也提供了从主干到分支的合并。在john的开发环境中,切换到自己的分支版本,再次选择Merge菜单项。在合并类型的选择中,选择Mergearangeofrevision选项,将主干上的一部分版本或所有版本的变更反应到自己的分支中。在做这一次合并的过程中,要注意尽量不要选择全部版本合并,而要选特定版本号,把你自己刚才从分支到主干的合并的版本号去掉,要不在本地上次合并中的文件会报一个文件冲突,但这些文件本身除了版本号之外没有什么差异。如果已经发生这种情况,只要手工将这些文件MarkResolved即可。合并成功后,john本地文件中就多了passwd模块,再次commit新增的文件到自己的branch即可完成所有的合并工作。进入下一步的正常开发。当然,也有更简单比较偷懒的方法,就是删除原有分支,重新再建立一个新的分支,这样不需要做任何从主干到分支的合并。注意,删除分支的时候,也需要将本地的开发环境中整个项目都删除,重新从Subversioncheckout,或者checkout到一个新的目录,形成一个新的项目。删除分支方法如下图:五、发生冲突刚才的例子都是没有冲突需要解决的情况,在实际的开发过程中,我们还是很有可能遇到一些代码的冲突的,特别是一些公共模块的开发中,经常需要人工介入解决冲突。例如,jim在开发的过程中,修改了文件account.py,在该文件中,对index函数修改了atest的值,增加了一个jimtest=“none”的赋值,增加了一个jimtest的函数。并且已经将这个修改正常提交到了trunk上。如下所示:classAccountController(BaseController):defindex(self):#Returnarenderedtemplate#returnrender('/account.mako')#or,returnaresponseatest=jimbtest=Nonejimtest=nonereturn'HelloWorld'deftestjim(self):#Returnarenderedtemplate#returnrender('/account.mako')#or,returnaresponseatest=nonebtest=Nonereturn'HelloWorld'现在john在刚才同步好的版本上也进行了相应的开发,同时也修改了account.py这个文件,对其中的函数index修改了其中atest的值,增加了一个johntest的赋值,并也添加了一个函数testjohn。现在john也已经将这个修改提交到他自己的分支,并且马上要合并到主干。代码如下:classAccountController(BaseController):defindex(self):#Returnarenderedtemplate#returnrender('/account.mako')#or,returnaresponseatest=johnbtest=Nonejohntest=nonereturn'HelloWorld'deftestjohn(self):#Returnarenderedtemplate#returnrender('/account.mako')#or,returnaresponseatest=nonebtest=Nonereturn'HelloWorld'john将开发环境切换到trunk上,如前所述,运行Merge命令,选择reintegrateabranch将自己分支上的修改合并到trunk上。这时他会遇到如下冲突:在对话框中选择稍后处理(当然如果你逐够自信,也可以选择其他两项)。确定后,Subversion将初步合并文件,做好手工合并的准备。处理完后,Eclipse中项目显示如下:上图就是文件合并冲突的结果。其中代码合并后形成如下样式,其中跟======之间的内容是原始内容(注意你现在的工作区是trunk,你的原始内容就是trunk上的最新内容,不是你自己的分支哦),=====跟之间的内容是合并过来的内容(就是你的分支内容啦)classAccountController(BaseController):defindex(self):#Returnarenderedtemplate#returnrender('/account.ma