本手册旨在描述 Subversion 1.2。如果您正在运行更新版本的 Subversion,我们强烈建议您访问 https://svnbooks.subversion.org.cn/ 并参考适合您 Subversion 版本的版本。
在这一点上,您应该了解每次提交如何在存储库中创建一个全新的文件系统树(称为“修订版”)。如果没有,请返回并阅读 名为“修订版”的部分 中关于修订版的介绍。
在本节中,我们将回到第 2 章中的相同示例。请记住,您和您的合作者 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
的副本开始。
有两种不同的方法可以进行复制。我们首先演示混乱的方法,只是为了使概念清晰。首先,检出项目的根目录 /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,"具有新副本的存储库" 所示。请注意,然而,第二种方法执行了一个 立即 提交。 [8] 这是一个更简单的过程,因为它不需要您检出一个大型的存储库镜像。实际上,此技术甚至不需要您拥有 рабочие копии。
现在您已经创建了项目的分支,您可以检出一个新的 рабочие копии 来开始使用它。
$ 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 --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 确实 显示这两个文件共享一个共同的历史记录。在修订版 341 中进行分支复制之前,它们曾经是同一个文件。这就是您和 Sally 都能看到修订版 303 和 98 中所做更改的原因。
您应该从本节记住两个重要要点。
与许多其他版本控制系统不同,Subversion 的分支存在于存储库中的 正常文件系统目录 中,而不是在另一个维度中。这些目录只是恰好携带了一些额外的历史信息。
Subversion 没有分支的内部概念,只有副本。当您复制目录时,结果目录仅是一个“分支”,因为 您 将此含义附加到它。您可能以不同的方式看待该目录或以不同的方式处理它,但对 Subversion 而言,它只是一个普通目录,它碰巧是通过复制创建的。