版本库维护

维护一个Subversion版本库是一项令人沮丧的工作,主要因为有数据库后端与生俱来的复杂性。做好这项工作需要知道一些工具——它们是什么,什么时候用以及如何使用。这一节将会向你介绍Subversion自带的版本库管理工具,以及如何使用它们来完成诸如版本库移植、升级、备份和整理之类的任务。

管理员的工具箱

Subversion提供了一些用来创建、查看、修改和修复版本库的工具。让我们首先详细了解一下每个工具,然后,我们再看一下仅在Berkeley DB后端分发版本中提供的版本数据库工具。

svnadmin

svnadmin程序是版本库管理员最好的朋友。除了提供创建Subversion版本库的功能,这个程序使你可以维护这些版本库。svnadmin的语法同其他Subversion命令类似:

$ svnadmin help
general usage: svnadmin SUBCOMMAND REPOS_PATH  [ARGS & OPTIONS ...]
Type 'svnadmin help <subcommand>' for help on a specific subcommand.
Type 'svnadmin --version' to see the program version and FS modules.

Available subcommands:
   crashtest
   create
   deltify
…

我们已经讨论了svnadmincreate子命令(参照“创建和配置你的版本库”一节),本章后面我们会详细讲解大多数其他的子命令,关于所有的子命令你可以参考“ svnadmin ”一节

svnlook

svnlook是Subversion提供的用来查看版本库中不同的修订版本和事务(正在产生的修订版本)。这个程序不会修改版本库内容-这是个“只读”的工具。svnlook通常用在版本库钩子程序中,用来记录版本库即将提交(用在pre-commit钩子时)或者已经提交的(用在post-commit钩子时)修改。版本库管理员可以将这个工具用于诊断。

svnlook的语法很直接:

$ svnlook help
general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]
Note: any subcommand which takes the '--revision' and '--transaction'
      options will, if invoked without one of those options, act on
      the repository's youngest revision.
Type 'svnlook help <subcommand>' for help on a specific subcommand.
Type 'svnlook --version' to see the program version and FS modules.
…

几乎svnlook的每一个子命令都能操作修订版本或事务树,显示树本身的信息,或是它与版本库中上一个修订版本的不同。你可以用--revision (-r)--transaction (-t)选项指定要查看的修订版本或事务。如果没有指定--revision (-r)--transaction (-t)选项,svnlook会检查版本库最新的(或者说“HEAD”)修订版本。所以当19是位于/path/to/repos的版本库的最新版本时,如下的两个名字起到相同的效果:

$ svnlook info /path/to/repos
$ svnlook info /path/to/repos -r 19

这些子命令的唯一例外是svnlook youngest,它不需要任何选项,只会打印出版本库的最新修订版本号。

$ svnlook youngest /path/to/repos
19

注意

请记住只能浏览未提交的事物,大多数版本库没有这样的事物,因为事物要么是已经提交的(也就是你可以--revision (-r)访问的修订版本),要么是退出的和删除的。

svnlook的输出被设计为人和机器都易理解,拿info子命令举例来说:

$ svnlook info /path/to/repos
sally
2002-11-04 09:29:13 -0600 (Mon, 04 Nov 2002)
27
Added the usual
Greek tree.

info子命令的输出定义如下:

  1. 作者,后接换行。

  2. 日期,后接换行。

  3. 日志消息的字数,后接换行。

  4. 日志信息本身, 后接换行。

这种输出是人可阅读的,像是时间戳这种有意义的条目,使用文本表示,而不是其他比较晦涩的方式(例如许多无聊的人推荐的十亿分之一秒的数量)。这种输出也是机器可读的—因为日志信息可以有多行,没有长度的限制,svnlook在日志消息之前提供了消息的长度,这使得脚本或者其他这个命令的封装器能够针对日志信息做出许多职能的决定,或仅仅是在这个输出成为最后一个字节之前应该略过多少字节。

svnlook还可以做很多别的查询:显示我们先前提到的信息的一些子集,递归显示版本目录树,报告指定的修订版本或事务中哪些路径曾经被修改过,显示对文件和目录做过的文本和属性的修改,等等。“ svnlook ”一节svnlook命令能接受子命令的完全特性参考。

svndumpfilter

虽然在管理员的日常工作中并不会经常使用,不过svndumpfilter提供了一项特别有用的功能—可以简单快速的作为Subversion版本库历史的以路径为基础的过滤器。

svndumpfilter的语法如下:

$ svndumpfilter help
general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]
Type "svndumpfilter help <subcommand>" for help on a specific subcommand.
Type 'svndumpfilter --version' to see the program version.
  
Available subcommands:
   exclude
   include
   help (?, h)

有意义的子命令只有两个。你可以使用这两个子命令说明你希望保留和不希望保留的路径:

exclude

将指定路径的数据从转储数据流中排除。

include

将指定路径的数据添加到转储数据流中。

关于这些子命令和svndumpfilter的唯一目的的,可以见“过滤版本库历史”一节

svnsync

svnsync程序是Subversion 1.4版的新特性,提供了维护一个只读版本库镜像的全部功能。这个程序只有一个工作—将一个版本库的历史转移到另一个,尽管有几种方法,但这种方法的主要特点是可以远程操作—“”,“目标[30]版本库以及svnsync程序可能在不同的计算机上。

就像你期望的,svnsync的语法与本节提到的其他命令非常类似。

$ svnsync help
general usage: svnsync SUBCOMMAND DEST_URL  [ARGS & OPTIONS ...]
Type 'svnsync help <subcommand>' for help on a specific subcommand.
Type 'svnsync --version' to see the program version and RA modules.

Available subcommands:
   initialize (init)
   synchronize (sync)
   copy-revprops
   help (?, h)
$

我们会在“版本库复制”一节详细讨论使用svnsync实现版本库复制。

Berkeley DB 工具

如果你使用Berkeley DB版本库,那么所有纳入版本控制的文件系统结构和数据都储存在一系列数据库的表中,而这个目录就是版本库的db/。这个子目录是一个标准的Berkeley DB环境目录,可以应用任何Berkeley数据库工具进行操作,通常这些工具随Berkeley DB发布。

对于Subversion的日常使用来说,这些工具并没有什么用处。大多数Subversion版本库必须的数据库操作都集成到svnadmin工具中。比如,svnadmin list-unused-dblogssvnadmin list-dblogs实现了Berkeley db_archive命令功能的一个子集,而svnadmin recover则起到了db_recover工具的作用。

当然,还有一些Berkeley DB工具有时是有用的。db_loaddb_dump分别将Berkeley DB数据库中的键值对以特定的格式读写文件。Berkeley数据库本身不支持跨平台转移,这两个工具在这样的情况下就可以实现在平台间转移数据库的功能,而无需关心操作系统或机器架构。就像我们以前描述的,你可以使用svnadmin dumpsvnadmin load实现类似的目的,但是db_dumpdb_load可以更快一点,它们也可以协助Berkeley DB的hacker来篡改BDB后端的数据,这是Subversion工具不允许的。此外,db_stat工具能够提供关于Berkeley DB环境的许多有用信息,包括详细的锁定和存储子系统的统计信息。

关于Berkeley DB工具的更多信息,可以访问Oracle网站的Berkeley DB文档部分,在http://www.oracle.com/technology/documentation/berkeley-db/db/

修正提交消息

有时用户输入的日志信息有错误(比如拼写错误或者内容错误)。如果配置版本库时设置了(使用pre-revprop-changepost-revprop-change钩子;参见“实现版本库钩子”一节)允许用户在提交后修改日志信息的选项,那么用户可以使用svn程序的propset命令(参见第 9 章 Subversion 完全参考)“修正”日志信息中的错误。不过为了避免永远丢失信息,Subversion版本库通常设置为仅能由管理员修改非版本化属性(这也是默认的选项)。

如果管理员想要修改日志信息,那么可以使用svnadmin setlog命令。这个命令从指定的文件中读取信息,取代版本库中某个修订版本的日志信息(svn:log属性)。

$ echo "Here is the new, correct log message" > newlog.txt
$ svnadmin setlog myrepos newlog.txt -r 388

即使是svnadmin setlog命令也受到限制。pre-post-revprop-change钩子同样会被触发,因此必须进行相应的设置才能允许修改非版本化属性。不过管理员可以使用svnadmin setlog命令的--bypass-hooks选项跳过钩子。

警告

不过需要注意的是,一旦跳过钩子也就跳过了钩子所提供的所有功能,比如邮件通知(通知属性有改动)、系统备份(可以用来跟踪非版本化的属性变更)等等。换句话说,要留心你所作出的修改,以及你作出修改的方式。

管理磁盘空间

虽然存储器的价格在过去的几年里以让人难以致信的速度滑落,但是对于那些需要对大量数据进行版本管理的管理员们来说,磁盘空间的消耗依然是一个重要的因素。版本库每增加一个字节都意味着需要多一个字节的磁盘空间进行备份,对于多重备份来说,就需要消耗更多的磁盘空间。Berkeley DB版本库的主要存储机制是基于一个复杂的数据库系统建立的,因此了解一些数据性质是有意义的,比如哪些数据必须保持在线,哪些数据需要备份、哪些数据可以安全的删除等等。

Subversion如何节约磁盘空间

为了尽可能减小版本库的体积,Subversion在版本库中采用了增量化技术(或称为“增量存储技术”)。增量化技术可以将一组数据表示为相对于另一组数据的不同。如果这两组数据十分相似,增量化技术就可以仅保存其中一组数据以及两组数据的差别,而不需要同时保存两组数据,从而节省了磁盘空间。每次一个文件的新版本提交到版本库,版本库就会将之前的版本(之前的多个版本)相对于新版本做增量化处理。采用了这项技术,版本库的数据量大小基本上是可以估算出来的—主要是版本化的文件的大小—并且远小于“全文”保存所需的数据量。而Subversion 1.4以后,空间存储变得更为节省—现在文件内容的全文本身都是压缩的了。

注意

由于Subversion版本库的增量化数据保存在单一Berkeley DB数据库文件中,减少数据的体积并不一定能够减小数据库文件的大小。但是,Berkeley DB会在内部记录未使用的数据库文件区域,并且在增加数据库文件大小之前会首先使用这些未使用的区域。因此,即使增量化技术不能立杆见影的节省磁盘空间,也可以极大的减慢数据库的膨胀速度。

删除终止的事务

尽管不太常见,Subversion的提交进程也有失败,同时留下将要生成的修订版本—未提交的事物和所有随之的文件和目录修改。出现这种情况可能有以下原因:客户端的用户粗暴的结束了操作,操作过程中出现网络故障,等等。不管是什么原因,死亡的事务总是有可能会出现。这类事务不会产生什么负面影响,仅仅是消耗了一点点磁盘空间。不过,严厉的管理员总是希望能够将它们清除出去。

可以使用svnadminlstxns 命令列出当前的事务名。

$ svnadmin lstxns myrepos
19
3a1
a45
$

将输出的结果条目作为svnlook(设置--transaction (-t)选项)的参数,就可以获得事务的详细信息,如事务的创建者、创建时间,事务已作出的更改类型,由这些信息可以判断出是否可以将这个事务安全的删除。如果可以安全删除,那么只需将事务名作为参数输入到svnadmin rmtxns,就可以将事务清除掉了。其实rmtxns子命令可以直接以lstxns的输出作为输入进行清理。

$ svnadmin rmtxns myrepos `svnadmin lstxns myrepos`
$

在按照上面例子中的方法清理版本库之前,你或许应该暂时关闭版本库和客户端的连接。这样在你开始清理之前,不会有正常的事务进入版本库。例 5.1 “txn-info.sh(报告异常事务)”中的shell脚本可以用来迅速获得版本库中异常事务的信息。

例 5.1. txn-info.sh(报告异常事务)

#!/bin/sh

### Generate informational output for all outstanding transactions in
### a Subversion repository.

REPOS="${1}"
if [ "x$REPOS" = x ] ; then
  echo "usage: $0 REPOS_PATH"
  exit
fi

for TXN in `svnadmin lstxns ${REPOS}`; do 
  echo "---[ Transaction ${TXN} ]-------------------------------------------"
  svnlook info "${REPOS}" -t "${TXN}"
done

该命令的输出主要由多个svnlook info(参见“svnlook”一节)的输出组成,类似于下面的例子:

$ txn-info.sh myrepos
---[ Transaction 19 ]-------------------------------------------
sally
2001-09-04 11:57:19 -0500 (Tue, 04 Sep 2001)
0
---[ Transaction 3a1 ]-------------------------------------------
harry
2001-09-10 16:50:30 -0500 (Mon, 10 Sep 2001)
39
Trying to commit over a faulty network.
---[ Transaction a45 ]-------------------------------------------
sally
2001-09-12 11:09:28 -0500 (Wed, 12 Sep 2001)
0
$

一个废弃了很长时间的事务通常是提交错误或异常中断的结果。事务的时间戳可以提供给我们一些有趣的信息,比如一个进行了9个月的操作居然还是活动的等等。

简言之,作出事务清理的决定前应该仔细考虑一下。许多信息源—比如Apache的错误和访问日志,已成功完成的Subversion提交日志等等—都可以作为决策的参考。当然,管理员还可以直接和那些似乎已经死亡事务的提交者直接交流(比如通过邮件),来确认该事务确实已经死亡了。

删除不使用的Berkeley DB日志文件

目前为止,Subversion版本库中耗费磁盘空间的最大凶手是日志文件,每次Berkeley DB在修改真正的数据文件之前都会进行预写入(pre-writes)操作。这些文件记录了数据库从一个状态变化到另一个状态的所有动作——数据库文件反映了特定时刻数据库的状态,而日志文件则记录了所有状态变化的信息。因此,日志文件会以很快的速度膨胀起来。

幸运的是,从版本4.2开始,Berkeley DB的数据库环境无需额外的操作即可删除无用的日志文件。如果编译svnadmin时使用了高于4.2版本的Berkeley DB,那么由此svnadmin程序创建的版本库就具备了自动清除日志文件的功能。如果想屏蔽这个功能,只需设置svnadmin create命令的--bdb-log-keep选项即可。如果创建版本库以后想要修改关于此功能的设置,只需编辑版本库中db目录下的DB_CONFIG文件,注释掉包含set_flags DB_LOG_AUTOREMOVE内容的这一行,然后运行svnadmin recover强制设置生效就行了。查阅“Berkeley DB 配置”一节获得更多关于数据库配置的帮助信息。

如果不自动删除日志文件,那么日志文件会随着版本库的使用逐渐增加。这多少应该算是数据库系统的特性,通过这些日志文件可以在数据库严重损坏时恢复整个数据库的内容。但是一般情况下,最好是能够将无用的日志文件收集起来并删除,这样就可以节省磁盘空间。使用svnadmin list-unused-dblogs命令可以列出无用的日志文件:

$ svnadmin list-unused-dblogs /path/to/repos
/path/to/repos/log.0000000031
/path/to/repos/log.0000000032
/path/to/repos/log.0000000033
…
$ rm `svnadmin list-unused-dblogs /path/to/repos`
## disk space reclaimed!

警告

BDB后端的版本库的日志文件如果是用来作为备份或容灾恢复计划时,不要使用日志文件的自动删除特性。从日志文件重新构建版本库数据只有在所有的日志文件都存在时才能完成,如果有一些文件在别的程序将其拷贝之前就已经被删除了,不完整的备份日志文件就没有用了。

Berkeley DB 恢复

就像在“Berkeley DB”一节提到的,如果没有正确的关闭,Berkeley DB版本库有时候会进入冻结的状态。当发生这种情况时,管理员需要恢复版本库进入一致的状态。当然这种情况只发生在BDB版本库,FSFS版本库不会有这种情况。对于使用Subversion 1.4和Berkeley DB 4.4或更新版本的用户,你一定发现Subversion对于这种情况已经更富弹性,但是Berkeley DB楔住的情况还是会发生,管理员需要知道如何安全的处理种情况。

Berkeley DB使用一种锁机制保护版本库中的数据。锁机制确保数据库不会同时被多个访问进程修改,也就保证了从数据库中读取到的数据始终是稳定而且正确的。当一个进程需要修改数据库中的数据时,首先必须检查目标数据是否已经上锁。如果目标数据没有上锁,进程就将它锁上,然后作出修改,最后再将锁解除。而其它进程则必须等待锁解除后才能继续访问数据库中的相关内容。(你对这种锁无能为力,作为一个用户,可以应用版本库的版本化文件;我们会在锁定的三种含义讨论因为术语冲突导致的概念混淆。)

在操作Subversion版本库的过程中,致命错误(如内存或硬盘空间不足)或异常中断可能会导致某个进程没能及时将锁解除。结果就是后端的数据库系统被“塞住”了。一旦发生这种情况,任何访问版本库的进程都会挂起(每个访问进程都在等待锁被解除,但是锁已经无法解除了)。

如果你的版本库出现这种情况,没什么好惊慌的。Berkeley DB的文件系统采用了数据库事务、检查点以及预写入日志等技术来确保只有灾难性的事件[31]才能永久性的破坏数据库环境。所以虽然一个过于稳重的版本库管理员通常都会按照某种方案进行大量的版本库离线备份,不过不要急着通知你的管理员进行恢复。

然后,使用下面的方法试着“恢复”你的版本库:

  1. 确保没有其它进程访问(或者试图访问)版本库。对于网络版本库,这意味着关闭Apache HTTP Server或svnserve。

  2. 成为版本库的拥有者和管理员。这一点很重要,如果以其它用户的身份恢复版本库,可能会改变版本库文件的访问权限,导致在版本库“恢复”后依旧无法访问。

  3. 运行命令svnadmin recover /path/to/repos。 输出如下:

    Repository lock acquired。
    Please wait; recovering the repository may take some time...
    
    Recovery completed.
    The latest repos revision is 19.
    

    此命令可能需要数分钟才能完成。

  4. 重新启动服务进程。

这个方法能修复几乎所有版本库锁住的问题。记住,要以数据库的拥有者和管理员的身份运行这个命令,而不一定是root用户。恢复过程中可能会使用其它数据存储区(例如共享内存区)重建一些数据库文件。如果以root用户身份恢复版本库,这些重建的文件拥有者将变成root用户,也就是说,即使恢复了到版本库的连接,一般的用户也无权访问这些文件。

如果因为某些原因,上面的方法没能成功的恢复版本库,那么你可以做两件事。首先,将破损的版本库保存到其它地方,然后从最新的备份中恢复版本库。然后,发送一封邮件到Subversion用户列表(地址是:),写清你所遇到的问题。对于Subversion的开发者来说,数据安全是最重要的问题。

版本库数据的移植

Subversion文件系统将数据保存在许多数据库表中,而这些表的结构只有Subversion开发者们才了解(也只有他们才感兴趣),不过,有些时候我们会想到把所有或一部分数据转移到另一个版本库。

Subversion提供了转储版本库的功能,一个版本库转储流(当存放在磁盘上叫做“dumpfile”)是一种可移植的,普通文件格式,可以用来描述版本库的不同版本—什么发生了修改,谁做的,何时等等。这种转储流是解析版本化历史的主要机制—全部或部分,包含或部包含修改—在版本库之间。Subversion也提供了创建和加载这些转储流的工具—对应的svnadmin dumpsvnadmin load子命令。

警告

虽然Subversion版本库转储格式包含了人可读的部分和熟悉的结构(类似RFC-822格式,大多数邮件使用的),它不是纯文本的格式,这种格式必须作为二进制文件格式处理,对修改高度敏感。例如,许多文本编辑器会破坏这种文件的内容,通常是因为自动换行符替换。

有很多导出和加载Subversion版本库数据的方法,在Subversion的早期阶段,最主要的原因是Subversion本身的进化。随着Subversion的成熟,对于数据后端模式的改变会导致更多的兼容性问题,所以用户需要使用旧版本的Subversion将版本库数据导出,然后用新版的版本库加载内容到新建的版本库。目前,这种类型的模式修改从Subversion 1.0版本还没有发生,而且Subversion开发者也许诺不会强制用户在小版本(如1.3到1.4)升级之间导入和导出版本库。但是也有一些其它原因导出和导入,包括重新部署Berkeley DB到版本库到新的OS或CPU架构,在Berkeley DB和FSFS后端之间切换,或者(我们会在“过滤版本库历史”一节覆盖)从版本库历史中清理文件。

无论你是什么原因需要移植版本库历史,都可以直接使用svnadmin dumpsvnadmin loadsvnadmin dump命令会将版本库中的修订版本数据按照特定的格式输出到转储流中,转储数据会输出到标准输出,而提示信息会输出到标准错误。这就是说,可以将转储数据存储到文件中,而同时在终端窗口中监视运行状态,例如:

$ svnlook youngest myrepos
26
$ svnadmin dump myrepos > dumpfile
* Dumped revision 0.
* Dumped revision 1.
* Dumped revision 2.
…
* Dumped revision 25.
* Dumped revision 26.

最后,版本库中的指定的修订版本数据被转储到一个独立的文件中(在上面的例子中是dumpfile)。注意,svnadmin dump从版本库中读取修订版本树与其它“读者”(比如svn checkout)的过程相同,所以可以在任何时候安全的运行这个命令。

另一个命令,svnadmin load,从标准输入流中读取Subversion转储数据,并且高效的将数据转载到目标版本库中。这个命令的提示信息输出到标准输出流中:

$ svnadmin load newrepos < dumpfile
<<< Started new txn, based on original revision 1
     * adding path : A ... done.
     * adding path : A/B ... done.
     …
------- Committed new rev 1 (loaded from original rev 1) >>>

<<< Started new txn, based on original revision 2
     * editing path : A/mu ... done.
     * editing path : A/D/G/rho ... done.

------- Committed new rev 2 (loaded from original rev 2) >>>

…

<<< Started new txn, based on original revision 25
     * editing path : A/D/gamma ... done.

------- Committed new rev 25 (loaded from original rev 25) >>>

<<< Started new txn, based on original revision 26
     * adding path : A/Z/zeta ... done.
     * editing path : A/mu ... done.

------- Committed new rev 26 (loaded from original rev 26) >>>

load命令的结果就是添加一些新的修订版本—与使用普通Subversion客户端直接提交到版本库相同。正像一次简单的提交,你也可以使用钩子脚本在每次load的开始和结束执行一些操作。通过传递--use-pre-commit-hook--use-post-commit-hook选项给svnadmin load,你可以告诉Subversion的对每一个加载修订版本执行pre-commit和post-commit钩子脚本,可以利用这个选项确保这种提交也能通过一般提交的检验。当然,你要小心使用这个选项,你一定不想接受一大堆提交邮件。你可以查看“实现版本库钩子”一节来得到更多相关信息。

既然svnadmin使用标准输入流和标准输出流作为转储和装载的输入和输出,那么更漂亮的用法是(管道两端可以是不同版本的svnadmin

$ svnadmin create newrepos
$ svnadmin dump oldrepos | svnadmin load newrepos

默认情况下,转储文件的体积可能会相当庞大——比版本库自身大很多。这是因为在转储文件中,每个文件的每个版本都以完整的文本形式保存下来。这种方法速度很快,而且很简单,尤其是直接将转储数据通过管道输入到其它进程中时(比如一个压缩程序,过滤程序,或者一个装载进程)。不过如果要长期保存转储文件,那么可以使用--deltas选项来节省磁盘空间。设置这个选项,同一个文件的数个连续修订版本会以增量式的方式保存—就像储存在版本库中一样。这个方法较慢,但是转储文件的体积则基本上与版本库的体积相当。

之前我们提到svnadmin dump输出指定范围内的修订版本,使用--revision (-r)选项可以指定一个单独的修订版本,或者一个修订版本的范围。如果忽略这个选项,所有版本库中的修订版本都会被转储。

$ svnadmin dump myrepos -r 23 > rev-23.dumpfile
$ svnadmin dump myrepos -r 100:200 > revs-100-200.dumpfile

Subversion在转储修订版本时,仅会输出与前一个修订版本之间的差异,通过这些差异足以从前一个修订版本中重建当前的修订版本。换句话说,在转储文件中的每一个修订版本仅包含这个修订版本作出的修改。这个规则的唯一一个例外是当前svnadmin dump转储的第一个修订版本。

默认情况下,Subversion不会把转储的第一个修订版本看作对前一个修订版本的更改。 首先,转储文件中没有比第一个修订版本更靠前的修订版本了!其次,Subversion不知道装载转储数据时(如果真的需要装载的话)的版本库是什么样的情况。为了保证每次运行svnadmin dump都能得到一个独立的结果,第一个转储的修订版本默认情况下会完整的保存目录、文件以及属性等数据。

不过,这些都是可以改变的。如果转储时设置了--incremental选项,svnadmin会比较第一个转储的修订版本和版本库中前一个修订版本,就像对待其它转储的修订版本一样。转储时也是一样,转储文件中将仅包含第一个转储的修订版本的增量信息。这样的好处是,可以创建几个连续的小体积的转储文件代替一个大文件,比如:

$ svnadmin dump myrepos -r 0:1000 > dumpfile1
$ svnadmin dump myrepos -r 1001:2000 --incremental > dumpfile2
$ svnadmin dump myrepos -r 2001:3000 --incremental > dumpfile3

这些转储文件可以使用下列命令装载到一个新的版本库中:

$ svnadmin load newrepos < dumpfile1
$ svnadmin load newrepos < dumpfile2
$ svnadmin load newrepos < dumpfile3

另一个有关的技巧是,可以使用--incremental选项在一个转储文件中增加新的转储修订版本。举个例子,可以使用post-commit钩子在每次新的修订版本提交后将其转储到文件中。或者,可以编写一个脚本,在每天夜里将所有新增的修订版本转储到文件中。这样,svnadmin dump命令就变成了很好的版本库备份工具,以防万一出现系统崩溃或其它灾难性事件。

转储还可以用来将几个独立的版本库合并为一个版本库。使用svnadmin load--parent-dir选项,可以在装载的时候指定根目录。也就是说,如果有三个不同版本库的转储文件,比如calc-dumpfilecal-dumpfile,和ss-dumpfile,可以在一个新的版本库中保存所有三个转储文件中的数据:

$ svnadmin create /path/to/projects
$

然后在版本库中创建三个目录分别保存来自三个不同版本库的数据:

$ svn mkdir -m "Initial project roots" \
      file:///path/to/projects/calc \
      file:///path/to/projects/calendar \
      file:///path/to/projects/spreadsheet
Committed revision 1.
$ 

最后,将转储文件分别装载到各自的目录中:

$ svnadmin load /path/to/projects --parent-dir calc < calc-dumpfile
…
$ svnadmin load /path/to/projects --parent-dir calendar < cal-dumpfile
…
$ svnadmin load /path/to/projects --parent-dir spreadsheet < ss-dumpfile
…
$

我们再介绍一下Subversion版本库转储数据的最后一种用途——在不同的存储机制或版本控制系统之间转换。因为转储数据的格式的大部分是可以阅读的,所以使用这种格式描述变更集(每个变更集对应一个新的修订版本)会相对容易一些。事实上,cvs2svn工具(参见 “迁移CVS版本库到Subversion”一节)正是将CVS版本库的内容转换为转储数据格式,如此才能将CVS版本库的数据导入Subversion版本库之中。

过滤版本库历史

因为Subversion使用底层的二进制区别和压缩算法(也可以选择完全非透明数据库系统)储存各类数据,手工调整是不明智的,即使这样做并不困难,我们也不鼓励这样做。然而,一旦你的数据存进了版本库,Subversion没有提供删除数据的简单办法。[32]但是不可避免的,总会有些时候你需要处理版本库的历史数据。你也许想把一个不应该出现的文件从版本库中彻底清除(无论任何原因不应该在那个位置出现)。或者,你曾经用一个版本库管理多个工程,现在又想把它们分开。要完成这样的工作,管理员们需要更易于管理和扩展的方法表示版本库中的数据,Subversion版本库转储文件格式就是一个很好的选择。

就像我们在“版本库数据的移植”一节中说的,Subversion版本库转储文件记录了所有版本数据的变更信息,而且以易于阅读的格式保存。可以使用svnadmin dump命令生成转储文件,然后用svnadmin load命令生成一个新的版本库。(参见 “版本库数据的移植”一节)。转储文件易于阅读意味着你可以查看和修改它。当然,问题是如果你有一个运行了三年的版本库,那么生成的转储文件会很庞大,阅读和手工修改起来都会花费很多时间。

这正是svndumpfilter发挥作用的地方,这个程序可以对版本库转储流进行特定路径的过滤。这是一个独特而很有意义的用法,可以帮助你快速方便的修改转储的数据。使用时,只需提供一个你想要保留的(或者不想保留的)路径列表,然后把你的版本库转储文件送进这个过滤器。最后你就可以得到一个仅包含你想保留路径(明确的或含蓄的)的转储数据流。

现在我来演示如何使用这个命令。我们会在其它章节(参见 “规划你的版本库结构”一节)讨论关于如何选择设定版本库布局的问题,比如应该使用一个版本库管理多个项目还是使用一个版本库管理一个项目,或者如何在版本库中安排数据等等。不过,有些时候,即使在项目已经展开以后,你还是希望对版本库的布局做一些调整。最常见的情况是,把原来存放在同一个版本库中的几个项目分开,各自成家。

假设有一个包含三个项目的版本库: calccalendar,和 spreadsheet。它们在版本库中的布局如下:

/
   calc/
      trunk/
      branches/
      tags/
   calendar/
      trunk/
      branches/
      tags/
   spreadsheet/
      trunk/
      branches/
      tags/

现在要把这三个项目转移到三个独立的版本库中。首先,转储整个版本库:

$ svnadmin dump /path/to/repos > repos-dumpfile
* Dumped revision 0.
* Dumped revision 1.
* Dumped revision 2.
* Dumped revision 3.
…
$

然后,将转储文件三次送入过滤器,每次仅保留一个顶级目录,就可以得到三个转储文件:

$ svndumpfilter include calc < repos-dumpfile > calc-dumpfile
…
$ svndumpfilter include calendar < repos-dumpfile > cal-dumpfile
…
$ svndumpfilter include spreadsheet < repos-dumpfile > ss-dumpfile
…
$

现在你必须要作出一个决定了。这三个转储文件中,每个都可以用来创建一个可用的版本库,不过它们保留了原版本库的精确路径结构。也就是说,虽然项目calc现在独占了一个版本库,但版本库中还保留着名为calc的顶级目录。如果希望trunktagsbranches这三个目录直接位于版本库的根路径下,你可能需要编辑转储文件,调整Node-pathCopyfrom-path头参数,将路径calc/删除。同时,你还要删除转储数据中创建calc目录的部分。一般来说,就是如下的一些内容:

Node-path: calc
Node-action: add
Node-kind: dir
Content-length: 0
  

警告

如果你打算通过手工编辑转储文件来移除一个顶级目录,注意不要让你的编辑器将换行符转换为本地格式(比如将\r\n转换为\n)。否则文件的内容就与所需的格式不相符,这个转储文件也就失效了。

剩下的工作就是创建三个新的版本库,然后将三个转储文件分别导入:

$ svnadmin create calc; svnadmin load calc < calc-dumpfile
<<< Started new transaction, based on original revision 1
     * adding path : Makefile ... done.
     * adding path : button.c ... done.
…
$ svnadmin create calendar; svnadmin load calendar < cal-dumpfile
<<< Started new transaction, based on original revision 1
     * adding path : Makefile ... done.
     * adding path : cal.c ... done.
…
$ svnadmin create spreadsheet; svnadmin load spreadsheet < ss-dumpfile
<<< Started new transaction, based on original revision 1
     * adding path : Makefile ... done.
     * adding path : ss.c ... done.
…
$

svndumpfilter的两个子命令都可以通过选项设定如何处理“”修订版本。如果某个指定的修订版本仅包含路径的更改,过滤器就会将它删除,因为当前为空的修订版本通常是无用的甚至是让人讨厌的。为了让用户有选择的处理这些修订版本,svndumpfilter提供了以下命令行选项:

--drop-empty-revs

不生成任何空修订版本,忽略它们。

--renumber-revs

如果空修订版本被剔除(通过使用--drop-empty-revs选项),依次修改其它修订版本的编号,确保编号序列是连续的。

--preserve-revprops

如果空修订版本被保留,保持这些空修订版本的属性(日志信息,作者,日期,自定义属性,等等)。如果不设定这个选项,空修订版本将仅保留初始时间戳,以及一个自动生成的日志信息,表明此修订版本由svndumpfilter处理过。

尽管svndumpfilter十分有用,能节省大量的时间,但它却是把不折不扣的双刃剑。首先,这个工具对路径语义极为敏感。仔细检查转储文件中的路径是不是以斜线开头。也许Node-pathCopyfrom-path这两个头参数对你有些帮助。

…
Node-path: spreadsheet/Makefile
…

如果这些路径以斜线开头,那么你传递给svndumpfilter includesvndumpfilter exclude的路径也必须以斜线开头(反之亦然)。如果因为某些原因转储文件中的路径没有统一使用或不使用斜线开头,[33]也许需要修正这些路径,统一使用斜线开头或不使用斜线开头。

此外,复制操作生成的路径也会带来麻烦。Subversion支持在版本库中进行复制操作,也就是复制一个存在的路径,生成一个新的路径。问题是,svndumpfilter保留的某个文件或目录可能是由某个svndumpfilter排除的文件或目录复制而来的。也就是说,为了确保转储数据的完整性,svndumpfilter需要切断这些复制自被排除路径的文件与源文件的关系,还要将这些文件的内容以新建的方式添加到转储数据中。但是由于Subversion版本库转储文件格式中仅包含了修订版本的更改信息,因此源文件的内容基本上无法获得。如果你不能确定版本库中是否存在类似的情况,最好重新考虑一下到底保留/排除哪些路径。

最后,svndumpfilter就是字面上的意思,如果你尝试将目录trunk/my-project中的内容迁移到其自己版本库,你可以使用svndumpfilter include命令保持trunk/my-project目录下的所有修改。但是结果转储文件对于将要被加载入的版本库没有任何假定,特别的,目录trunk/my-project可能从创建这个目录的修订版本开始,而它不会包含以自己创建trunk目录的指示(因为trunk没有匹配include过滤)。在尝试将转储流存放到版本库之前,你需要确定任何转储流将要存在的目录必须存在于目标版本库。

版本库复制

有许多场景下会存在一个Subversion版本库的版本历史与另一个完全相同。或许最明显的就是在主版本库因为硬件故障或网络已出或其他原因而不可用时,维护一个简单的备份版本库。其他的场景包括,部署一个镜像版本库来分流压力,作为软升级机制等等。

Subversion 1.4提供了管理这种场景的工具—svnsyncsvnsync实质上就是通知版本库“重放”修订版本,一次一个,然后将修订版本信息模拟提交到另一个版本库。svnsync运行不需要能够本地访问版本库—它的参数是版本库URL,所有的工作是通过Subversion版本库访问层(RA)接口实现的,所有要做的就是读源版本库,然后读写访问目标版本库。

注意

当对远程源版本库使用svnsync时,Subversion版本库的服务器必须是Subversion1.4或更高的版本。

假定你已经有了一个希望镜像的源版本库,下一步就是你要有一个作为镜像的目标版本库。目标版本库可以使用任意文件系统数据存储后端(见“选择数据存储格式”一节),但是其中一定不能有历史版本。svnsync的通讯议对于源和目标版本库版本历史的不一致非常敏感,因此,虽然svnsync无法要求目标版本库是只读的,[34]最好的办法就是只允许镜像进程修改目标版本库内容。

警告

不要做出会对镜像版本库产生版本库历史偏移的修改,所有提交和版本库的属性修改必须是由svnsync执行的。

对于目标版本库的另一种需求是svnsync可以修改特定版本化属性。svnsync在目标版本库的修订版本0的特别属性上记录了簿记信息,因为svnsync在版本库的钩子系统的框架下工作的,版本库缺省的状态(关闭了版本库属性修改;见pre-revprop-change)是不够的。你会需要明确的实现pre-revprop-change钩子,而且你的脚本必须允许svnsync设置它的特别属性,有了这些准备工作,你就可以开始镜像版本库修订版本了。

提示

实现授权措施允许复制进程的操作,同时防止其他用户修改镜像版本库内容是一个好主意。

让我们在一个典型的镜像场景中浏览一下svnsync的使用,我们急着讨论实践推荐,但是如果你们不需要或者感到不适合你们的环境,你可以不必去关注。

作为开发者喜欢的版本控制系统的一个服务,我们会Subversion的源代码版本库镜像到Internet,存放在不同的主机上,而不仅仅只有最初的Subversion版本库。远程主机的全局设置允许匿名用户读取版本库的信息,但是需要认证的用户才能修改版本库。(请原谅我们在此刻这里曲解Subversion服务器配置的细节—这些内容在第 6 章 服务配置。)因为没有更多的理由来建立更有趣的例子,我们会在第三个机器上创建复制进程,我们正在使用的一个例子。

首先,我们会创建一个作为镜像的版本库,下面两步需要我们能够通过shell访问镜像版本库的机器。一旦版本库配置完成,我们不必再直接碰它了。

$ ssh admin@svn.example.com \
      "svnadmin create /path/to/repositories/svn-mirror"
admin@svn.example.com's password: ********
$

此刻,我们有了我们的版本库,因为我们服务器的配置,这个版本库现在“存在于”Internet。现在,因为除了复制进程我们不希望任何其他修改,我们需要将这个进程同其他可能的提交者区分开来。为此,我们的进程使用专用的用户,只有特定用户syncuser的提交和属性修改可以被执行。

我们会使用版本库的钩子系统来允许复制进程完成我们的任务,我们通过实现两个版本库事件钩子pre-revprop-change和start-commit来强制这个过程。我们的pre-revprop-change钩子脚本可以在例 5.2 “镜像版本库的 pre-revprop-change 钩子”找到,只是验证尝试修改属性的用户是syncuser,如果是,则允许修改;否则,拒绝修改。

例 5.2. 镜像版本库的 pre-revprop-change 钩子

#!/bin/sh 

USER="$3"

if [ "$USER" = "syncuser" ]; then exit 0; fi

echo "Only the syncuser user may change revision properties" >&2
exit 1

这里覆盖了修订版本属性修改,我们现在需要来确认只有用户syncuser允许提交新版本到版本库,我们使用了一个像例 5.3 “镜像版本库的 start-commit 钩子”start-commit钩子。

例 5.3. 镜像版本库的 start-commit 钩子

#!/bin/sh 

USER="$2"

if [ "$USER" = "syncuser" ]; then exit 0; fi

echo "Only the syncuser user may commit new revisions" >&2
exit 1

在安装了我们的钩子脚本和确定它们可以被Subversion服务器执行后,我们完成了镜像版本库的配置,现在我们开始实际的镜像。

对于svnsync,我们首先需要在目标版本库上注册源版本库,我们通过svnsync initialize实现这一步。注意,svnsync子命令提供了许多类似svn认证相关的选项,包括:--username--password--non-interactive--config-dir--no-auth-cache

$ svnsync help init
initialize (init): usage: svnsync initialize DEST_URL SOURCE_URL

Initialize a destination repository for synchronization from
another repository.

The destination URL must point to the root of a repository with
no committed revisions.  The destination repository must allow
revision property changes.

You should not commit to, or make revision property changes in,
the destination repository by any method other than 'svnsync'.
In other words, the destination repository should be a read-only
mirror of the source repository.

Valid options:
  --non-interactive        : do no interactive prompting
  --no-auth-cache          : do not cache authentication tokens
  --username arg           : specify a username ARG
  --password arg           : specify a password ARG
  --config-dir arg         : read user configuration files from directory ARG

$ svnsync initialize http://svn.example.com/svn-mirror \
                     http://svn.collab.net/repos/svn \
                     --username syncuser --password syncpass
Copied properties for revision 0.
$

我们的目标版本库现在记住了它是Subversion公共源代码版本库的镜像,注意我们在svnsync提供了一个用户名和密码—这是我们的镜像版本库pre-revprop-change钩子的要求。

注意

提供给svnsync的URL必须是指向目标和源版本库的根目录,这个工具不支持对版本库子树的镜像处理。

注意

svnsync的最初版本(在Subversion 1.4)有一些缺陷—用来认证的--username--password命令行参数同时作用于源和目标版本库。显然,我们无法保证同步的用户认证信息是相同的,如果不一样,用户使用非交互模式(--non-interactive选项)来运行svnsync时会遇到这个问题。

现在有趣的部分开始了,通过一个单独的子命令,我们可以告诉svnsync将所有未镜像的修订版本从源版本库拷贝到目标版本库。[35]svnsync synchronize子命令会查看目标版本库特定修订版本的属性,并且检测同步的版本库是哪一个,以及最新镜像的修订版本是0。然后它会查询源版本库,检测其最新的修订版本。最后,它会询问源版本库服务器来开始重演从修订版本0到最新修订版本。svnsync从源版本库服务器得到返回的结果,然后将其作为新的提交转发到目标版本库服务器。

$ svnsync help synchronize
synchronize (sync): usage: svnsync synchronize DEST_URL

Transfer all pending revisions from source to destination.
…
$ svnsync synchronize http://svn.example.com/svn-mirror \
                      --username syncuser --password syncpass
Committed revision 1.
Copied properties for revision 1.
Committed revision 2.
Copied properties for revision 2.
Committed revision 3.
Copied properties for revision 3.
…
Committed revision 23406.
Copied properties for revision 23406.
Committed revision 23407.
Copied properties for revision 23407.
Committed revision 23408.
Copied properties for revision 23408.

镜像修订版本有一点特别有趣,首先是到目标版本库的修订版本提交,然后跟着属性修改。这是因为最初的提交是通过用户syncuser执行的,而时间戳是提交的时间,而且Subversion底层的版本库访问接口不允许在提交时任意修改修订版本属性,所以svnsync会立即使用属性修改,将源版本库发现的所有修订版本属性拷贝到目标版本库,这其中就包括了修改作者和时间戳使之与源版本库一致的效果。

值得注意的是svnsync会小心簿记所有的操作,可以安全的中断并重新开始,而不必破坏镜像数据的完整性。如果在svnsync synchronize时出现网络故障,只需要重新运行svnsync synchronize,她会从中断处开始。实际上,随着新的修订版本在源版本库出现,这样就可以保证你的镜像不会过时。

然而,这个进程还有一点不雅的地方,因为Subversion属性修改可以发生在整个生命周期的任何时候,不会留下任何审计痕迹来说明所作的修改,扶植进程需要对此额外关注。如果你已经镜像了某个版本库的15个修订版本,而某个人修改了修订版本12的属性,你需要告诉它手工使用(或一些额外的工具)svnsync copy-revprops子命令,只是简单的重新复制某个特定修订版本的属性。

$ svnsync help copy-revprops
copy-revprops: usage: svnsync copy-revprops DEST_URL REV

Copy all revision properties for revision REV from source to
destination.
…
$ svnsync copy-revprops http://svn.example.com/svn-mirror 12 \
                        --username syncuser --password syncpass
Copied properties for revision 12.
$

版本库复制只是一个壳,你一定会希望利用这个进程的自动化。例如,如果我们的例子是一个“拖和推”设置,你或许希望在post-commit和post-revprop-change钩子实现中从你的主版本库将修改推倒一个或多个镜像,这样就可以近乎实时的保持镜像的时效性。

而且,这样做并不平凡,在人证用户只有部分读权限时svnsync也会优雅的镜像,它只会拷贝允许查看的版本库内容,显然这种镜像不适合备份方案。

只要用户与版本库和镜像的交互继续,是可以有一个工作拷贝直接与这两个版本库交互。但是你需要跳出几个圈子才能做到这样。第一,你需要保证主和镜像版本库有相同的UUID(通常缺省不是相同),你可以加载一个包含住版本库的UUID转储文件来设置镜像版本库的UUID。

$ cat - <<EOF | svnadmin load --force-uuid dest
SVN-fs-dump-format-version: 2

UUID: 65390229-12b7-0310-b90b-f21a5aa7ec8e
EOF
$

现在两个版本库有了相同的UUID,你可以使用svn switch --relocate指向任何你希望操作的版本库,详细方法见svn switch。这里也可能有危险,尽管如果主和镜像版本库没有同步的关闭,一个工作拷贝对于主版本库没有过时,而重定位的镜像却是过时的,显然期望存在的修订版本缺失会造成困惑。如果发生这个情况,你可以将工作拷贝重新定位到主版本库,然后等待镜像版本库变成最新,或者将工作拷贝恢复到你知道的版本库修订版本,再尝试重新定位。

最后我们需要意识到,svnsync只支持修订版本为基础的复制,它没有包括诸如钩子实现,版本库或服务器配置数据,未提交事务或关于用户锁定版本库路径的信息,只有Subversion版本库转储文件格式在复制时包含这些信息。

版本库备份

尽管现代计算机的诞生带来了许多便利,但有一件事听起来是完全正确的—有时候,事情变的糟糕,很糟糕,动力损耗、网络中断、坏掉的内存和损坏的硬盘都是对魔鬼的一种体验,即使对于最尽职的管理员,命运也早已注定。所以我们来到了这个最重要的主题—怎样备份你的版本库数据。

Subversion版本库管理有两种备份方法—完全和增量。一个完全的版本库备份包含了在重大灾难后重建版本库所需的所有信息,通常,这意味着对版本库目录(包括Berkeley DB或FSFS环境)的完全复制,增量备份的内容要少一些,只包含在上次备份后改变的部分。

随着完全备份的使用,这种幼稚的方法或许看起来有点不够健全,但是除非你临时关闭所有访问版本库的进程,否则这种递归的拷贝目录会有产生错误拷贝的风险。Berkeley DB的情况下,其文档中记述了按照什么顺序拷贝可以保证正确的备份拷贝,FSFS也有类似的顺序。但是你不必自己实现这种算法,因为Subversion的开发团队已经这样做了。svnadmin hotcopy关注了在热拷贝版本库时的所有细节,它的调用就像Unix的cp或Windows的copy一样琐碎:

$ svnadmin hotcopy /path/to/repos /path/to/repos-backup

作为结果的备份是一个完全功能的版本库,当发生严重错误时可以作为你的活动版本库的替换。

当进行Berkeley DB版本库的备份时,你可以指导svnadmin hotcopy清理源版本库中无用的Berkeley DB日志文件(见“删除不使用的Berkeley DB日志文件”一节),只需要简单的在命令行里提供--clean-logs

$ svnadmin hotcopy --clean-logs /path/to/bdb-repos /path/to/bdb-repos-backup

还有一些附加的加工命令,Subversion源程序中的tools/backup/目录包含了hot-backup.py脚本,这个脚本在hot-backup.py之上增加了备份管理功能,你可以保存每个版本库最近的配置号码。为了防止与以前的备份冲突,它会自动管理备份版本库目录名字,“循环”利用备份名,删除掉旧的,保存新的。即使你也有一个增量的备份,你还是会希望有规律的运行这个程序。例如,你会在一个调度程序(例如Unix系统的cron)中调用hot-backup.py会导致它在半夜执行(或者是任何你认为安全的时间间隔)。

一些管理员使用不同的备份机制,通过生成和保存版本库转储数据。我们在“版本库数据的移植”一节中描述如何使用svnadmin dump --incremental来对一个修订版本或一个修订版本范围执行增量备份。当然,通过取消--incremental选项可以得到完整的备份。在备份信息中方法的值非常灵活—不会与特定平台,版本化的文件系统类型或Subversion和Berkeley DB的版本绑定。但是灵活带来了代价,数据恢复会占用更长的时间—比每个新版本提交更长。此外,在非完全的量转储生成时,对已经备份修订版本的修订版本属性的修改不会被采纳,因为这些原因,我们不建议你单独依赖转储为基础的备份方法。

如你所见,几种备份方式都有各自的优点,最简单的方式是完全热备份,将会每次建立版本库的完美复制品,这意味着如果当你的活动版本库发生了什么事情,你可以用备份恢复。但不幸的是,如果你维护多个备份,每个完全的备份会吞噬掉和你的活动版本库同样的空间。与之相对照的是增量备份,能够快速生成小的备份,但是恢复过程将会很痛苦,通常要包括多个增量拷贝的应用。其他方法都有自己的特点,管理员需要在创建拷贝和恢复的代价之间寻求平衡。

svnsync(见“版本库复制”一节)实际上提供了一种更易实施的妥协方法,如果你有规律的同步镜像版本库,则在必要时,镜像版本库就成了主版本库发生问题时的一个合适替代者。这个方法最大的缺点是只有版本化的数据得到了同步—版本库的配置信息,用户指定的路径锁定和其它以物理形式存在于版本库路径而不存在于版本库虚拟文件系统的项目不会被svnsync处理。

在每一种备份情境下,版本库管理员需要意识到对未版本化的修订版本属性的修改对备份的影响,因为这些修改本身不会产生新的修订版本,所以不会触发post-commit的钩子程序,也不会触发pre-revprop-change和post-revprop-change的钩子。 [36]而且因为你可以改变修订版本的属性,而不需要遵照时间顺序—你可在任何时刻修改任何修订版本的属性—因此最新版本的增量备份不会捕捉到以前特定修订版本的属性修改。

通常说来,在每次提交时,只有妄想狂才会备份整个版本库,然而,假设一个给定的版本库拥有一些恰当粒度的冗余机制(如每次提交的邮件)。版本库管理员也许会希望将版本库的热备份引入到系统级的每夜备份,对大多数版本库,归档的提交邮件为保存资源提供了足够的冗余措施,至少对于最近的提交。但是它是你的数据—你喜欢怎样保护都可以。

通常情况下,最好的版本库备份方式是混合的,你可以平衡完全和增量备份,另外配合提交邮件的归档。Subversion开发者,举个例子,使用hot-backup.py对Subversion版本库进行完全备份并使用rsync同步这些备份;同时保存所有的提交日至和修改通知邮件;并且使用许多志愿者维护的svnsync镜像版本库。你们的解决方案可能非常类似,但是要实现满足需要和便利性的平衡。无论你做了什么,你需要一次次的验证你的备份—就像要检查备用轮胎是否有个窟窿?当然,所有做的事情都无法回避我们的硬件来自钢铁的命运,[37]它将帮助你从艰难的时光恢复过来。



[30] 或者是, “sync” ?

[31] 比如:硬盘 + 大号电磁铁 = 毁灭。

[32] 那就是你是用版本控制的原因,对吗?

[33] 尽管svnadmin dump对是否以斜线作为路径的开头有统一的规定——这个规定就是不以斜线作为路径的开头——其它生成转储文件的程序不一定会遵守这个规定。

[34] 实际上,它不是真的完全只读,或者svnsync本身有时间将版本库历史拷入。

[35] 要预先警告一下,尽管对于普通读者只需要几秒钟就可以理解下面的输出,而对于整个镜像过程花费的时间可能会非常长。

[36] svnadmin setlog可以被绕过钩子程序被调用。

[37] 你知道的—只是对各种变化莫测的问题的统称。