本文档用于描述 Subversion 1.6.x 系列。如果您正在运行其他版本的 Subversion,强烈建议您访问 https://svnbook.subversion.org.cn/,并查阅适合您 Subversion 版本的文档。
到目前为止,您应该了解每次提交如何在存储库中创建一个新的文件系统树状态(称为“版本”)。如果您不了解,请返回并阅读“版本”部分的内容。
在本节中,我们将回到第 1 章,基本概念中使用的同一个示例。请记住,您和您的合作者 Sally 共享一个包含两个项目(paint
和 calc
)的存储库。请注意,在图 4.2,“起始存储库布局”中,每个项目目录现在都包含名为 trunk
和 branches
的子目录。这将在后面解释。
与之前一样,假设您和 Sally 都拥有 “calc” 项目的工作副本。具体来说,你们都拥有 /calc/trunk
的工作副本。该项目的全部文件位于此子目录中,而不是在 /calc
本身,因为您的团队已决定将 /calc/trunk
作为开发的“主线”。
假设您被分配了一个大型软件功能的开发任务。这需要很长时间才能完成,并将影响项目中的所有文件。直接的问题是,您不想干扰 Sally,她正在修补一些小的错误。她依赖于最新版本的项目(在 /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
的副本。
您可能已经看到过将 svn copy 用于在工作副本中将一个文件复制到另一个文件。但它也可以用于在存储库中进行完全的“远程”复制。只需将一个 URL 复制到另一个 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,“包含新副本的存储库”所示。[23] 虽然也可以通过使用 svn copy 在工作副本中复制目录来创建分支,但不推荐此方法。实际上,它可能很慢!在客户端复制目录是一个线性时间操作,因为它实际上需要在本地磁盘上复制该工作副本目录中的每个文件和子目录。但是,在服务器上复制目录是一个常数时间操作,这也是大多数人创建分支的方式。
现在您已经创建了项目的副本,您可以检出一个新的工作副本来开始使用它
$ 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。
现在,在 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: A /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: A /calc/trunk/integer.c * integer.c: adding this file to the project. ------------------------------------------------------------------------
Sally 会看到她自己的版本 344 更改,但不会看到您在版本 343 中所做的更改。就 Subversion 而言,这两个提交影响了存储库中不同位置的不同文件。但是,Subversion确实表明这两个文件共享一个共同的历史。在版本 341 中进行分支复制之前,这两个文件曾经是同一个文件。这就是您和 Sally 都能看到版本 303 和 98 中所做更改的原因。
您应该记住本节中的两个重要知识点。首先,Subversion 没有分支的内部概念,它只知道如何进行复制。当您复制目录时,结果目录只是一个“分支”,因为您将此含义附加到它。您可能会以不同的方式思考或处理该目录,但对 Subversion 来说,它只是一个普通目录,恰好带有额外的历史信息。
其次,由于这种复制机制,Subversion 的分支存在于存储库中的普通文件系统目录中。这与其他版本控制系统不同,在其他版本控制系统中,分支通常通过向文件集合添加额外的维度“标签”来定义。分支目录的位置对 Subversion 来说并不重要。大多数团队遵循将所有分支都放在 /branches
目录中的约定,但您可以自由地发明任何您想要的策略。