使用分支

到这里,你应该理解每次提交是如何在资料库中创建一个全新的文件系统树的(叫做一个“修订版”)。 如果不理解,回头看看“修订版(Revision)”一节中关于修订版的部分。

在这一章,我们将回到和第二章相同的例子。记着你和你的合作者Sally在共享一个资料库,其中包括两个项目: paintcalc。然而注意在图 4.2 “开始时的资料库布局”中,每个项目目录 现在包括名为trunkbranches的子目录。这么做的原因很快就会清楚了。

图 4.2. 开始时的资料库布局

开始时的资料库布局

和前面一样,假设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复制到一个新的工作目录。 如你用 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 “有了新拷贝的资料库”。注意第二种办法, [6] 这个过程更简单,因为它不需要你检出资料库的庞大的镜像。事实上,这种技术甚至根本不需要你有工作副本。

图 4.3. 有了新拷贝的资料库

有了新拷贝的资料库

在你的分支上工作

现在你已经创建了项目的一个分支,你可以检出一个工作副本并开始使用它:

$ 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,这创建了修改版343。

现在有两个独立的开发线修改了integer.c,如图所示图 4.4 “一个文件历史的分支”

图 4.4. 一个文件历史的分支

一个文件历史的分支

当你查看你的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的所有历史,甚至穿过了它被复制的那一点。 分支的创建作为历史中的一个事件显示,因为integer.c是随着所有包含在/calc/trunk/ 中的项的复制而被复制的。现在看看当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显示了这两个文件共享一个共同的历史。 在修订版341分支创建前,它们是同一个文件。这既是为什么你和Sally都可以看到在修订版303和98做的修改。

分支背后的关键思想

本节你应该记住两个重要的结论:

  1. Subversion和其他版本控制系统不同,它的分支在资料库中就是一个普通的文件系统目录, 不是特殊的东西。只是这些目录带了一些额外的历史信息。

  2. Subversion内部没有分支的概念,只有复制。当你复制一个目录时,得到的目录是一个“分支”,只是 因为赋予它这样的意义。你可以认为这个目录特别,或者特别的对待它,但对于Subversion来说 那只是普通的目录,只不过碰巧是通过复制来创建的而已。



[6] Subversion不支持资料库之间的复制。当在svn copysvn move中使用URL时,你只能复制在同一资料库中的项。