本手册编写于 Subversion 1.4。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/,并查阅适合您 Subversion 版本的版本。
到目前为止,您应该了解每次提交如何在存储库中创建一个全新的文件系统树(称为“修订版”)。如果不是,请返回阅读关于名为“修订版”部分的修订版。
在本章中,我们将回到来自第 1 章,基本概念的相同示例。请记住,您和您的合作者莎莉,正在共享一个包含两个项目的存储库,paint
和 calc
。请注意,在图 4.2,“起始存储库布局”中,但是,每个项目目录现在包含名为 trunk
和 branches
的子目录。原因很快就会变得很清楚。
与以前一样,假设莎莉和您都有“calc”项目的签出副本。具体来说,你们每个人都有 /calc/trunk
的签出副本。该项目的所有文件都在此子目录中,而不是在 /calc
本身中,因为您的团队决定,/calc/trunk
是进行“主线”开发的地方。
假设您被赋予了实现激进的新项目功能的任务。这将需要很长时间才能编写,并将影响项目中的所有文件。这里的问题是,您不想干预莎莉,她正在修复此处的各种小错误。她依赖于最新的项目版本(在 /calc/trunk
中)始终可用。如果您开始逐个提交您的更改,您一定会破坏莎莉的东西。
一种策略是躲起来:您和莎莉可以停止共享信息一两周。也就是说,开始修改和重组您签出副本中的所有文件,但不要提交或更新,直到您完全完成任务。但是,这种方法存在许多问题。首先,它并不安全。大多数人喜欢经常将他们的工作保存到存储库中,以防意外发生在他们的签出副本上。其次,它不够灵活。如果您在不同的计算机上进行工作(也许您在两台不同的机器上都有 /calc/trunk
的签出副本),您需要手动将更改复制来回,或者只在一台计算机上完成所有工作。同样,很难与其他人共享您的进度更改。常见的软件开发“最佳实践”是允许您的同行在您进行过程中审查您的工作。如果没有人看到您的中间提交,您会失去潜在的反馈。最后,当您完成所有更改时,您可能会发现将最终工作重新合并到公司代码的主体部分非常困难。莎莉(或其他人)可能在存储库中进行了许多其他更改,这些更改很难合并到您的签出副本中,尤其是如果您在隔离几周后运行 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
工作目录复制到一个新的工作目录,branches/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,“带有新副本的存储库”中。但是,请注意,第二种方法执行 立即 提交,并且时间恒定。[21] 它是一个更简单的过程,因为它不需要您签出一个大型存储库部分。事实上,此技术甚至不需要您拥有签出副本。这是大多数用户创建分支的方式。
现在您已经创建了项目的副本,您可以签出新的签出副本以开始使用它
$ 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.
此签出副本没有什么特别之处,它只是镜像了存储库中的不同目录。但是,当您提交更改时,莎莉在更新时不会看到它们,因为她的签出副本是 /calc/trunk
。(请务必阅读本章后面的名为“遍历分支”部分:svn switch 命令是创建分支签出副本的另一种方法。)
假设过了一周,发生了以下提交
您对 /calc/branches/my-calc-branch/button.c
进行了更改,这创建了修订版 342。
您对 /calc/branches/my-calc-branch/integer.c
进行了更改,这创建了修订版 343。
莎莉对 /calc/trunk/integer.c
进行了更改,这创建了修订版 344。
现在,图 4.4,“一个文件的历史分支”显示了在 integer.c
上发生的两个独立的开发线。
当您查看对您 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
被隐式地复制。现在看看莎莉在她的文件副本上运行相同命令时会发生什么
$ 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. ------------------------------------------------------------------------
莎莉看到了她自己的修订版 344 更改,但没有看到您在修订版 343 中进行的更改。就 Subversion 而言,这两个提交影响了存储库中不同位置的不同文件。但是,Subversion 确实 显示了这两个文件共享一个共同的历史。在修订版 341 中进行分支复制之前,它们曾经是同一个文件。这就是您和莎莉都看到在修订版 303 和 98 中进行的更改的原因。
您应该从本节中记住两个重要的教训。首先,Subversion 没有分支的内部概念,它只知道如何进行复制。当您复制目录时,生成的目录只是一个“分支”,因为 您 将此含义附加到它。您可能以不同的方式思考目录,或以不同的方式对待它,但对于 Subversion 来说,它只是一个普通的目录,碰巧携带一些额外的历史信息。其次,由于这种复制机制,Subversion 的分支在存储库中以 正常文件系统目录 的形式存在。这与其他版本控制系统不同,在其他版本控制系统中,分支通常通过向文件集合添加额外的“标签”来定义。