使用分支

在这一点上,你必须理解每一次提交是怎样建立整个新的文件系统树(叫做“修订版本”)的,如果没有,可以回头去读“修订版本”一节

对于本章节,我们会回到第 1 章 基本概念的同一个例子,还记得你和你的合作者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工作目录拷贝到一个新的目录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 “版本库与复制”看到,注意第二种方法,只是执行了一个立即提交。 [20]这是一个简单的过程,因为你不需要取出版本库一个庞大的镜像,事实上,这个技术不需要你有工作拷贝,这是大多数用户创建分支的方式。

图 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,生成了修订版本344。

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

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


当你看到integer.c的改变时,你会发现很有趣:

$ pwd
/home/user/my-calc-branch

$ svn log -v 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 -v 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的修改。

分支背后的关键概念

在本节,你需要记住两件重要的课程。首先,Subversion并没有内在的分支概念—只有拷贝,当你拷贝一个目录,这个结果目录就是一个“分支”,只是因为你给了它这样一个含义而已。你可以换一种角度考虑,或者特别对待,但是对于Subversion它只是一个普通的拷贝,只不过碰巧包含了一些额外的历史信息。第二,因为拷贝机制,Subversion的分支是以普通文件系统目录存在的,这与其他版本控制系统不同,它们都为分支定义了另一维度的“标签”。



[20] Subversion不支持跨版本库的拷贝,当使用svn copy或者svn move直接操作URL时你只能在同一个版本库内操作。