### Subversion实战 是时候从抽象转到具体了,在本小节,我们会展示一个Subversion真实使用的例子。 ### 工作拷贝 你已经阅读过了关于工作拷贝的内容,现在我们要讲一讲客户端怎样建立和使用它。 一个Subversion工作拷贝是你本地机器一个普通的目录,保存着一些文件,你可以任意的编辑文件,而且如果是源代码文件,你可以像平常一样编译,你的工作拷贝是你的私有工作区,在你明确的做了特定操作之前,Subversion不会把你的修改与其他人的合并,也不会把你的修改展示给别人。 当你在工作拷贝作了一些修改并且确认它们工作正常之后,Subversion提供了一个命令可以“发布”你的修改给项目中的其他人(通过写到版本库),如果别人发布了各自的修改,Subversion提供了手段可以把这些修改与你的工作目录进行合并(通过读取版本库)。 一个工作拷贝也包括一些由Subversion创建并维护的额外文件,用来协助执行这些命令。通常情况下,你的工作拷贝每一个文件夹有一个以`.svn`为名的文件夹,也被叫做工作拷贝*管理目录*,这个目录里的文件能够帮助Subversion识别哪一个文件做过修改,哪一个文件相对于别人的工作已经过期了。 一个典型的Subversion的版本库经常包含许多项目的文件(或者说源代码),通常每一个项目都是版本库的子目录,在这种安排下,一个用户的工作拷贝往往对应版本库的的一个子目录。 举一个例子,你的版本库包含两个软件项目,`paint`和`calc`。每个项目在它们各自的顶级子目录下,见[图2.6 “版本库的文件系统”]( "图2.6.版本库的文件系统")。 **图2.6.版本库的文件系统** ![版本库的文件系统](https://box.kancloud.cn/2016-08-21_57b8a333efbe5.png) 为了得到一个工作拷贝,你必须*检出*(*check out*)版本库的一个子树,(术语“check out”听起来像是锁定或者保存资源,实际上不是,只是简单的得到一个项目的私有拷贝),举个例子,你检出 `/calc`,你可以得到这样的工作拷贝: ~~~ $ svn checkout http://svn.example.com/repos/calc A calc A calc/Makefile A calc/integer.c A calc/button.c $ ls -A calc Makefile integer.c button.c .svn/ ~~~ 列表中的A表示Subversion增加了一些条目到工作拷贝,你现在有了一个`/calc`的个人拷贝,有一个附加的目录―`.svn`―保存着前面提及的Subversion需要的额外信息。 **版本库的URL** Subversion可以通过多种方式访问―本地磁盘访问,或各种各样不同的网络协议,但一个版本库地址永远都是一个URL,表格2.1描述了不同的URL模式对应的访问方法。 **表2.1.版本库访问URL** | 模式 | 访问方法 | |-----|-----| | `file:///` | 直接版本库访问(本地磁盘)。 | | `http://` | 通过配置Subversion的Apache服务器的WebDAV协议。 | | `https://` | 与`http://`相似,但是包括SSL加密。 | | `svn://` | 通过`svnserve`服务自定义的协议。 | | `svn+ssh://` | 与`svn://`相似,但通过SSH封装。 | 关于Subversion解析URL的更多信息,见[“Subversion版本库URL”一节]( "Subversion版本库URL")。 假定你修改了`button.c`,因为`.svn`目录记录着文件的修改日期和原始内容,Subversion可以告诉你已经修改了文件,然而,在你明确告诉它之前,Subversion不会将你的改变公开。将改变公开的操作被叫做提交(*committing*,或者是*checking in*)修改到版本库。 发布你的修改给别人,你可以使用Subversion的提交(**commit**)命令: ~~~ $ svn commit button.c Sending button.c Transmitting file data . Committed revision 57. ~~~ 这时你对`button.c`的修改已经提交到了版本库,如果其他人取出了`/calc`的一个工作拷贝,他们会看到这个文件最新的版本。 假设你有个合作者,Sally,她和你同时取出了`/calc`的一个工作拷贝,你提交了你对`button.c`的修改,Sally的工作拷贝并没有改变,Subversion只在用户要求的时候才改变工作拷贝。 要使项目最新,Sally可以要求Subversion*更新*她的工作备份,通过使用更新(**update**)命令,将结合你和所有其他人在她上次更新之后的改变到她的工作拷贝。 ~~~ $ pwd /home/sally/calc $ ls -A .svn/ Makefile integer.c button.c $ svn update U button.c ~~~ **svn update**命令的输出表明Subversion更新了`button.c`的内容,注意,Sally不必指定要更新的文件,subversion利用`.svn`以及版本库的进一步信息决定哪些文件需要更新。 ### 修订版本 一个**svn commit**操作可以作为一个原子事务操作发布任意数量文件和目录的修改,在你的工作拷贝里,你可以改变文件内容、删除、改名和拷贝文件和目录,然后作为一个整体提交。 在版本库中,每一次提交被当作一次原子事务操作:要么所有的改变发生,要么都不发生,Subversion努力保持原子性以应对程序错误、系统错误、网络问题和其他用户行为。 每当版本库接受了一个提交,文件系统进入了一个新的状态,叫做一次修订(*revision*),每一个修订版本被赋予一个独一无二的自然数,一个比一个大,初始修订号是0,只创建了一个空目录,没有任何内容。 [图2.7 “版本库”]( "图2.7.版本库")可以更形象的描述版本库,想象有一组修订号,从0开始,从左到右,每一个修订号有一个目录树挂在它下面,每一个树好像是一次提交后的版本库“快照”。 **图2.7.版本库** ![版本库](https://box.kancloud.cn/2016-08-21_57b8a3340ddb4.png) **全局修订号** 不像其他版本控制系统,Subversion的修订号是针对整个*目录树*的,而不是单个文件。每一个修订号代表了一次提交后版本库整个目录树的特定状态,另一种理解是修订号N代表版本库已经经过了N次提交。当Subversion用户讨论“`foo.c`的修订号5”时,他们的实际意思是“在修订号5时的`foo.c`”。需要注意的是,修订号N和M并*不*表示一个文件需要不同。因为CVS使用每一个文件一个修订号的策略,CVS用户可能希望察看[附录A, *Subversion对于CVS用户*]( "附录A.Subversion对于CVS用户")来得到更多细节。 需要特别注意的是,工作拷贝并不一定对应版本库中的单个修订版本,他们可能包含多个修订版本的文件。举个例子,你从版本库检出一个工作拷贝,最近的修订号是4: ~~~ calc/Makefile:4 integer.c:4 button.c:4 ~~~ 此刻,工作目录与版本库的修订版本4完全对应,然而,你修改了`button.c`并且提交之后,假设没有别的提交出现,你的提交会在版本库建立修订版本5,你的工作拷贝会是这个样子的: ~~~ calc/Makefile:4 integer.c:4 button.c:5 ~~~ 假设此刻,Sally提交了对`integer.c`的修改,建立修订版本6,如果你使用**svn update**来更新你的工作拷贝,你会看到: ~~~ calc/Makefile:6 integer.c:6 button.c:6 ~~~ Sally对`integer.c`的改变会出现在你的工作拷贝,你对`button.c`的改变还在,在这个例子里,`Makefile`在4、5、6修订版本都是一样的,但是Subversion会把他的`Makefile`的修订号设为6来表明它是最新的,所以你在工作拷贝顶级目录作一次干净的更新,会使得所有内容对应版本库的同一修订版本。 ### 工作拷贝怎样追踪版本库 对于工作拷贝的每一个文件,Subversion在管理区域`.svn/`记录两项关键的信息: - 工作文件所作为基准的修订版本(叫做文件的*工作修订版本*)和 - 一个本地拷贝最后更新的时间戳。 给定这些信息,通过与版本库通讯,Subversion可以告诉我们工作文件是处与如下四种状态的那一种: 未修改且是当前的 文件在工作目录里没有修改,在工作修订版本之后没有修改提交到版本库。**svn commit**操作不做任何事情,**svn update**不做任何事情。 本地已修改且是当前的 在工作目录已经修改,从基本修订版本之后没有修改提交到版本库。本地修改没有提交,因此**svn commit**会成功的提交,**svn update**不做任何事情。 未修改且不是当前的了 这个文件在工作目录没有修改,但在版本库中已经修改了。这个文件最终将更新到最新版本,成为当时的公共修订版本。**svn commit**不做任何事情,**svn update**将会取得最新的版本到工作拷贝。 本地已修改且不是最新的 这个文件在工作目录和版本库都得到修改。一个**svn commit**将会失败,这个文件必须首先更新,**svn update**命令会合并公共和本地修改,如果Subversion不可以自动完成,将会让用户解决冲突。 这看起来需要记录很多事情,但是**svn status**命令可以告诉你工作拷贝中文件的状态,关于此命令更多的信息,请看[“**svn status**”一节]( "svn status")。 ### 修订版本混合的限制 作为通常的原则,Subversion期望尽可能的灵活,一个灵活性的表现就是能够在工作拷贝中混合有不同的修订版本。 起初,为什么把这种灵活性看作一种特性并没有完全看清楚,这也不是一个任务。完成了提交之后,干净的提交的文件比其他文件有更加新的版本,这看起来有些混乱,但是像以前说过的,通过**svn update**可以使整个版本统一起来, 怎么会有人*故意的*混合版本呢? 假设你的项目非常复杂,有时候需要强制地使工作拷贝的一部分“回到”某一个日期,你可以在第3章学习如何操作。或许你也希望测试某一目录下子模块早期的版本,或许你想检查某一文件过去的一系列版本在最新目录树环境下的表现。 无论你在工作拷贝中如何利用混合版本,对于这种灵活性是有限制的。 首先,你不可以提交一个不是完全最新的文件或目录,如果有个新的版本存在于版本库,你的删除操作会被拒绝,这防止你不小心破坏你没有见到的东西。 第二,如果目录已经不是最新的了,你不能提交一个目录的元数据更改。你将会在第6章学习附加“属性”,一个目录的工作修订版本定义了许多条目和属性,因而对一个过期的版本提交属性会破坏一些你没有见到的属性。