﻿<?xml version="1.0" encoding="UTF-8"?>
<chapter id="svn.reposadmin">
  <title>版本库管理</title>

  <simplesect>
    <para>Subversion版本库是保存任意数量项目版本化数据的中央仓库，因此，版本库成为管理员关注的对象。版本库的维护一般并不需要太多的关注，但为了避免一些潜在的问题和解决一些实际问题，理解怎样适当的配置和维护还是非常重要的。</para>

    <para>在这一章里，我们将讨论如何建立和配置一个Subversion版本库，还会讨论版本库的维护，包括<command>svnlook</command>和<command>svnadmin</command>工具的使用（它们都包含在Subversion中）。我们将说明一些常见的问题和错误，并提供一些安排版本库数据的建议。</para>

    <para>如果您只是以普通用户的身份访问版本库对数据进行版本控制（就是说通过Subversion客户端），您完全可以跳过本章。但是如果您已经是或打算成为Subversion版本库的管理员，<footnote>
        <para>这可能听起来很崇高， 但我们所指的只是那些对管理别人工作拷贝数据之外的神秘领域感兴趣的人。</para>
      </footnote>您一定要关注一下本章的内容。</para>
  </simplesect>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.reposadmin.basics">
    <title>版本库基本知识</title>

    <para>在进入版本库管理这块宽广的主题之前，让我们进一步确定一下版本库的定义，它是怎样工作的？让人有什么感觉？它希望茶是热的还是冰的，加糖或柠檬吗？作为一名管理员，你应该既从逻辑视角－数据在版本库中如何展示，又能从物理具体细节的视角－版本库如何响应一个非Subversion的工具，来理解版本库的组成。下面的小节从一个比较高的层面覆盖了这些基本概念。</para>

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.basics.txnsrevs">
      <title>理解事务和修订版本</title>

      <para>从概念上来说，Subversion的版本库就是一串目录树。每一个目录树，就是版本库的文件和目录在某一时刻的快照。这些快照是客户端使用者操作的结果，叫做修订版本。</para>

      <para>每一个修订版本都是以事务树开始其生命周期。做提交操作时，客户端建立了一个映射本地修改的Subversion事务（加上客户端提交操作后任何对版本库的更改），然后指导版本库将该树存储为下一个快照。要是提交成功，这个事务就会成为新的修订版本树，并被赋予新的修订版本号。如果因为某些原因提交失败，事务会被销毁，客户端将被通知这个事务失败。</para>

      <para>更新的动作也类似这样。客户端建立一个临时的事务树，映射工作文件的状态。然后版本库比较事务树和被请求的修订版本树（通常是最新的，也就是最“年轻”的修订版本树），然后发回消息通知客户端哪些变更需要将拷贝发送到修订版本树。更新完成后，临时事务将被删除。</para>

      <para>事务树的使用是对版本库中版本控制文件系统产生永久变更的唯一方法。一个事务的生命周期非常灵活，了解这一点很重要。在更新的情况下，事务只是马上会被销毁的临时树。在提交的情况下，事务会变成固定的修订版本（如果失败的情况下，则会被删除）。在出现错误或bug的情况下，事务可能会被留在版本库中（不会影响任何东西，但是会占据空间）。</para>

      <para>理论上，有一天整个流程能够发展到对事务进行更加细密的流程控制。可以想象一个系统，在客户端完成操作，将要保存到版本库中时，每个加到它的事务都变成一个修订版本。这将会使每一个新的提交都可以被别人查看到，也许是主管，也许是质量保证小组，他们可以决定是要接收这个事务成为修订版本，还是放弃它。</para>
    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.basics.revprops">
      <title>未受版本控制的属性</title>

      <para>事务和修订版本在Subversion版本库中可以附加属性。这些属性就是普通的属性名和属性值的映射，被用来存储与对应目录树有关的信息。这些属性名和属性值跟你的其他数据一样，被存储在版本库文件系统中。</para>

      <para>修订版本和事务的属性对于存储一个跟目录树相关，但与树中的某个具体目录或文件不相关的性质很有用－即并不被客户端工作拷贝所管理的属性。举例来说，当一个新的提交事务在版本库中被创建时，Subversion给这个事务添加一个叫做<literal>svn:date</literal>的属性—一个表示事务何时被创建的时间戳。当提交进程结束，该事务成为一个固定的修订版本，这个目录树被赋予一个用来存储这个版本作者名称的属性（<literal>svn:author</literal>）和一个用来存储与这个修订版本关联日志信息的属性（<literal>svn:log</literal>）。</para>

      <para>修订版本和事务的属性都是未受版本控制的－因为当它们被修改时，先前的值就被完全舍弃了。修订版本树自身是不能变更的，与之关联的属性可以修改。你可在日后添加、删除、修改修订版本的属性。如果你提交一个新的修订版本之后意识到遗漏了一些信息或在日志中的拼写错误，你可以直接以正确的信息覆盖<literal>svn:log</literal>它的值。</para>
    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.basics.backends">
      <title>版本库数据存储</title>

      <para>在Subversion1.2中，版本库中存储数据有两种方式。一种是在Berkeley DB数据库中存储数据；另一种是使用普通的文件，使用自定义格式。因为Subversion的开发者称版本库为（版本化的）文件系统，他们接受了称后一种存储方式为FSFS<footnote>
          <para>读作<quote>fuzz-fuzz</quote>, 如果Jack
            Repenning说起这个问题。</para>
        </footnote>的习惯，也就是说，使用本地操作系统文件系统来存储数据的版本化文件的系统。</para>

      <para>建立一个版本库时，管理员必须决定使用Berkeley DB还是FSFS。它们各有优缺点，我们将详细描述。这两个中并没有一个是更正式的，访问版本库的程序与采用哪一种实现方式无关。访问程序并不知道版本库如何存储数据，它们只是从版本库的API读取到修订版本和事务树。</para>

      <para><xref linkend="svn.reposadmin.basics.backends.tbl-1"/>从总体上比较了Berkeley DB和FSFS版本库，下一部分将会详细讲述细节。</para>

      <table id="svn.reposadmin.basics.backends.tbl-1">
        <title>版本库数据存储对照表</title>

        <tgroup cols="3">
          <thead>
            <row>
              <entry>特性</entry>

              <entry>Berkeley DB</entry>

              <entry>FSFS</entry>
            </row>
          </thead>

          <tbody>
            <row>
              <entry>对操作中断的敏感</entry>

              <entry>很敏感；系统崩溃或者权限问题会导致数据库<quote>塞住</quote>，需要定期进行恢复。</entry>

              <entry>不敏感。</entry>
            </row>

            <row>
              <entry>可只读加载</entry>

              <entry>不能</entry>

              <entry>可以</entry>
            </row>

            <row>
              <entry>存储平台无关</entry>

              <entry>不能</entry>

              <entry>可以</entry>
            </row>

            <row>
              <entry>可从网络文件系统访问</entry>

              <entry>不能</entry>

              <entry>可以</entry>
            </row>

            <row>
              <entry>版本库大小</entry>

              <entry>稍大</entry>

              <entry>稍小</entry>
            </row>

            <row>
              <entry>可扩展性：修订版本树的数量</entry>

              <entry>数据库，没有限制</entry>

              <entry>许多古老的本地文件系统在处理单一目录包含上千个条目时出现问题。</entry>
            </row>

            <row>
              <entry>可扩展性：文件较多的目录</entry>

              <entry>较慢</entry>

              <entry>较快</entry>
            </row>

            <row>
              <entry>速度：检出最新的代码</entry>

              <entry>较快</entry>

              <entry>较慢</entry>
            </row>

            <row>
              <entry>速度： 大的提交</entry>

              <entry>较慢，但是时间被分配在整个提交操作中</entry>

              <entry>较快，但是最后较长的延时可能会导致客户端操作超时</entry>
            </row>

            <row>
              <entry>组访问权处理</entry>

              <entry>对于用户的umask设置十分敏感，最好只由一个用户访问。</entry>

              <entry>对umask设置不敏感</entry>
            </row>

            <row>
              <entry>功能成熟时间</entry>

              <entry>2001年开始使用</entry>

              <entry>2004年开始使用</entry>
            </row>
          </tbody>
        </tgroup>
      </table>

      <sect3 id="svn.reposadmin.basics.backends.bdb">
        <title>Berkeley DB</title>

        <para>在Subversion的初始设计阶段，开发者因为多种原因而决定采用Berkeley
        DB，比如它的开源协议、事务支持、可靠性、性能、简单的API、线程安全、支持游标等。</para>

        <para>Berkeley DB提供了真正的事务支持－这或许是它最强大的特性，访问你的Subversion版本库的多个进程不必担心偶尔会破坏其他进程的数据。事务系统提供的隔离对于任何给定的操作，Subversion版本库代码看到的只是数据库的静态视图－而不是一个在其他进程影响不断变化的数据库－并能够根据该视图作出决定。如果该决定正好同其他进程所做操作冲突，整个操作会回滚，就像什么都没有发生一样，并且Subversion会优雅的再次对更新的静态视图进行操作。</para>

        <para>Berkeley DB另一个强大的特性是热备份－不必<quote>脱机</quote>就可以备份数据库环境的能力。我们将会在<xref
        linkend="svn.reposadmin.maint.backup"/>讨论如何备份你的版本库，能够不停止系统对版本库做全面备份的好处是显而易见的。</para>

        <para>Berkeley DB同时是一个可信赖的数据库系统。Subversion利用了Berkeley DB可以记日志的便利，这意味着数据库先在磁盘上写一个日志文件，描述它将要做的修改，然后再做这些修改。这是为了确保如果如果任何地方出了差错，数据库系统能恢复到先前的检查点&mdash;一个日志文件认为没有错误的位置，重新开始事务直到数据恢复为一个可用的状态。关于Berkeley DB日志文件的更多信息请查看<xref
        linkend="svn.reposadmin.maint.diskspace"/>。</para>

        <para>但是每朵玫瑰都有刺，我们也必须记录一些Berkeley DB已知的缺陷。首先，Berkeley DB环境不是跨平台的。你不能简单的拷贝一个在Unix上创建的Subversion版本库到一个Windows系统并期望它能够正常工作。尽管Berkeley DB数据库的大部分格式是不受架构约束的，但环境还是有一些方面没有独立出来。其次，使用Berkeley DB的Subversion不能在95/98系统上运行&mdash;如果你需要将版本库建在一个Windows机器上，请装到Windows2000或WindowsXP上。另外，Berkeley DB版本库不能放在网络共享文件夹中，尽管Berkeley DB承诺如果按照一套特定规范的话，可以在网络共享上正常运行，但实际上已知的共享类型几乎都不满足这套规范。</para>

        <para>最后，因为Berkeley DB的库直接链接到了Subversion中，它对于中断比典型的关系型数据库系统更为敏感。大多数SQL系统，举例来说，有一个主服务进程来协调对数据库表的访问。如果一个访问数据库的程序因为某种原因出现问题，数据库守护进程察觉到连接中断会做一些清理。因为数据库守护进程是唯一访问数据库表的进程，应用程序不需要担心访问许可的冲突。但是，这些情况与Berkeley DB不同。Subversion（和使用Subversion库的程序）直接访问数据库的表，这意味着如果有一个程序崩溃，就会使数据库处于一个暂时的不一致、不可访问的状态。当这种情况发生时，管理员需要让Berkeley DB恢复到一个检查点，这的确有点讨厌。除了崩溃的进程，还有一些情况能让版本库出现异常，比如程序在数据库文件的所有权或访问权限上发生冲突。因为Berkeley DB版本库非常快，并且可以扩展，非常适合使用一个单独的服务进程，通过一个用户来访问&mdash;比如Apache的<command>httpd</command>或<command>svnserve</command>（参见<xref linkend="svn.serverconfig"/>）&mdash;而不是多用户通过<literal>file:///</literal>或<literal>svn+ssh://</literal>URL的方式多用户访问。如果将Berkeley DB版本库直接用作多用户访问，请先阅读<xref linkend="svn.serverconfig.multimethod"/>。</para>
      </sect3>

      <sect3 id="svn.reposadmin.basics.backends.fsfs">
        <title>FSFS</title>

        <para>在2004年中期，另一种版本库存储系统慢慢形成了：一种不需要数据库的存储系统。FSFS版本库在单一文件中存储修订版本树，所以版本库中所有的修订版本都在一个子文件夹中有限的几个文件里。事务在单独的子目录中被创建，创建完成后，一个单独的事务文件被创建并移动到修订版本目录，这保证提交是原子性的。因为一个修订版本文件是持久不可改变的，版本库也可以做到热备份，就象Berkeley DB版本库一样。</para>

        <para>修订版本文件格式代表了一个修订版本的目录结构，文件内容，和其它修订版本树中相关信息。不像Berkeley DB数据库，这种存储格式可跨平台并且与CPU架构无关。因为没有日志或用到共享内存的文件，数据库能被网络文件系统安全的访问和在只读环境下检查。缺少数据库花消同时也意味着版本库的总体体积可以稍小一点。</para>

        <para>FSFS也有一种不同的性能特性。当提交大量文件时，FSFS使用O(N)算法来追加条目，而Berkeley DB则用(N^2)算法来重写整个目录。另一方面，FSFS通过写入与上一个版本比较的变化来记录新版本，这也意味着获取最新修订版本时会比Berkeley DB慢一点，提交时FSFS也会有一个更长的延迟，在某些极端情况下会导致客护端在等待回应时超时。</para>


        <para>最重要的区别是当出现错误时FSFS不会楔住的能力。如果使用Berkeley DB的进程发生许可错误或突然崩溃，数据库会一直无法使用，直到管理员恢复。假如在应用FSFS版本库时发生同样的情况，版本库不会受到任何干扰，最坏情况下也就是会留下一些事务数据。</para>

        <para>唯一真正对FSFS不利的是相对于Berkeley DB的不成熟，缺乏足够的使用和压力测试，许多关于速度和可扩展性的判断都是建立在良好的猜测之上。在理论上，它承诺会降低管理员新手的门槛并且更加不容易发生问题。在实践中，只有时间可以证明。</para>

      </sect3>
    </sect2>
  </sect1>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.reposadmin.create">
  
    <title>版本库的创建和配置</title>

    <para>创建一个 Subversion 版本库出乎寻常的简单。 Subversion
    提供的<command>svnadmin</command> 工具，有一个执行这个功能的子命令。要建立一个新的版本库，只需要运行：</para>

    <screen>
$ svnadmin create /path/to/repos
</screen>

    <para>这个命令在目录<filename>/path/to/repos</filename>创建了一个新的版本库。这个新的版本库会以修订版本版本0开始其生命周期，里面除了最上层的根目录(<filename>/</filename>)，什么都没有。刚开始，修订版本0有一个修订版本属性<literal>svn:date</literal>，设置为版本库创建的时间。</para>

    <para>在 Subversion 1.2中，版本库默认使用FSFS后端存储方式来创建（见<xref
      linkend="svn.reposadmin.basics.backends"/>）。不管怎样，存储类型可以使用<option>--fs-type</option>参数明确说明：</para>

    <screen>
$ svnadmin create --fs-type fsfs /path/to/repos
$ svnadmin create --fs-type bdb /path/to/other/repos
</screen>

    <warning>
      <para>不要在网络共享上创建Berkeley DB版本库&mdash;它不能存在于诸如NFS， AFS或Windows SMB的远程文件系统中，Berkeley 数据要求底层文件系统实现严格的POSIX锁定语义，几乎没有任何网络文件系统提供这些特性，假如你在网络共享上使用Berkeley DB，结果是不可预知的——许多错误可能会立刻发现，也有可能在几个月之后才能发现</para>

      <para>假如你需要多台计算机来访问，你需要在网络共享上创建FSFS版本库，而不是Berkeley DB的版本库。或者更好的办法，你建立一个真正的服务进程（例如Apache或<command>svnserve），把版本库放在</command>服务器能访问到的本地文件系统中，以便能通过网络访问。详情请参看linkend="svn.serverconfig"/>。</para>
    </warning>

    <para>你可能已经注意到了，<command>svnadmin</command>命令的路径参数只是一个普通的文件系统路径，而不是一个<command>svn</command>客户端程序访问版本库时使用的URL。<command>svnadmin</command>和<command>svnlook</command>都被认为是服务器端工具&mdash;它们在版本库所在的机器上使用，用来检查或修改版本库，不能通过网络来执行任务。一个Subversion的新手通常会犯的错误，就是试图将URL（甚至<quote>本地</quote><literal>file:</literal>路径）传给这两个程序。</para>

    <para>所以，当你运行<command>svnadmin create</command>命令后，就会在运行目录创建一个崭新的Subversion版本库，让我们看一下在这个目录创建中创建了什么。</para>

    <screen>
$ ls repos
conf/  dav/  db/  format  hooks/  locks/  README.txt
</screen>

    <para>除了<filename>README.txt</filename>和<filename>format</filename>文件，版本库目录就是一些子目录了。就像Subversion其它部分的设计一样，模块化是一个很重要的原则，而且层次化的组织要比杂乱无章好。下面是对新的版本库目录中各个项目的简要介绍：</para>

    <variablelist>
      <varlistentry>
        <term>conf</term>

        <listitem>
          <para>一个存储版本库配置文件的目录。</para>
        </listitem>
      </varlistentry>

      <varlistentry>
        <term>dav</term>

        <listitem>
          <para>提供给Apache和mod_dav_svn的目录，让它们存储自己的数据。</para>
        </listitem>
      </varlistentry>

      <varlistentry>
        <term>db</term>

        <listitem>
          <para>你所有的受版本控制数据的所在之处。这个目录或者是个Berkeley DB环境（满是数据表和其他东西），或者是一个包含修订版本文件的FSFS环境。</para>
        </listitem>
      </varlistentry>

      <varlistentry>
        <term>format</term>

        <listitem>
          <para>包含了用来表示版本库布局版本号的整数。</para>
        </listitem>
      </varlistentry>

      <varlistentry>
        <term>hooks</term>

        <listitem>
          <para>一个存储钩子脚本模版的目录（还有钩子脚本本身， 如果你安装了的话）。</para>
        </listitem>
      </varlistentry>

      <varlistentry>
        <term>locks</term>

        <listitem>
          <para>一个存储Subversion版本库锁定数据的目录，被用来追踪对版本库的访问。</para>
        </listitem>
      </varlistentry>

      <varlistentry>
        <term>README.txt</term>

        <listitem>
          <para>这个文件只是用来告诉它的阅读者，他现在看的是 Subversion 的版本库。</para>
        </listitem>
      </varlistentry>
    </variablelist>

    <para>一般来说，你不需要手动干预版本库。<command>svnadmin</command>工具应该足以用来处理对版本库的任何修改，或者你也可以使用第三方工具（比如Berkeley DB的工具包）来调整部分版本库。不过还是会有些例外情况，我们会在这里提到。</para>

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.create.hooks">
      <title>钩子脚本</title>

      <para>所谓<firstterm>钩子</firstterm>就是与一些版本库事件触发的程序，例如新修订版本的创建，或是未版本化属性的修改。每个钩子都会被告知足够多的信息，包括那是什么事件，所操作的对象，和触发事件的用户名。通过钩子的输出或返回状态，钩子程序能让工作继续、停止或是以某种方式挂起。</para>

      <para>
        <firstterm>默认情况下，钩子的子目录中包含各种版本库钩子模板。</firstterm>
      </para>

      <screen>
$ ls repos/hooks/
post-commit.tmpl          post-unlock.tmpl          pre-revprop-change.tmpl
post-lock.tmpl            pre-commit.tmpl           pre-unlock.tmpl
post-revprop-change.tmpl  pre-lock.tmpl             start-commit.tmpl       
</screen>
      <para>对每种Subversion版本库支持的钩子的都有一个模板，通过查看这些脚本的内容，你能看到是什么事件触发了脚本及如何给传脚本传递数据。同时，这些模版也是如何使用这些脚本，结合Subversion支持的工具来完成有用任务的例子。要实际安装一个可用的钩子，你需要在<filename>repos/hooks</filename>目录下安装一些与钩子同名（如 <command>start-commit</command>或者<command>post-commit</command>）的可执行程序或脚本。</para>

      <para>在Unix平台上，这意味着要提供一个与钩子同名的脚本或程序（可能是shell 脚本，Python 程序，编译过的c语言二进制文件或其他东西）。当然，脚本模板文件不仅仅是展示了一些信息&mdash;在Unix下安装钩子最简单的办法就是拷贝这些模板，并且去掉.tmpl扩展名，然后自定义钩子的内容，确定脚本是可运行的。Windows用文件的扩展名来决定一个程序是否可运行，所以你要使程序的基本名与钩子同名，同时，它的扩展名是Windows系统所能辨认的，例如<filename>exe</filename>、<filename>com</filename>和批处理的<filename>bat</filename>。</para>

      <tip>      <para>由于安全原因，Subversion版本库在一个空环境中执行钩子脚本&mdash;就是没有任何环境变量，甚至没有<literal>$PATH</literal>或<literal>%PATH%</literal>。由于这个原因，许多管理员会感到很困惑，它们的钩子脚本手工运行时正常，可在Subversion中却不能运行。要注意，必须在你的钩子中设置好环境变量或为你的程序指定好绝对路径。</para>
</tip>
      <para>目前Subversion有已实现了九种钩子：</para>

      <variablelist>
        <varlistentry>
          <term>
            <filename>start-commit</filename>
          </term>
          <listitem>            <para>它在提交事务产生前已运行，通常用来判定一个用户是否有权提交。版本库传给该程序两个参数：到版本库的路径，和要进行提交的用户名。如果程序返回一个非零值，会在事务产生前停止该提交操作。如果钩子程序要在stderr中写入数据，它将排队送至客户端。</para>
          </listitem>
        </varlistentry>

        <varlistentry>
        <term><filename>pre-commit</filename></term>
        <listitem><para>在事务完成提交之前运行，通常这个钩子是用来保护因为内容或位置（例如，你要求所有到一个特定分支的提交必须包括一个bug追踪的ticket号，或者是要求日志信息不为空）而不允许的提交。版本库传递两个参数到程序：版本库的路径和正在提交的事务名称，如果程序返回非零值，提交会失败，事务也会删除。如果钩子程序在stderr中写入了数据，也会传递到客户端。</para>
        

            <para>Subversion的分发版本包括了一些访问控制脚本（在Subversion源文件目录树的<filename>tools/hook-scripts</filename>目录），可以用来被<command>pre-commit</command>调用来实现精密的写访问控制。另一个选择是使用Apache的httpd模块<command>mod_authz_svn</command>，可以对单个目录进行读写访问控制（见<xref
              linkend="svn.serverconfig.httpd.authz.perdir"/>）。在未来的Subversion版本中,我们计划直接在文件系统中实现访问控制列表（ACLs）。</para>
          </listitem>
        </varlistentry>

        <varlistentry>
          <term>
            <filename>post-commit</filename>
          </term>

          <listitem>            <para>它在事务完成后运行，创建一个新的修订版本。大多数人用这个钩子来发送关于提交的描述性电子邮件，或者作为版本库的备份。版本库传给程序两个参数：到版本库的路径和被创建的新的修订版本号。退出程序会被忽略。</para>
          
          <para>Subversion分发版本中包括<command>mailer.py</command>和<command>commit-email.pl</command>脚本（存于Subversion源代码树中的<filename>tools/hook-scripts/</filename>目录中）可以用来发送描述给定提交的email（并且或只是追加到一个日志文件），这个mail包含变化的路径清单，提交的日志信息、日期和作者以及修改文件的GNU区别样式输出。</para>

            <para>Subversion提供的另一个有用的工具是<command>hot-backup.py</command>脚本（在Subversion源代码树中的tools/backup/目录中）。这个脚本可以为Subversion版本库进行热备份（Berkeley DB数据库后端支持的一种特性），可以制作版本库每次提交的快照作为归档和紧急情况的备份。</para>
          </listitem>
        </varlistentry>

        <varlistentry>
          <term>
            <filename>pre-revprop-change</filename>
          </term>

          <listitem>
<para>因为Subversion的修订版本属性不是版本化的，对这类属性的修改（例如提交日志属性<literal>svn:log</literal>）将会永久覆盖以前的属性值。因为数据在此可能丢失，所以Subversion提供了这种钩子（及与之对应的<filename>post-revprop-change</filename>），因此版本库管理员可用一些外部方法记录变化。作为对丢失未版本化属性数据的防范，Subversion客户端不能远程修改修订版本属性，除非为你的版本库实现这个钩子。</para>

            <para>这个钩子在对版本库进行这种修改时才会运行，版本库给钩子传递四个参数：到版本库的路径，要修改属性的修订版本，经过认证的用户名和属性自身的名字。</para>
          </listitem>
        </varlistentry>

        <varlistentry>
          <term>
            <filename>post-revprop-change</filename>
          </term>

          <listitem>
            <para>我们在前面提到过，这个钩子与<filename>pre-revprop-change</filename>对应。事实上，因为多疑的原因，只有存在<filename>pre-revprop-change</filename>时这个脚本才会执行。当这两个钩子都存在时，<filename>post-revprop-change</filename>在修订版本属性被改变之后运行，通常用来发送包含新属性的email。版本库传递四个参数给该钩子：到版本库的路径，属性存在的修订版本，经过校验的产生变化的用户名，和属性自身的名字。</para>

            <para>Subversion分发版本中包含<command>propchange-email.pl</command>脚本（在Subversion源代码树中的<filename>tools/hook-scripts/</filename>目录中），可以用来发送修订版本属性修改细节的email（并且或只是追加到一个日志文件）。这个email包含修订版本和发生变化的属性名，作出修改的用户和新属性值。
            </para>
          </listitem>
        </varlistentry>
        
        
        <varlistentry>
          <term><filename>pre-lock</filename></term>
          <listitem>
            <para>这个钩子会在每次有人尝试锁定文件时执行，可以防止完全的锁定，或者用来制定控制哪些用户可以锁定特定路径的复杂策略，如果钩子发现已存在的钩子，也可以决定是否<quote>窃取</quote>这个钩子。版本库传递三个参数到钩子：到版本库的路径、锁定的路径和企图执行锁定的用户。如果程序返回非零值，锁定动作会退出，并且所有的标准输出返回到客户端。
           </para>
          </listitem>
        </varlistentry>

        <varlistentry>
          <term><filename>post-lock</filename></term>
          <listitem>
            <para>这个钩子在一个路径被锁定后执行，锁定的路径传递给钩子的标准输入，这个钩子也接受两个参数：到版本库的路径和企图执行锁定的用户。可以用这个钩子发送通知邮件来记录这种锁定事件，因为锁定已经发生，输出会被钩子忽略。
           </para>
          </listitem>
        </varlistentry>

        <varlistentry>
          <term><filename>pre-unlock</filename></term>
          <listitem>
            <para>这个钩子在某人企图删除一个文件上的钩子时发生，可以用来制定哪些用户可以解除文件锁定的策略。制定破坏锁定的策略非常重要，如果一个用户A锁定了一个文件，允许用户B打开这个锁？如果这个锁已经一周了呢？这种事情可以通过钩子决定并执行。版本库传递三个参数到钩子：到版本库的路径、将要解锁的路径和企图解锁的用户。如果程序返回非零值，解锁操作退出并会将标准错误传输到客户端。
           </para>
          </listitem>
        </varlistentry>

        <varlistentry>
          <term><filename>post-unlock</filename></term>
          <listitem>
            <para>钩子在一个路径被解锁后执行，被解锁的路径会传递到钩子的标准输入，钩子也会得到两个参数：到版本库的路径和删除锁定的用户。可以用钩子发送记录这些事件的邮件。因为删除已经发生，钩子的输出被忽略。
           </para>
          </listitem>
        </varlistentry>
      </variablelist>

      <warning>
        <para />

        <para>不要尝试用钩子脚本修改事务。一个常见的例子就是在提交时自动设置<literal>svn:eol-style</literal>或<literal>svn:mime-type</literal>这类属性。这看起来是个好主意，但它会引起问题。主要的问题是客户并不知道由钩子脚本进行的修改，同时没有办法通告客户它的数据是过时的，这种矛盾会导致出人意料和不能预测的行为。</para>

        <para>作为尝试修改事务的替代，我们通过检查<filename>pre-commit</filename>钩子的事务，在不满足要求时拒绝提交。</para>
      </warning>

      <para>Subversion会试图以当前访问版本库的用户身份执行钩子。通常，对版本库的访问总是通过Apache HTTP服务器和mod_dav_svn进行，因此，执行钩子的用户就是运行Apache的用户。钩子本身需要具有操作系统级的访问许可，用户可以运行它。另外，其它被钩子直接或间接使用的文件或程序（包括Subversion版本库本身）也要被同一个用户访问。换句话说，要注意潜在的访问控制问题，它可能会让你的钩子无法按照你的目的顺利执行。</para>
    </sect2>


    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.create.bdb">
      <title>Berkeley DB配置</title>

      <para>Berkeley DB环境是对一个或多个数据库、日志文件、区域文件和配置文件的封装。Berkeley DB环境对许多参数有自己的缺省值，例如任何时间里可用的数据库锁定数目、日志文件的最大值等。Subversion文件系统会使用Berkeley DB的默认值。 不过，有时候你的特定版本库与它独特的数据集合和访问类型，可能需要不同的配置选项。</para>

      <para>Sleepycat（Berkeley DB的制造厂商）的人们清楚不同的数据库有不同的需求，所以他们提供了在运行中覆盖Berkeley DB环境配置参数的机制。Berkeley在每一个环境目录中检查是否存在一个名叫<filename>DB_CONFIG</filename>的文件，然后解析其中的参数成为Berkeley环境所用的选项。</para>

      <para>你的版本库的Berkeley配置文件位于<filename>db</filename>目录的<filename>repos/db/DB_CONFIG</filename>， Subversion在创建版本库时自己创建了这个文件。这个文件初始时包含了一些默认选项，也包含了Berkeley DB在线文档，使你能够了解这些选项是做什么的。当然，你也可以为你的<filename>DB_CONFIG</filename> 文件添加任何Berkeley DB支持的选项。需要注意到，虽然Subversion不会尝试读取并解析这个文件，或使用其中的设置，你一定要避免会导致Berkeley DB按照Subversion代码不习惯的方式工作的修改。另外，<filename>DB_CONFIG</filename>的修改在复原数据库环境（用<command>svnadmin recover</command>）之前不会产生任何效果。</para>

    </sect2>
  </sect1>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.reposadmin.maint">
    <title>版本库维护</title>

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

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.maint.tk">
      <title>管理员的工具箱</title>

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

      <sect3 id="svn.reposadmin.maint.tk.svnlook">
        <title>svnlook</title>

        <para><command>svnlook</command>是Subversion提供的用来查看版本库中不同的修订版本和事务。这个程序不会修改版本库内容－这是个<quote>只读</quote>的工具。<command>svnlook</command>通常用在版本库钩子程序中，用来记录版本库即将提交（<command>用在pre-commit钩子时）</command>或者已经提交的（用在<command>post-commit</command>钩子时）修改。版本库管理员可以将这个工具用于诊断。</para>

        <para><command>svnlook</command> 的语法很直接：</para>

        <screen>
$ svnlook help
general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS &amp; 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 &lt;subcommand&gt;" for help on a specific subcommand.
…
</screen>

        <para>几乎<command>svnlook</command>的每一个子命令都能操作修订版本或事务树，显示树本身的信息，或是它与版本库中上一个修订版本的不同。你可以用<option>--revision</option> 和 <option>--transaction</option>选项指定要查看的修订版本或事务。注意，虽然修订版本号看起来像自然数，但是事务名称是包含英文字母与数字的字符串。请记住文件系统只允许浏览未提交的事务（还没有形成一个新的修订版本的事务）。多数版本库没有这种事务，因为事务通常或者被提交了（这样便不能被查看），或者被中止并删除了。</para>

        <para>如果没有<option>--revision</option>和<option>--transaction</option>选项，<command>svnlook</command>会查看版本库中最年轻的修订版本（或<quote>HEAD</quote>）。当版本库中的<filename>/path/to/repos</filename>的最年轻的修订版本是19时，下边的两个命令执行结果完全相同：</para>

        <screen>
$ svnlook info /path/to/repos
$ svnlook info /path/to/repos --revision 19
</screen>

        <para>这些子命令的唯一例外，是<command>svnlook youngest</command>命令，它不需要选项，只会显示出<literal>HEAD</literal>的修订版本号。</para>

        <screen>
$ svnlook youngest /path/to/repos
19
</screen>

        <para><command>svnlook</command>的输出被设计为人和机器都易理解，拿<literal>info</literal>子命令举例来说：</para>

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

        <para><literal>info</literal>子命令的输出定义如下：</para>

        <orderedlist>
          <listitem>
            <para>作者，后接换行。</para>
          </listitem>

          <listitem>
            <para>日期，后接换行。</para>
          </listitem>

          <listitem>
            <para>日志消息的字数，后接换行。</para>
          </listitem>

          <listitem>
            <para>日志信息本身， 后接换行。</para>
          </listitem>
        </orderedlist>

        <para>这种输出是人可阅读的，像是时间戳这种有意义的条目，使用文本表示，而不是其他比较晦涩的方式（例如许多无聊的人推荐的十亿分之一秒的数量）。这种输出也是机器可读的&mdash;因为日志信息可以有多行，没有长度的限制，<command>svnlook</command>在日志消息之前提供了消息的长度，这使得脚本或者其他对这个命令进行的封装提供了更强的功能，比如日志消息使用了多少内存，或在这个输出成为最后一个字节之前应该略过多少字节。</para>

        <para>另一个<command>svnlook</command>常见的用法是查看修订版本树或事务树的内容。<command>svnlook tree</command> 命令显示在请求的树中的目录和文件。如果你提供了<option>--show-ids</option>选项，它还会显示每个路径的文件系统节点修订版本ID（这一点对开发者往往更有用）。</para>

        <screen>
$ svnlook tree /path/to/repos --show-ids
/ &lt;0.0.1&gt;
 A/ &lt;2.0.1&gt;
  B/ &lt;4.0.1&gt;
   lambda &lt;5.0.1&gt;
   E/ &lt;6.0.1&gt;
    alpha &lt;7.0.1&gt;
    beta &lt;8.0.1&gt;
   F/ &lt;9.0.1&gt;
  mu &lt;3.0.1&gt;
  C/ &lt;a.0.1&gt;
  D/ &lt;b.0.1&gt;
   gamma &lt;c.0.1&gt;
   G/ &lt;d.0.1&gt;
    pi &lt;e.0.1&gt;
    rho &lt;f.0.1&gt;
    tau &lt;g.0.1&gt;
   H/ &lt;h.0.1&gt;
    chi &lt;i.0.1&gt;
    omega &lt;k.0.1&gt;
    psi &lt;j.0.1&gt;
 iota &lt;1.0.1&gt;
</screen>

        <para>如果你看过树中目录和文件的布局，你可以使用<command>svnlook cat</command>，<command>svnlook propget</command>， 和<command>svnlook  proplist</command>命令来查看这些目录和文件的细节。</para>

        <para><command>svnlook</command>还可以做很多别的查询，显示我们先前提到的信息的一些子集，报告指定的修订版本或事务中哪些路径曾经被修改过，显示对文件和目录做过的文本和属性的修改，等等。下面是<command>svnlook</command>命令能接受的子命令的介绍，以及这些子命令的输出：</para>

        <variablelist>
          <varlistentry>
            <term>
              <literal>author</literal>
            </term>

            <listitem>
              <para>显示该树的作者。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>cat</literal>
            </term>

            <listitem>
              <para>显示树中某文件的内容。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>changed</literal>
            </term>

            <listitem>
              <para>显示树中修改过的所有文件和目录。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>date</literal>
            </term>

            <listitem>
              <para>显示该树的时间戳。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>diff</literal>
            </term>

            <listitem>
              <para>使用统一区别格式显示被修改的文件。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>dirs-changed</literal>
            </term>

            <listitem>
              <para>显示树中本身被修改或者其中文件被修改的目录。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>history</literal>
            </term>

            <listitem>
              <para>显示受到版本控制的路径（更改和复制发生过的地方）中重要的历史点。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>info</literal>
            </term>

            <listitem>
              <para>显示树的作者、时间戳、日志大小和日志信息。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><literal>lock</literal></term>
            <listitem>
              <para>如果路径被锁定，则是对锁定属性的描述。</para>
            </listitem>
          </varlistentry>
          
          <varlistentry>
            <term>
              <literal>log</literal>
            </term>

            <listitem>
              <para>显示树的日志信息。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>propget</literal>
            </term>

            <listitem>
              <para>显示树中路径的属性值。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>proplist</literal>
            </term>

            <listitem>
              <para>显示树中属性集合的名字与值。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>tree</literal>
            </term>

            <listitem>
              <para>显示树列表，可选的显示与路径有关的文件系统节点的修订版本号。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>uuid</literal>
            </term>

            <listitem>
              <para>显示版本库的UUID&mdash;全局唯一标示。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>youngest</literal>
            </term>

            <listitem>
              <para>显示最年轻的修订版本号。</para>
            </listitem>
          </varlistentry>
        </variablelist>
      </sect3>

      <sect3 id="svn.reposadmin.maint.tk.svnadmin">
        <title>svnadmin</title>

        <para><command>svnadmin</command>程序是版本库管理员最好的朋友。除了提供创建Subversion版本库的功能，这个程序使你可以维护这些版本库。<command>svnadmin</command>的语法跟<command>svnlook</command>类似：</para>

        <screen>
$ svnadmin help
general usage: svnadmin SUBCOMMAND REPOS_PATH  [ARGS &amp; OPTIONS ...]
Type "svnadmin help &lt;subcommand&gt;" for help on a specific subcommand.

Available subcommands:
   create
   deltify
   dump
   help (?， h)
…
</screen>

        <para>我们已经提过<command>svnadmin</command>的<literal>create</literal>子命令（参照<xref linkend="svn.reposadmin.create"/>）。本章中我们会详细讲解大多数其他的命令。现在，我们来简单的看一下每个可用的子命令提供了什么功能。</para>

        <variablelist>
          <varlistentry>
            <term>
              <literal>create</literal>
            </term>

            <listitem>
              <para>创建一个新的Subversion版本库。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>deltify</literal>
            </term>

            <listitem>              <para>在指定的修订版本范围内，对其中修改过的路径做增量化操作。如果没有指定修订版本，这条命令会修改<literal>HEAD</literal>修订版本。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>dump</literal>
            </term>

            <listitem>
              <para>导出版本库修订一定版本范围内的内容，使用可移植转储格式。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>hotcopy</literal>
            </term>

            <listitem>              <para>对版本库做热拷贝，用这个方法你能任何时候安全的备份版本库而无需考虑是否正在使用。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>list-dblogs</literal>
            </term>

            <listitem>
              <para>（Berkeley DB版本库专有）列出Berkeley DB中与版本库有关的日志文件清单。这个清单包括所有的日志文件&mdash;仍然被版本库使用的和不再使用的。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>list-unused-dblogs</literal>
            </term>

            <listitem>
              <para>（Berkeley DB版本库专有)列出Berkeley DB版本库有关的不在使用日志文件路径清单。你能安全的从版本库中删除那些日志文件，也可以将它们存档以用来在灾难事件后版本库的恢复。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>load</literal>
            </term>

            <listitem>
              <para>导入由<literal>dump</literal>子命令导出的可移植转储格式的一组修订版本。</para>
            </listitem>
          </varlistentry>
          
          <varlistentry>
            <term><literal>lslocks</literal></term>
            <listitem>
              <para>List and describe any locks that exist in the
                repository.</para>
            </listitem>
          </varlistentry>
          
          <varlistentry>
            <term>
              <literal>lstxns</literal>
            </term>

            <listitem>
              <para>列出刚刚在版本库的没有提交的Subversion事务清单。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>recover</literal>
            </term>

            <listitem>
              <para>恢复版本库，通常在版本库发生了致命错误的时候，例如阻碍进程干净的关闭同版本库的连接的错误。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><literal>rmlocks</literal></term>
            <listitem>
              <para>无条件删除所列路径里的锁定。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>rmtxns</literal>
            </term>

            <listitem>
              <para>从版本库中清除Subversion事务（通过加工<literal>lstxns</literal>子命令的输出即可）。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>setlog</literal>
            </term>

            <listitem>
              <para>替换给定修订版本的<literal>svn:log</literal>（提交日志信息）属性值。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>verify</literal>
            </term>

            <listitem>
              <para>验证版本库的内容，包括校验比较本地版本化数据和版本库。</para>
            </listitem>
          </varlistentry>
        </variablelist>
      </sect3>

      <sect3 id="svn.reposadmin.maint.tk.svndumpfilter">
        <title>svndumpfilter</title>

        <para>因为Subversion使用底层的数据库储存各类数据，手工调整是不明智的，即使这样做并不困难。何况，一旦你的数据存进了版本库，通常很难再将它们从版本库中删除。<footnote>
            <para>顺便说一句，这是Subversion的<emphasis>特性</emphasis>，而不是bug。</para>
          </footnote>但是不可避免的，总会有些时候你需要处理版本库的历史数据。你也许想把一个不应该出现的文件从版本库中彻底清除。或者，你曾经用一个版本库管理多个工程，现在又想把它们分开。要完成这样的工作，管理员们需要更易于管理和扩展的方法表示版本库中的数据，Subversion版本库转储文件格式就是一个很好的选择。</para>

        <para>Subversion版本库转储文件记录了所有版本数据的变更信息，而且以易于阅读的格式保存。可以使用<command>svnadmin dump</command>命令生成转储文件，然后用<command>svnadmin load</command>命令生成一个新的版本库。（参见 <xref
        linkend="svn.reposadmin.maint.migrate"/>）。转储文件易于阅读意味着你可以小心翼翼的查看和修改它。当然，问题是如果你有一个运行了两年的版本库，那么生成的转储文件会很庞大，阅读和手工修改起来都会花费很多时间。</para>

        <para>虽然在管理员的日常工作中并不会经常使用，不过<command>svndumpfilter</command>可以对特定的路径进行过滤。这是一个独特而很有意义的用法，可以帮助你快速方便的修改转储的数据。使用时，只需提供一个你想要保留的（或者不想保留的）路径列表，然后把你的版本库转储文件送进这个过滤器。最后你就可以得到一个仅包含你想保留的路径的转储数据流。</para>

        <para><command>svndumpfilter</command>的语法如下：</para>

        <screen>
$ svndumpfilter help
general usage: svndumpfilter SUBCOMMAND [ARGS &amp; OPTIONS ...]
Type "svndumpfilter help &lt;subcommand&gt;" for help on a specific subcommand.

Available subcommands:
   exclude
   include
   help (?， h)
</screen>

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

        <variablelist>
          <varlistentry>
            <term>
              <literal>exclude</literal>
            </term>

            <listitem>
              <para>将指定路径的数据从转储数据流中排除。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <literal>include</literal>
            </term>

            <listitem>
              <para>将指定路径的数据添加到转储数据流中。</para>
            </listitem>
          </varlistentry>
        </variablelist>

        <para>现在我来演示如何使用这个命令。我们会在其它章节（参见 <xref
        linkend="svn.reposadmin.projects.chooselayout"/>）讨论关于如何选择设定版本库布局的问题，比如应该使用一个版本库管理多个项目还是使用一个版本库管理一个项目，或者如何在版本库中安排数据等等。不过，有些时候，即使在项目已经展开以后，你还是希望对版本库的布局做一些调整。最常见的情况是，把原来存放在同一个版本库中的几个项目分开，各自成家。</para>

        <para>假设有一个包含三个项目的版本库：
        <literal>calc</literal>，<literal>calendar</literal>，和
        <literal>spreadsheet</literal>。它们在版本库中的布局如下：</para>

        <screen>
/
   calc/
      trunk/
      branches/
      tags/
   calendar/
      trunk/
      branches/
      tags/
   spreadsheet/
      trunk/
      branches/
      tags/
</screen>

        <para>现在要把这三个项目转移到三个独立的版本库中。首先，转储整个版本库：</para>

        <screen>
$ svnadmin dump /path/to/repos &gt; repos-dumpfile
* Dumped revision 0.
* Dumped revision 1.
* Dumped revision 2.
* Dumped revision 3.
…
$
</screen>

        <para>然后，将转储文件三次送入过滤器，每次仅保留一个顶级目录，就可以得到三个转储文件：</para>

        <screen>
$ svndumpfilter include calc &lt; repos-dumpfile &gt; calc-dumpfile
…
$ svndumpfilter include calendar &lt; repos-dumpfile &gt; cal-dumpfile
…
$ svndumpfilter include spreadsheet &lt; repos-dumpfile &gt; ss-dumpfile
…
$
</screen>

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

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

</screen>

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

        <para>剩下的工作就是创建三个新的版本库，然后将三个转储文件分别导入：</para>

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

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

        <variablelist>
          <varlistentry>
            <term>
              <option>--drop-empty-revs</option>
            </term>

            <listitem>
              <para>不生成任何空修订版本，忽略它们。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <option>--renumber-revs</option>
            </term>

            <listitem>
              <para>如果空修订版本被剔除（通过使用<option>--drop-empty-revs</option>选项），依次修改其它修订版本的编号，确保编号序列是连续的。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>
              <option>--preserve-revprops</option>
            </term>

            <listitem>
              <para>如果空修订版本被保留，保持这些空修订版本的属性（日志信息，作者，日期，自定义属性，等等）。如果不设定这个选项，空修订版本将仅保留初始时间戳，以及一个自动生成的日志信息，表明此修订版本由<command>svndumpfilter</command>处理过。</para>
            </listitem>
          </varlistentry>
        </variablelist>

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

        <screen>
…
Node-path: spreadsheet/Makefile
…
</screen>

        <para>如果这些路径以斜线开头，那么你传递给<command>svndumpfilter
        include</command>和<command>svndumpfilter exclude</command>的路径也必须以斜线开头（反之亦然）。如果因为某些原因转储文件中的路径没有统一使用或不使用斜线开头，<footnote>
            <para>尽管<command>svnadmin dump</command>对是否以斜线作为路径的开头有统一的规定——这个规定就是不以斜线作为路径的开头——其它生成转储文件的程序不一定会遵守这个规定。</para>
          </footnote>也许需要修正这些路径，统一使用斜线开头或不使用斜线开头。</para>

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

      <sect3 id="svn.reposadmin.maint.tk.bdbutil">
        
        <title>Berkeley DB工具</title>

        <para>如果你使用Berkeley DB版本库，那么所有纳入版本控制的文件系统结构和数据都储存在一系列数据库的表中，而这个位于版本库的<filename>db</filename>子目录下。这个子目录是一个标准的Berkeley DB环境目录，可以应用任何Berkeley数据库工具进行操作（参考SleepyCat网站<ulink url="http://www.sleepycat.com/"/>上关于这些工具的介绍）。</para>

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

        <para>当然，还有一些Berkeley DB工具有时是有用的。<command>db_dump</command>将Berkeley DB数据库中的键值对以特定的格式写入文件中，而<command>db_load</command>则可以将这些键值对注入到数据库中。Berkeley数据库本身不支持跨平台转移，这两个工具在这样的情况下就可以实现在平台间转移数据库的功能，而无需关心操作系统或机器架构。此外，<command>db_stat</command>工具能够提供关于Berkeley DB环境的许多有用信息，包括详细的锁定和存储子系统的统计信息。</para>
      </sect3>
    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.maint.cleanup">
      <title>版本库清理</title>

      <para>Subversion版本库一旦按照需要配置完成，一般情况下不需要特别的关照。不过有些时候还是需要管理员手工干预一下。<command>svnadmin</command>工具就能够帮你完成以下这类工作：</para>

      <itemizedlist>
        <listitem>
          <para>修改提交日志信息，</para>
        </listitem>

        <listitem>
          <para>移除中止的事务，</para>
        </listitem>

        <listitem>
          <para>恢复<quote>塞住</quote>的版本库，以及</para>
        </listitem>

        <listitem>
          <para>将一个版本库中的内容搬移到另一个版本库中。</para>
        </listitem>
      </itemizedlist>

      <para><command>svnadmin</command>的子命令中最经常用到的恐怕就是<literal>setlog</literal>。用户在提交时输入的日志信息随着相关事务提交到版本库并升级成为修订版本后，便作为新修订版本的非版本化（即没有进行版本管理）属性保存下来。换句话说，版本库只记得最新的属性值，而忽略以前的。</para>

      <para>有时用户输入的日志信息有错误（比如拼写错误或者内容错误）。如果配置版本库时设置了（使用<literal>pre-revprop-change</literal>和      <literal>post-revprop-change</literal>钩子；参见<xref
      linkend="svn.reposadmin.create.hooks"/>）允许用户在提交后修改日志信息的选项，那么用户可以使用<command>svn</command>程序的<literal>propset</literal>命令（参见<xref linkend="svn.ref"/>）<quote>修正</quote>日志信息中的错误。不过为了避免永远丢失信息，Subversion版本库通常设置为仅能由管理员修改非版本化属性（这也是默认的选项）。</para>

      <para>如果管理员想要修改日志信息，那么可以使用<command>svnadmin setlog</command>命令。这个命令从指定的文件中读取信息，取代版本库中某个修订版本的日志信息（<literal>svn:log</literal>属性）。</para>

      <screen>
$ echo "Here is the new， correct log message" &gt; newlog.txt
$ svnadmin setlog myrepos newlog.txt -r 388
</screen>

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

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

      <para><command>svnadmin</command>的另一个常见用途是查询异常的&mdash;可能是已经死亡的&mdash;Subversion事务。通常提交操作失败时，与之相关的事务就会被清除。也就是说，事务本身及所有与该事务相关（且仅与该事务相关）的数据会从版本库中删除。不过偶尔也会出现操作失败而事务没有被清除的情况。出现这种情况可能有以下原因：客户端的用户粗暴的结束了操作，操作过程中出现网络故障，等等。不管是什么原因，死亡的事务总是有可能会出现。这类事务不会产生什么负面影响，仅仅是消耗了一点点磁盘空间。不过，严厉的管理员总是希望能够将它们清除出去。</para>

      <para>可以使用<command>svnadmin</command>的<literal>lstxns</literal> 命令列出当前的异常事务名。</para>

      <screen>
$ svnadmin lstxns myrepos
19
3a1
a45
$
</screen>

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

      <screen>
$ svnadmin rmtxns myrepos `svnadmin lstxns myrepos`
$
</screen>

      <para>在按照上面例子中的方法清理版本库之前，你或许应该暂时关闭版本库和客户端的连接。这样在你开始清理之前，不会有正常的事务进入版本库。下面例子中的shell脚本可以用来迅速获得版本库中异常事务的信息：</para>

      <example id="svn.reposadmin.maint.cleanup.ex-1">
        <title>txn-info.sh（异常事务报告）</title>

        <programlisting>
#!/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}" --transaction "${TXN}"
done
</programlisting>
      </example>

      <para>可以用下面的命令使用上例中脚本：
      <command>/path/to/txn-info.sh /path/to/repos</command>。该命令的输出主要由多个<command>svnlook info</command>参见<xref linkend="svn.reposadmin.maint.tk.svnlook"/>）的输出组成，类似于下面的例子：</para>

      <screen>
$ 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
$
</screen>

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

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

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.maint.diskspace">
      <title>管理磁盘空间</title>

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

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

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

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

      <screen>
$ svnadmin list-unused-dblogs /path/to/repos
/path/to/repos/log.0000000031
/path/to/repos/log.0000000032
/path/to/repos/log.0000000033

$ svnadmin list-unused-dblogs /path/to/repos | xargs rm
## disk space reclaimed!
</screen>

      <para>为了尽可能减小版本库的体积，Subversion在版本库中采用了<firstterm>增量化技术</firstterm>（或称为<quote>增量存储技术</quote>）。增量化技术可以将一组数据表示为相对于另一组数据的不同。如果这两组数据十分相似，增量化技术就可以仅保存其中一组数据以及两组数据的差别，而不需要同时保存两组数据，从而节省了磁盘空间。每次一个文件的新版本提交到版本库，版本库就会将之前的版本（之前的多个版本）相对于新版本做增量化处理。采用了这项技术，版本库的数据量大小基本上是可以估算出来的&mdash;主要是版本化的文件的大小&mdash;并且远小于<quote>全文</quote>保存所需的数据量。</para>

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

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.maint.recovery">
      <title>版本库的恢复</title>

      <para><xref linkend="svn.reposadmin.basics.backends.bdb"/>中曾提到，Berkeley DB版本库如果没有正常关闭可能会进入冻结状态。这时，就需要管理员将数据库恢复到正常状态。</para>

      <para>Berkeley DB使用一种锁机制保护版本库中的数据。锁机制确保数据库不会同时被多个访问进程修改，也就保证了从数据库中读取到的数据始终是稳定而且正确的。当一个进程需要修改数据库中的数据时，首先必须检查目标数据是否已经上锁。如果目标数据没有上锁，进程就将它锁上，然后作出修改，最后再将锁解除。而其它进程则必须等待锁解除后才能继续访问数据库中的相关内容。</para>

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

      <para>首先，如果你的版本库出现这种情况，没什么好惊慌的。Berkeley DB的文件系统采用了数据库事务、检查点以及预写入日志等技术来取保只有灾难性的事件<footnote>
          <para>比如：硬盘 + 大号电磁铁 = 毁灭。</para>
        </footnote>才能永久性的破坏数据库环境。所以虽然一个过于稳重的版本库管理员通常都会按照某种方案进行大量的版本库离线备份，不过不要急着通知你的管理员进行恢复。</para>

      <para>然后，使用下面的方法试着<quote>恢复</quote>你的版本库：</para>

      <orderedlist>
        <listitem>
          <para>确保没有其它进程访问（或者试图访问）版本库。对于网络版本库，关闭Apache HTTP服务器是个好办法。</para>
        </listitem>

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

        <listitem>
          <para>运行命令<command>svnadmin recover /path/to/repos</command>。 输出如下：</para>

          <screen>
Repository lock acquired。
Please wait; recovering the repository may take some time...

Recovery completed.
The latest repos revision is 19.
</screen>

          <para>此命令可能需要数分钟才能完成。</para>
        </listitem>

        <listitem>
          <para>重新启动Subversion服务器。</para>
        </listitem>
      </orderedlist>

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

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

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.maint.migrate">
      <title>版本库的移植</title>

      <para>Subversion文件系统将数据保存在许多数据库表中，而这些表的结构只有Subversion开发者们才了解（也只有他们才感兴趣）不过，有些时候我们会想到把所有的数据（或者一部分数据）保存在一个独立的、可移植的、普通格式的文件中。Subversion通过<command>svnadmin</command>的两个子命令<literal>dump</literal>和<literal>load</literal>提供了类似的功能。</para>

      <para>对版本库的转储和装载的需求主要还是由于Subversion自身处于变化之中。在Subversion的成长期，后端数据库的设计多次发生变化，这些变化导致之前的版本库出现兼容性问题。当然，将Berkeley DB版本库移植到不同的操作系统或者CPU架构上，或者在Berkeley DB和FSFS后端之间进行转化也需要转储和装载功能。按照下面的介绍，只需简单几步就可以完成数据库的移植：</para>

      <orderedlist>
        <listitem>
          <para>使用<emphasis>当前</emphasis>版本的<command>svnadmin</command>将版本库转储到文件中。</para>
        </listitem>

        <listitem>
          <para>升级Subversion。</para>
        </listitem>

        <listitem>
          <para>移除以前的版本库，并使用<emphasis>新版本</emphasis>的<command>svnadmin</command>在原来版本库的位置建立空的版本库。</para>
        </listitem>

        <listitem>
          <para>还是使用<emphasis>新版本</emphasis>的<command>svnadmin</command>从转储文件中将数据装载到新建的空版本库中。</para>
        </listitem>

        <listitem>
          <para>记住从以前的版本库中复制所有的定制文件到新版本库中，包括<filename>DB_CONFIG</filename>文件和钩子脚本。最好阅读一下新版本的release notes，看看此次升级是否会影响钩子和配置选项。</para>
        </listitem>

        <listitem>
          <para>如果移植的同时改变的版本库的访问地址（比如移植到另一台计算机或者改变了访问策略），那么可以通知用户运行<command>svn switch --relocate</command>来切换他们的工作副本。参见<xref
          linkend="svn.ref.svn.c.switch"/>。</para>
        </listitem>
      </orderedlist>

      <para><command>svnadmin dump</command>命令会将版本库中的修订版本数据按照特定的格式输出到转储流中。转储数据会输出到标准输出流，而提示信息会输出到标准错误流。这就是说，可以将转储数据存储到文件中，而同时在终端窗口中监视运行状态。例如：</para>

      <screen>
$ svnlook youngest myrepos
26
$ svnadmin dump myrepos &gt; dumpfile
* Dumped revision 0.
* Dumped revision 1.
* Dumped revision 2.
…
* Dumped revision 25.
* Dumped revision 26.
</screen>

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

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

      <screen>
$ svnadmin load newrepos &lt; dumpfile
&lt;&lt;&lt; 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) &gt;&gt;&gt;

&lt;&lt;&lt; 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) &gt;&gt;&gt;

…

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

------- Committed new rev 25 (loaded from original rev 25) &gt;&gt;&gt;

&lt;&lt;&lt; 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) &gt;&gt;&gt;

</screen>

      <para>load命令的结果就是添加一些新的修订版本&mdash;与使用普通Subversion客户端直接提交到版本库相同。正像一次简单的提交，你也可以使用钩子脚本在每次load的开始和结束执行一些操作。通过传递<option>--use-pre-commit-hook</option>和<option>--use-post-commit-hook</option>选项给<command>svnadmin load</command>，你可以告诉Subversion的对每一个加载修订版本执行pre-commit和post-commit钩子脚本，可以利用这个选项确保这种提交也能通过一般提交的检验。当然，你要小心使用这个选项，你一定不像接受一大堆提交邮件。你可以查看<xref linkend="svn.reposadmin.create.hooks"/>来得到更多相关信息。
     </para>

      <para>既然<command>svnadmin</command>使用标准输入流和标准输出流作为转储和装载的输入和输出，那么更漂亮的用法是（管道两端可以是不同版本的<command>svnadmin</command>：</para>

      <screen>
$ svnadmin create newrepos
$ svnadmin dump myrepos | svnadmin load newrepos
</screen>

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

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

      <screen>
$ svnadmin dump myrepos --revision 23 &gt; rev-23.dumpfile
$ svnadmin dump myrepos --revision 100:200 &gt; revs-100-200.dumpfile
</screen>

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

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

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

      <screen>
$ svnadmin dump myrepos --revision 0:1000 &gt; dumpfile1
$ svnadmin dump myrepos --revision 1001:2000 --incremental &gt; dumpfile2
$ svnadmin dump myrepos --revision 2001:3000 --incremental &gt; dumpfile3
</screen>

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

      <screen>
$ svnadmin load newrepos &lt; dumpfile1
$ svnadmin load newrepos &lt; dumpfile2
$ svnadmin load newrepos &lt; dumpfile3
</screen>

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

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

      <screen>
$ svnadmin create /path/to/projects
$
</screen>

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

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

      <para>最后，将转储文件分别装载到各自的目录中：</para>

      <screen>
$ svnadmin load /path/to/projects --parent-dir calc &lt; calc-dumpfile
…
$ svnadmin load /path/to/projects --parent-dir calendar &lt; cal-dumpfile
…
$ svnadmin load /path/to/projects --parent-dir spreadsheet &lt; ss-dumpfile
…
$
</screen>

      <para>我们再介绍一下Subversion版本库转储数据的最后一种用途——在不同的存储机制或版本控制系统之间转换。因为转储数据的格式的大部分是可以阅读的，<footnote>
          <para>Subversion版本库的转储文件格式类似于RFC-822格式，后者广泛的应用于电子邮件系统中。</para>
        </footnote>所以使用这种格式描述变更集（每个变更集对应一个新的修订版本）会相对容易一些。事实上，<command>cvs2svn</command>工具（参见
      <xref linkend="svn.forcvs.convert"/>）正是将CVS版本库的内容转换为转储数据格式，如此才能将CVS版本库的数据导入Subversion版本库之中。</para>
    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.maint.backup">
      <title>版本库备份</title>

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

      <para>Subversion版本库管理员通常有两种备份方式&mdash;增量的和完全的。我们在早先的章节曾经讨论过如何使用<command>svnadmin
      dump --incremental</command>命令执行增量备份（见<xref
      linkend="svn.reposadmin.maint.migrate"/>），从本质上讲，这个方法只是备份了从你上次备份版本库到现在的变化。</para>

      <para>一个完全的版本库备份照字面上讲就是对整个版本库目录的复制（包括伯克利数据库或者文件FSFS环境），现在，除非你临时关闭了其他对版本库的访问，否则仅仅做一次迭代的拷贝会有产生错误备份的风险，因为有人可能会在并行的写数据库。</para>

      <para>如果是伯克利数据库，恼人的文档描述了保证安全拷贝的步骤，对于FSFS的数据，也有类似的顺序。我们有更好的选择，我们不需要自己去实现这个算法，因为Subversion开发小组已经为你实现了这些算法。Subversion源文件分发版本的<filename>tools/backup/</filename>目录有一个<command>hot-backup.py</command>文件。只要给定了版本库路径和备份路径，<command>hot-backup.py</command>&mdash;一个包裹了<command>svnadmin hotcopy</command>但更加智能的命令&mdash;将会执行必要的步骤来备份你的活动的版本库&mdash;不需要你首先禁止公共的版本库访问&mdash;而且之后会从你的版本库清理死掉的伯克利日志文件。</para>

      <para>甚至当你用了一个增量备份时，你也会希望有计划的运行这个程序。举个例子，你考虑在你的调度程序（如Unix下的<command>cron</command>）里加入<command>hot-backup.py</command>，或者你喜欢更加细致的备份解决方案，你可以让你的post-commit的钩子脚本执行<command>hot-backup.py</command>（见see
      <xref
      linkend="svn.reposadmin.create.hooks" />），这样会导致你的版本库的每次提交执行一次备份，只要在你的<filename>hooks/post-commit</filename>脚本里添加如下代码：</para>

      <programlisting>
(cd /path/to/hook/scripts; ./hot-backup.py ${REPOS} /path/to/backups &amp;)
</programlisting>

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

      <para>两种备份方式都有各自的优点，最简单的方式是完全备份，将会每次建立版本库的完美复制品，这意味着如果当你的活动版本库发生了什么事情，你可以用备份恢复。但不幸的是，如果你维护多个备份，每个完全的备份会吞噬掉和你的活动版本库同样的空间。</para>

      <para>增量备份会使用的版本库转储格式，在Subversion的数据库模式改变时非常完美，因此当我们升级Subversion数据库模式的时候，一个完整的版本库导出和导入是必须的，做一半工作非常的容易（导出部分），不幸的是，增量备份的创建和恢复会占用很长时间，因为每一次提交都会被重放。</para>

      <para>在每一种备份情境下，版本库管理员需要意识到对未版本化的修订版本属性的修改对备份的影响，因为这些修改本身不会产生新的修订版本，所以不会触发post-commit的钩子程序，也不会触发pre-revprop-change和post-revprop-change的钩子。
      <footnote>
          <para><command>svnadmin setlog</command>可以被绕过钩子程序被调用。</para>
        </footnote>
      而且因为你可以改变修订版本的属性，而不需要遵照时间顺序&mdash;你可在任何时刻修改任何修订版本的属性&mdash;因此最新版本的增量备份不会捕捉到以前特定修订版本的属性修改。</para>

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

      <para>通常情况下，最好的版本库备份方式是混合的，你可以平衡完全和增量备份，另外配合提交邮件的归档，Subversion开发者，举个例子，在每个新的修订版本建立时备份Subversion的源代码版本库，并且保留所有的提交和属性修改通知文件。你的解决方案类似，必须迎合你的需要，平衡便利和你的偏执。然而这些不会改变你的硬件来自钢铁的命运。<footnote>
          <para>你知道的&mdash;只是对各种变化莫测的问题的统称。</para>
        </footnote> 这一定会帮助你减少尝试的时间。</para>
    </sect2>
  </sect1>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.reposadmin.projects">
    <title>添加项目</title>

    <para>一旦你的版本库已经建立并且配置好了，剩下的就是使用了。如果你已经准备好了需要版本控制的数据，那么可以使用客户端软件<command>svn</command>的<literal>import</literal>子命令来实现你的期望。不过在这样做之前，你最好对版本库仔细的作一个长远的规划。本节，我们会给你一些好的建议，这些建议可以帮助你设计版本库的文件布局，以及如何在特定的布局中安排你的数据。</para>

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.projects.chooselayout">
      <title>选择一种版本库布局</title>

      <para>在Subversion版本库中，移动版本化的文件和目录不会损失任何信息，但是这样一来那些经常访问版本库并且以为文件总是在同一个路径的用户可能会受到干扰。为将来着想，最好预先对你的版本库布局进行规划。以一种高效的<quote>布局</quote>开始项目，可以减少将来很多不必要的麻烦。</para>

      <para>在建立Subversion版本库之前，有很多事情需要考虑。假如你是一个版本库管理员，需要向多个项目提供版本控制支持。那么，你首先要决定的是，用一个版本库支持多个项目，还是为每个项目建立一个版本库，还是为其中的某些项目提供独立的版本库支持，而将另外一些项目分布在几个版本库中。</para>

      <para>使用一个版本库支持多个项目有很多好处，最明显的无过于不需要维护好几个版本库。单一版本库就意味着只有一个钩子集，只需要备份一个数据库，当Subversion进行不兼容升级时，只需要一次转储和装载操作，等等。还有，你可以轻易的在项目之间移动数据，还不会损失任何历史版本信息。</para>

      <para>单一版本库的缺点是，不同的项目通常都有不同的提交邮件列表或者不同的权限认证和权限要求。还有，别忘了Subversion的修订版本号是针对整个版本库的。即使最近没有对某个项目作出修改，版本库的修订版本号还是会因为其它项目的修改而不停的提升，许多人并不喜欢这样的事实。</para>

      <para>可以采用折中的办法。比如，可以把许多项目按照彼此之间的关联程度划分为几个组合，然后为每一个项目组合建立一个版本库。这样，在相关项目之间共享数据依旧很简单，而如果修订版本号有了变化，至少开发人员知道，改变的东西多少和他们有些关系。</para>

      <para>在决定了如何用版本库组织项目以后，就该决定如何设置版本库的目录层次了。由于Subversion按普通的目录复制方式完成分支和标签操作（参见<xref
      linkend="svn.branchmerge"/>），Subversion社区建议为每一个项目建立一个<firstterm>项目根目录</firstterm>&mdash;项目的<quote>顶级</quote>目录&mdash;然后在根目录下建立三个子目录：<filename>trunk</filename>，保存项目的开发主线；<filename>branches</filename>，保存项目的各种开发分支；<filename>tags</filename>，保存项目的标签，也就是创建后永远不会修改的分支（可能会删除）。
        <footnote>
          <para>
          <filename>trunk</filename>、<filename>tags</filename>和<filename>branches</filename>可以使用<quote>TTB目录</quote>来表示。</para>
        </footnote>
     </para>

      <para>举个例子，一个版本库可能会有如下的布局：</para>

      <screen>
/
   calc/
      trunk/
      tags/
      branches/
   calendar/
      trunk/
      tags/
      branches/
   spreadsheet/
      trunk/
      tags/
      branches/
   …
</screen>

      <para>项目在版本库中的根目录地址并不重要。如果每个版本库中只有一个项目，那么就可以认为项目的根目录就是版本库的根目录。如果版本库中包含多个项目，那么可以将这些项目划分成不同的组合（按照项目的目标或者是否需要共享代码甚至是字母顺序）保存在不同子目录中，下面的例子给出了一个类似的布局：</para>

      <screen>
/
   utils/
      calc/
         trunk/
         tags/
         branches/
      calendar/
         trunk/
         tags/
         branches/
      …
   office/
      spreadsheet/
         trunk/
         tags/
         branches/
      …
</screen>

      <para>按照你因为合适方式安排版本库的布局。Subversion自身并不强制或者偏好某一种布局形式，对于Subversion来说，目录就是目录。最后，在设计版本库布局的时候，不要忘了考虑一下项目参与者们的意见。</para>
    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.reposadmin.projects.import">
      <title>创建布局，导入初始数据</title>

      <para>设计好版本库的布局后，就该在版本库中实现布局和导入初始数据了。在Subversion中，有很多种方法完成这项工作。可以使用<command>svn mkdir</command>命令（参见<xref linkend="svn.ref"/>）在版本库中逐个创建需要的目录。更快捷的方法是使用<command>svn import</command>命令（参见<xref linkend="svn.tour.other.import"/>）。首先，在硬盘上创建一个临时目录，并按照设计好的布局在其中创建子目录，然后通过导入命令一次性的提交整个布局到版本库中：</para>

      <screen>
$ mkdir tmpdir
$ cd tmpdir
$ mkdir projectA
$ mkdir projectA/trunk
$ mkdir projectA/branches
$ mkdir projectA/tags
$ mkdir projectB
$ mkdir projectB/trunk
$ mkdir projectB/branches
$ mkdir projectB/tags
…
$ svn import . file:///path/to/repos --message 'Initial repository layout'
Adding         projectA
Adding         projectA/trunk
Adding         projectA/branches
Adding         projectA/tags
Adding         projectB
Adding         projectB/trunk
Adding         projectB/branches
Adding         projectB/tags
…
Committed revision 1.
$ cd ..
$ rm -rf tmpdir
$
</screen>

      <para>然后可以使用<command>svn list</command>命令确认导入的结果是否正确：：</para>

      <screen>
$ svn list --verbose file:///path/to/repos
      1 harry               May 08 21:48 projectA/
      1 harry               May 08 21:48 projectB/
…
$
</screen>

      <para>创建了版本库布局以后，如果有项目的初始数据，那么可以将这些数据导入到版本库中。同样有很多种方法完成这项工作。首先，可以使用<command>svn import</command>命令。也可以先从版本库中取出工作副本，将已有的项目数据复制到工作副本中，再使用<command>svn add</command>和<command>svn commit</command>命令提交修改。不过这些工作就不属于版本库管理方面的内容了。如果对<command>svn</command> 客户端程序还不熟悉，请阅读<xref linkend="svn.tour"/>。</para>
    </sect2>
  </sect1>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.reposadmin.summary">
  
    <title>摘要</title>

    <para>现在，你应该已经对如何创建、配置以及维护Subversion版本库有了个基本的认识。我们向您介绍了几个可以帮助您工作的工具。通过这一章，我们说明了一些常见的管理误区，并提出了避免陷入误区的建议。</para>

    <para>剩下的，就是由你决定在你的版本库中存放一些什么有趣的资料，并最终通过网络获得这些资料。下一章是关于网络的内容。</para>
  </sect1>
</chapter>
<!--
local variables: 
sgml-parent-document: ("book.xml" "chapter")
end:
-->