### 使用分支 在这一点上,你必须理解每一次提交是怎样建立整个新的文件系统树(叫做“修订版本”)的,如果没有,可以回头去读[“修订版本”一节]( "修订版本")。 对于本章节,我们会回到第2章的同一个例子,还记得你和你的合作者Sally分享一个包含两个项目的版本库,`paint`和`calc`。注意[图4.2 “开始规划版本库”]( "图4.2.开始规划版本库"),然而,现在每个项目的都有一个`trunk`和`branches`子目录,它们存在的理由很快就会清晰起来。 **图4.2.开始规划版本库** ![开始规划版本库](https://box.kancloud.cn/2016-08-21_57b8a3344a365.png) 像以前一样,假定Sally和你都有“calc”项目的一份拷贝,更准确地说,你有一份`/calc/trunk`的工作拷贝,这个项目的所有的文件在这个子目录里,而不是在`/calc`下,因为你的小组决定使用`/calc/trunk`作为开发使用的“主线”。 假定你有一个任务,将要对项目做基本的重新组织,这需要花费大量时间来完成,会影响项目的所有文件,问题是你不会希望打扰Sally,她正在处理这样或那样的程序小Bug,一直使用整个项目(`/calc/trunk`)的最新版本,如果你一点一点的提交你的修改,你一定会干扰Sally的工作。 一种策略是自己闭门造车:你和Sally可以停止一个到两个星期的共享,也就是说,开始作出本质上的修改和重新组织工作拷贝的文件,但是在完成这个任务之前不做提交和更新。这样会有很多问题,首先,这样并不安全,许多人习惯频繁的保存修改到版本库,工作拷贝一定有许多意外的修改。第二,这样并不灵活,如果你的工作在不同的计算机(或许你在不同的机器有两份`/calc/trunk`的工作拷贝),你需要手工的来回拷贝修改,或者只在一个计算机上工作,这时很难做到共享你即时的修改,一项软件开发的“最佳实践”就是允许审核你做过的工作,如果没有人看到你的提交,你失去了潜在的反馈。最后,当你完成了公司主干代码的修改工作,你会发现合并你的工作拷贝和公司的主干代码会是一件非常困难的事情,Sally(或者其他人)也许已经对版本库做了许多修改,已经很难和你的工作拷贝结合―当你单独工作几周后运行**svn update**时就会发现这一点。 最佳方案是创建你自己的分支,或者是版本库的开发线。这允许你保存破坏了一半的工作而不打扰别人,尽管你仍可以选择性的同你的合作者分享信息,你将会看到这是怎样工作的。 ### 创建分支 建立分支非常的简单―使用**svn copy**命令给你的工程做个拷贝,Subversion不仅可以拷贝单个文件,也可以拷贝整个目录,在目前情况下,你希望作`/calc/trunk`的拷贝,新的拷贝应该在哪里?在你希望的任何地方―它只是在于项目的政策,我们假设你们项目的政策是在`/calc/branches`建立分支,并且你希望把你的分支叫做`my-calc-branch`,你希望建立一个新的目录`/calc/branches/my-calc-branch`,作为/calc/trunk的拷贝开始它的生命周期。 有两个方法作拷贝,我们首先介绍一个混乱的方法,只是让概念更清楚,作为开始,取出一个工程的根目录,`/calc`: ~~~ $ svn checkout http://svn.example.com/repos/calc bigwc A bigwc/trunk/ A bigwc/trunk/Makefile A bigwc/trunk/integer.c A bigwc/trunk/button.c A bigwc/branches/ Checked out revision 340. ~~~ 建立一个备份只是传递两个目录参数到**svn copy**命令: ~~~ $ cd bigwc $ svn copy trunk branches/my-calc-branch $ svn status A + branches/my-calc-branch ~~~ 在这个情况下,**svn copy**命令迭代的将`trunk`工作目录拷贝到一个新的目录branhes/my-calc-branch,像你从**svn status**看到的,新的目录是准备添加到版本库的,但是也要注意A后面的“+”号,这表明这个准备添加的东西是一份*备份*,而不是新的东西。当你提交修改,Subversion会通过拷贝`/calc/trunk`建立`/calc/branches/my-calc-branch`目录,而不是通过网络传递所有数据: ~~~ $ svn commit -m "Creating a private branch of /calc/trunk." Adding branches/my-calc-branch Committed revision 341. ~~~ 现在,我们必须告诉你建立分支最简单的方法:**svn copy**可以直接对两个URL操作。 ~~~ $ svn copy http://svn.example.com/repos/calc/trunk \ http://svn.example.com/repos/calc/branches/my-calc-branch \ -m "Creating a private branch of /calc/trunk." Committed revision 341. ~~~ 其实这两种方法没有什么区别,两个过程都在版本341建立了一个新目录作为`/calc/trunk`的一个备份,这些可以在[图4.3 “拷贝后的版本库”]( "图4.3.拷贝后的版本库")看到,注意第二种方法,只是执行了一个*立即*提交。 这是一个简单的过程,因为你不需要取出版本库一个庞大的镜像,事实上,这个技术不需要你有工作拷贝。 **图4.3.拷贝后的版本库** ![拷贝后的版本库](https://box.kancloud.cn/2016-08-21_57b8a3345e51b.png) **代价低廉的拷贝** Subversion的版本库有特殊的设计,当你复制一个目录,你不需要担心版本库会变得十分巨大―Subversion并不是拷贝所有的数据,相反,它建立了一个*已存在*目录树的入口,如果你是Unix用户,可以把它理解成硬链接,在这里,这个拷贝被可以被认为是“懒的”,如果你提交一个文件的修改,只有这个文件改变了―余下的文件还是作为原来文件的链接存在。 这就是为什么经常听到Subversion用户谈论“廉价的拷贝”,与目录的大小无关―这个操作会使用很少的时间,事实上,这个特性是Subversion提交工作的基础:每一次版本都是前一个版本的一个“廉价的拷贝”,只有少数项目修改了。(要阅读更多关于这部分的内容,访问Subversion网站并且阅读设计文档中的“bubble up”方法)。 当然,拷贝与分享的内部机制对用户来讲是不可见的,用户只是看到拷贝树,这里的要点是拷贝的时间与空间代价很小,所以你可以随意做想要的分支。 ### 在分支上工作 现在你已经在项目里建立分支了,你可以取出一个新的工作拷贝来开始使用: ~~~ $ svn checkout http://svn.example.com/repos/calc/branches/my-calc-branch A my-calc-branch/Makefile A my-calc-branch/integer.c A my-calc-branch/button.c Checked out revision 341. ~~~ 这一份工作拷贝没有什么特别的,它只是版本库另一个目录的一个镜像罢了,当你提交修改时,Sally在更新时不会看到改变,她是`/calc/trunk`的工作拷贝。(确定要读本章后面的[“转换工作拷贝”一节]( "转换工作拷贝"),**svn switch**命令是建立分支工作拷贝的另一个选择。) 我们假定本周就要过去了,如下的提交发生: - 你修改了`/calc/branches/my-calc-branch/button.c`,生成版本号342。 - 你修改了`/calc/branches/my-calc-branch/integer.c`,生成版本号343。 - Sally修改了`/calc/trunk/integer.c`,生成了版本号344。 现在有两个独立开发线,[图4.4 “一个文件的分支历史”]( "图4.4.一个文件的分支历史")显示了`integer.c`的历史。 **图4.4.一个文件的分支历史** ![一个文件的分支历史](https://box.kancloud.cn/2016-08-21_57b8a334727af.png) 当你看到`integer.c`的改变时,你会发现很有趣: ~~~ $ pwd /home/user/my-calc-branch $ svn log --verbose integer.c ------------------------------------------------------------------------ r343 | user | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: M /calc/branches/my-calc-branch/integer.c * integer.c: frozzled the wazjub. ------------------------------------------------------------------------ r341 | user | 2002-11-03 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: A /calc/branches/my-calc-branch (from /calc/trunk:340) Creating a private branch of /calc/trunk. ------------------------------------------------------------------------ r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: changed a docstring. ------------------------------------------------------------------------ r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: adding this file to the project. ------------------------------------------------------------------------ ~~~ 注意,Subversion追踪分支上的`integer.c`的历史,包括所有的操作,甚至追踪到拷贝之前。这表示了建立分支也是历史中的一次事件,因为在拷贝整个`/calc/trunk/`时已经拷贝了一份`integer.c`。现在看Sally在她的工作拷贝运行同样的命令: ~~~ $ pwd /home/sally/calc $ svn log --verbose integer.c ------------------------------------------------------------------------ r344 | sally | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: fix a bunch of spelling errors. ------------------------------------------------------------------------ r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: changed a docstring. ------------------------------------------------------------------------ r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: adding this file to the project. ------------------------------------------------------------------------ ~~~ sally看到她自己的344修订,你做的343修改她看不到,从Subversion看来,两次提交只是影响版本库中不同位置上的两个文件。然而,Subversion*显示*了两个文件有共同的历史,在分支拷贝之前,他们使用同一个文件,所以你和Sally都看到版本号303到98的修改。 ### 分支背后的关键概念 在这个章节你需要记住两个重要的经验。 1. 不像其他版本控制系统,Subversion的分支存在于真实的*正常文件系统*中,并不是存在于另外的维度,这些目录只是恰巧保留了额外的历史信息。 1. Subversion并没有内在的分支概念―只有拷贝,当你拷贝一个目录,这个结果目录就是一个“分支”,只是因为你给了它这样一个含义而已。你可以换一种角度考虑,或者特别处理,但是对于Subversion它只是一个普通的拷贝的结果。 [[7](#)] Subversion不支持跨版本库的拷贝,当使用**svn copy**或者**svn move**直接操作URL时你只能在同一个版本库内操作。