<chapter id="svn.advanced">
  <title>高级主题</title>

  <simplesect>

    <para>如果你是从头到尾按章节阅读本书，你一定已经具备了使用Subversion客户端执行大多数不同的版本控制操作足够的知识，你理解了怎样从Subversion版本库取出一个工作拷贝，你已经熟悉了通过<command>svn commit</command>和<command>svn update</command>来提交和接收修改，你甚至也经常下意识的使用<command>svn status</command>，无论目的是什么，你已经可以正常使用Subversion了。</para>

    <para>但是Subversion的特性集可不只是<quote>一般的版本控制操作</quote>。</para>

    <para>本章重点介绍一些Subversion不常用的特性，在这里，我们会讨论Subversion的属性（或者说<quote>元数据</quote>）支持，和如何通过更改运行配置区来改变Subversion的缺省行为方式，我们会描述怎样使用外部定义来指导Subversion从多个版本库得到数据，我们会覆盖一些Subversion分发版本附加的客户端和服务器端的工具的细节。</para>

    <para>在阅读本章之前，你一定要熟悉Subversion对文件和目录的基本版本操作能力，如果你还没有阅读这些内容，或者是需要一个复习，我们建议你重读<xref linkend="svn.basic" />和<xref linkend="svn.tour" />，一旦你已经掌握了基础知识和本章的内容，你会变成Subversion的超级用户！
    </para>

  </simplesect>

  <sect1 id="svn.advanced.confarea">
    <title>运行配置区</title>
    
    <para>Subversion提供了许多用户可以控制的可选行为方式，许多是用户希望添加到所有的Subversion操作中的选项，为了避免强制用户记住命令行参数并且在每个命令中使用，Subversion使用配置文件，并且将配置文件保存在独立的Subversion配置区。</para>

    <para>Subversion<firstterm>配置区</firstterm>是一个双层结构，保存了可选项的名称和值。通常，Subversion配置区是一个保存<firstterm>配置文件</firstterm>的特殊目录（第一层结构），目录中保存了一些标准INI格式的文本文件（文件中的<quote>section</quote>形成第二层结构）。这些文件可以简单用你喜欢的文本编辑器编辑（如Emacs或vi），而且保存了客户端可以读取的指示，用来指导用户的一些行为选项。</para>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.confarea.layout">
      <title>配置区布局</title>

      <para><command>svn</command>命令行客户端第一次执行时，会创建一个用户配置区，在类Unix系统中，配置区位于用户主目录中，名为<filename>.subversion</filename>。在Win32系统，Subversion创建一个名为<filename>Subversion</filename>的目录，这个目录通常位于用户配置目录（顺便说一句，通常是一个隐藏目录）的<filename>Application Data</filename>子目录下。然而，在Win32平台上，此目录的具体位置在不同的系统上是不一样的，由Windows注册表决定。
        <footnote>
          <para><literal>APPDATA</literal>环境变量指向<filename>Application Data</filename>目录，所以你可以通过<filename>%APPDATA%\Subversion</filename>引用用户配置区目录。
          </para>
        </footnote>
        我们以Unix下的名字<filename>.subversion</filename>来表示用户配置区。
      </para>

      <para>除了用户配置区，Subversion也提供了系统配置区，通过系统配置区，系统管理员可以为某个机器的所有用户建立缺省配置值。注意系统配置区不会规定强制性的策略&mdash;每个用户配置区都可以覆盖系统配置区中的配置项，而<command>svn</command>的命令行参数决定了最后的行为。在类Unix的平台上，系统配置区位于<filename>/etc/subversion</filename>目录下，在Windows平台上，系统配置区位于<filename>Application Data</filename>（再说一次，是由Windows注册表决定的）的<filename>Subversion</filename>目录中。与用户配置区不同，<command>svn</command>不会试图创建系统配置区。</para>

      <para>目前，Subversion的配置区包含三个文件&mdash;两个配置文件（<filename>config</filename>和<filename>servers</filename>），和一个INI文件格式的<filename>README.txt</filename>描述文件。配置文件创建的时候，Subversion的选项都设置为默认值。配置文件中的选项都按功能划分成组，大多数选项还有详细的文字描述注释，说明这些选项的值对Subversion的主要影响。要修改选项，只需用文本编辑器打开并编辑配置文件。如果想要恢复缺省的配置，可以直接删除（或者重命名）配置目录，并且运行一些如<command>svn --version</command>之类的无关紧要的<command>svn</command>命令，一个包含缺省值的新配置目录就会创建起来。</para>

      <para>用户配置区也缓存了认证信息，<filename>auth</filename>目录下的子目录中缓存了一些Subversion支持的各种认证方法的信息，这个目录需要相应的用户权限才可以访问。</para>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.confarea.windows-registry">
      <title>配置和Windows注册表</title>

      <para>除了基于INI文件的配置区，运行在Windows平台的Subversion客户端也可以使用Windows注册表来保存配置数据。注册表中保存的选项名称和值的含义与INI文件中相同，<quote>file/section</quote>在注册表中表现为注册表键树的层级，使得双层结构得以保留下来。</para>

      <para>Subversion的系统配置值保存在键<literal>HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion</literal>下。举个例子，<literal>global-ignores</literal>选项位于<filename>config</filename>文件的<literal>miscellany</literal>小节，在Windows注册表中，则位于<literal>HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Config\Miscellany\global-ignores</literal>。用户配置值存放在<literal>HKEY_CURRENT_USER\Software\Tigris.org\Subversion</literal>下。</para>

      <para>基于注册表的配置项在基于文件的配置项<emphasis>之前</emphasis>解析，所以其配置项的值会被配置文件中相同配置项的值覆盖，换句话说，在Windows系统下配置项的优先级是：</para>
      
      <orderedlist>
        <listitem>
          <para>命令行选项</para>
        </listitem>
        <listitem>
          <para>用户INI配置文件</para>
        </listitem>
        <listitem>
          <para>用户注册表值</para>
        </listitem>
        <listitem>
          <para>系统INI配置文件</para>
        </listitem>
        <listitem>
          <para>系统注册表值</para>
        </listitem>
      </orderedlist>

      <para>此外，虽然Windows注册表不支持<quote>注释掉</quote>这种概念，但是Subversion会忽略所有以井号（<literal>#</literal>）开始的字符，这允许你快速的取消一个选项而不需要删除整个注册表键，明显简化了恢复选项的过程。</para>

      <para><command>svn</command>命令行客户端不会尝试写Windows注册表，也不会在注册表中创建默认配置区。不过可以使用<command>REGEDIT</command>创建所需的键。此外，还可以创建一个<filename>.reg</filename>文件，并在文件浏览器中双击这个文件，文件中的数据就会合并到注册表中。</para>

      <example id="svn.advanced.confarea.windows-registry.ex-1">
        <title>注册表条目（.reg）样本文件。</title>

        <programlisting>
REGEDIT4

[HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Servers\groups]

[HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Servers\global]
"#http-proxy-host"=""
"#http-proxy-port"=""
"#http-proxy-username"=""
"#http-proxy-password"=""
"#http-proxy-exceptions"=""
"#http-timeout"="0"
"#http-compression"="yes"
"#neon-debug-mask"=""
"#ssl-authority-files"=""
"#ssl-trust-default-ca"=""
"#ssl-client-cert-file"=""
"#ssl-client-cert-password"=""

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\auth]
"#store-auth-creds"="no"

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\helpers]
"#editor-cmd"="notepad"
"#diff-cmd"=""
"#diff3-cmd"=""
"#diff3-has-program-arg"=""

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\miscellany]
"#global-ignores"="*.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#* .DS_Store"
"#log-encoding"=""
"#use-commit-times"=""
"#template-root"=""
"#enable-auto-props"=""

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\tunnels]

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\auto-props]
</programlisting>
      </example>

      <para>上面例子里显示的<filename>.reg</filename>文件中，包含了一些最常用的配置选项和它们的缺省值。注意，上面的例子中不仅包含了系统设置（关于网络代理相关的选项），也包含了用户设置（指定的编辑器程序，是否保存密码，以及其它选项）。同时要注意的是，所有选项都注释掉了，要启用其中的选项，只需删除该选项名称前面的井号（<literal>#</literal>），然后设置相应的值就可以了。</para>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.confarea.opts">
      <title>配置选项</title>
      
      <para>本节我们会详细讨论Subversion目前支持的运行配置选项。</para>

      <sect3 id="svn.advanced.confarea.opts.servers">
        <title>服务器</title>

        <para><filename>servers</filename>文件保存了Subversion关于网络层的配置选项，这个文件有两个特别的小节：<literal>groups</literal> 和<literal>global</literal>。<literal>groups</literal>小节是一个交叉引用表，其中的关键字是<filename>servers</filename>文件中其它小节的名字，值则是一个可以包含通配符的字符序列，对应于接收Subversion请求的主机名，称为glob。</para>

        <programlisting>
[groups]
beanie-babies = *.red-bean.com
collabnet = svn.collab.net

[beanie-babies]
&hellip;

[collabnet]
&hellip;
</programlisting>

        <para>当通过网络访问Subversion服务器时，客户端会设法匹配正在尝试连接的服务器名字和<literal>groups</literal>小节中的glob名称，如果发现匹配，Subversion会在<filename>servers</filename>文件中查找对应于这个glob名称的小节，并从该小节中去读取真实的网络配置设置。</para>

        <para>如果没有能够匹配到<literal>groups</literal>中的glob名称，<literal>global</literal>小节中的选项就会发生作用。<literal>global</literal>小节中的选项与其他小节一样（当然是除了<literal>groups</literal>小节），这些选项是：</para>

        <variablelist>
          <varlistentry>
            <term><literal>http-proxy-host</literal></term>
            <listitem>
              <para>代理服务器的详细主机名，是HTTP为基础的Subversion请求必须通过的，缺省值为空，意味着Subversion不会尝试通过代理服务器进行HTTP请求，而会尝试直接连接目标机器。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>http-proxy-port</literal></term>
            <listitem>
              <para>代理服务器的详细端口，缺省值为空。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>http-proxy-username</literal></term>
            <listitem>
              <para>代理服务器的用户名，缺省值为空。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>http-proxy-password</literal></term>
            <listitem>
              <para>代理服务器的密码，缺省为空。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>http-timeout</literal></term>
            <listitem>
              <para>等待服务器响应的时间，以秒为单位，如果你的网络速度较慢，导致Subversion的操作超时，你可以加大这个数值，缺省值是<literal>0</literal>，意思是让HTTP库Neon使用自己的缺省值。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>http-compression</literal></term>
            <listitem>
              <para>这说明是否在与设置好DAV的服务器通讯时使用网络压缩请求，缺省值是<literal>yes</literal>（尽管只有在这个功能编译到网络层时压缩才会有效），设置<literal>no</literal>来关闭压缩，如调试网络传输时。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>neon-debug-mask</literal></term>
            <listitem>
              <para>只是一个整形的掩码，底层的HTTP库Neon用来选择产生调试的输出，缺省值是<literal>0</literal>，意思是关闭所有的调试输出，关于Subversion使用Neon的详细信息，见<xref linkend="svn.developer" />。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>ssl-authority-files</literal></term>
            <listitem>
              <para>这是一个分号分割的路径和文件列表，这些文件包含了Subversion客户端在用HTTPS访问时可以接受的认证授权（或者CA）证书。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>ssl-trust-default-ca</literal></term>
            <listitem>
              <para>如果你希望Subversion可以自动相信OpenSSL携带的缺省的CA，可以设置为<literal>yes</literal>。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>ssl-client-cert-file</literal></term>
            <listitem>
              <para>如果一个主机（或是一些主机）需要一个SSL客户端证书，你会收到一个提示说需要证书的路径。通过设置这个路径你的Subversion客户端可以自动找到你的证书而不会打扰你。没有标准的存放位置；Subversion会从任何你指定的路径得到这个文件。</para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>ssl-client-cert-password</literal></term>
            <listitem>
              <para>如果你的SSL客户端证书文件是用密码加密的，Subversion会在每次使用证书时请你输入密码，如果你发现这很讨厌（并且不介意把密码存放在<filename>servers</filename>文件中），你可以设置这个参数为证书的密码，这样就不会再收到密码输入提示了。</para>
            </listitem>
          </varlistentry>
        </variablelist>

      </sect3>
      <sect3 id="svn.advanced.confarea.opts.config">
        <title>config</title>

        <para>其它的Subversion运行选项保存在<filename>config</filename>文件中，这些运行选项与网络连接无关，只是一些正在使用的选项，但是为了应对未来的扩展，也按小节划分成组。</para>

        <para><literal>auth</literal>小节保存了Subversion相关的认证和授权的设置，它包括：</para>

        <variablelist>
          <varlistentry>
            <term><literal>store-passwords</literal></term>
            <listitem>
              <para>这告诉Subversion是否缓存服务器认证要求时用户提供的密码，缺省值是<literal>yes</literal>。设置为<literal>no</literal>可以关闭在存盘的密码缓存，你可以通过<command>svn</command>的<option>--no-auth-cache</option>命令行参数（那些支持这个参数的子命令）来覆盖这个设置，详细信息请见<xref
                linkend="svn.serverconfig.netmodel.credcache"/>。
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>store-auth-creds</literal></term>
            <listitem>
              <para>这个设置与<literal>store-passwords</literal>相似，不过设置了这个选项将会保存<emphasis>所有</emphasis>认证信息，如用户名、密码、服务器证书，以及其他任何类型的可以缓存的凭证。</para>
            </listitem>
          </varlistentry>
        </variablelist>

        <para><literal>helpers</literal>小节控制完成Subversion任务的外部程序，正确的选项包括：</para>

        <variablelist>
          <varlistentry>
            <term><literal>editor-cmd</literal></term>
            <listitem>
              <para>Subversion在提交操作时用来询问用户日志信息的程序，例如使用<command>svn commit</command>而没有指定<option>--message</option>（<option>-m</option>）或者<option>--file</option>（<option>-F</option>）选项。这个程序也会与<command>svn propedit</command>一起使用&mdash;一个临时文件跳出来包含已经存在的用户希望编辑的属性，然后用户可以对这个属性进行编辑（见<xref linkend="svn.advanced.props" />），这个选项的缺省值为空，检测编辑器的顺序如下：
              </para>
              <orderedlist>
                <listitem>
                  <para>命令行选项<literal>--editor-cmd</literal></para>
                </listitem>
                <listitem>
                  <para>环境变量<literal>SVN_EDITOR</literal></para>
                </listitem>
                <listitem>
                  <para>配置选项<literal>editor-cmd</literal></para>
                </listitem>
                <listitem>
                  <para>环境变量<literal>VISUAL</literal></para>
                </listitem>
                <listitem>
                  <para>环境变量<literal>EDITOR</literal></para>
                </listitem>
                <listitem>
                  <para>也有可能Subversion会有一个内置的缺省值（官方编译版本不是如此）
                  </para>
                </listitem>
              </orderedlist>
              <para>所有这些选项和变量（不像<literal>diff-cmd</literal>）的值的开头都是shell中要执行的命令行，Subversion会追加一个空格和一个需要编辑的临时文件，编辑器必须修改临时文件，并且返回一个0来表明成功。
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>diff-cmd</literal></term>
            <listitem>
              <para>这个命令是比较程序的绝对路径，当Subversion生成了<quote>diff</quote>输出时（例如当使用<command>svn diff</command>命令）就会使用，缺省Subversion会使用一个内置的比较库&mdash;设置这个参数会强制它使用外部程序执行这个任务，此类程序的更多信息见<xref linkend="svn.advanced.externaldifftools"/>。
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>diff3-cmd</literal></term>
            <listitem>
              <para>这指定了一个三向的比较程序，Subversion使用这个程序来合并用户和从版本库接受的修改，缺省Subversion会使用一个内置的比较库&mdash;设置这个参数会导致它会使用外部程序执行这个任务，此类程序的更多信息见<xref linkend="svn.advanced.externaldifftools"/>。
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>diff3-has-program-arg</literal></term>
            <listitem>
              <para>如果<literal>diff3-cmd</literal>选项设置的程序接受一个<option>--diff-program</option>命令行参数，这个标记必须设置为<literal>true</literal>。</para>
            </listitem>
          </varlistentry>
        </variablelist>

        <para><literal>tunnels</literal>小节允许你定义一个<command>svnserve</command>和<literal>svn://</literal>客户端连接使用的管道模式，更多细节见<xref linkend="svn.serverconfig.svnserve.sshauth"/>。</para>

        <para><literal>miscellany</literal>小节是一些没法归到别处的选项。
          <footnote>
            <para>就是一个大杂烩？</para> 
          </footnote>
          在本小节，你会找到：</para>

        <variablelist>
          <varlistentry>
            <term><literal>global-ignores</literal></term>
            <listitem>
              <para>当运行<command>svn status</command>命令时，Subversion会和版本化的文件一样列出未版本化的文件和目录，并使用<literal>?</literal>字符（见see <xref linkend="svn.tour.cycle.examine.status" />）标记，有时候察看无关的未版本化文件会很讨厌&mdash;比如程序编译产生的对象文件&mdash;的显示出来。<literal>global-ignores</literal>选项是一个空格分隔的列表，用来描述Subversion在它们版本化之前不想显示的文件和目录，缺省值是<literal>*.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#* .DS_Store</literal>。</para>

              <para>就像<command>svn status</command>，<command>svn add</command>和<command>svn import</command>命令也会忽略匹配这个列表的文件，你可以用单个的<option>--no-ignore</option>命令行参数来覆盖这个选项，关于更加细致的控制忽略的项目，见<xref linkend="svn.advanced.props.special.ignore"/>。</para>
            </listitem>
          </varlistentry>
          
          <varlistentry>
            <term><literal>enable-auto-props</literal></term>
            <listitem>
              <para>这里指示Subversion自动对新加的或者导入的文件设置属性，缺省值是<literal>no</literal>，可以设置为<literal>yes</literal>来开启自动添加属性，这个文件的<literal>auto-props</literal>小节会说明哪些属性会被设置到哪些文件。</para>
            </listitem>            
          </varlistentry>

          <varlistentry>
            <term><literal>log-encoding</literal></term>
            <listitem>
              <para>这个变量设置提交日志缺省的字符集，是<option>--encoding</option>选项（见<xref linkend="svn.ref.svn.sw"/>）的永久形式，Subversion版本库保存了一些UTF-8的日志信息，并且假定你的日志信息是用操作系统的本地编码，如果你提交的信息使用别的编码方式，你一定要指定不同的编码。</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><literal>use-commit-times</literal></term>
            <listitem>
              <para>通常你的工作拷贝文件会有最后一次被进程访问的时间戳，不管是你自己的编辑器还是用<command>svn</command>子命令。这通常对人们开发软件提供了便利，因为编译系统通常会通过查看时间戳来决定那些文件需要重新编译。</para>

              <para>在其他情形，有时候如果工作拷贝的文件时间戳反映了上一次在版本库中更改的时间会非常好，<command>svn export</command>命令会一直放置这些<quote>上次提交的时间戳</quote>放到它创建的目录树。通过设置这个config参数为<literal>yes</literal>，<command>svn checkout</command>、<command>svn update</command>、 <command>svn switch</command>和<command>svn revert</command>命令也会为它们操作的文件设置上次提交的时间戳。</para>
            </listitem>
          </varlistentry>
          
	  <!-- ###TODO add description of other options shown in example
          registry file, e.g., template-root -->
        </variablelist>

        <para><literal>auto-props</literal>小节控制Subversion客户端自动设置提交和导入的文件的属性的能力，它可以包含任意数量的键-值对，格式是<literal>PATTERN = PROPNAME=PROPVALUE</literal>，其中<literal>PATTERN</literal>是一个文件模式，匹配一系列文件名，此行其它两项为属性和值。如果一个文件匹配多次，会导致有多个属性集；然而，没有手段保障自动属性不会按照配置文件中的顺序应用，所以你可以一个规则<quote>覆盖</quote>另一个。你可以在<filename>config</filename>文件找到许多自动属性的用法实例。最后，如果你希望开启自动属性，不要忘了设置<literal>miscellany</literal>小节的<literal>enable-auto-props</literal>为<literal>yes</literal>。</para>

      </sect3>

    </sect2>
  </sect1>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.advanced.props">
    <title>属性</title>

    <para>我们已经详细讲述了Subversion存储和检索版本库中不同版本的文件和目录的细节，并且用了好几个章节来论述这个工具的基本功能。到此为止，Subversion还仅仅表现出一个普通的版本控制理念。但是Subversion并没有就此止步。</para>

    <para>作为目录和文件版本化的补充，Subversion提供了对每一个版本化的目录和文件添加、修改和删除版本化的元数据的接口，我们用<firstterm>属性</firstterm>来表示这些元数据。我们可以认为它们是一个两列的表，附加到你的工作拷贝的每个条目上，映射属性名到任意的值。一般来说，属性的名称和值可以是你希望的任何值，限制就是名称必须是可读的文本，并且最好的一点是这些属性也是版本化的，就像你的文本内容文件，你可以像提交文本修改一样修改、提交和恢复属性修改，当你更新时也会接收到别人的属性修改。</para>

    <sidebar>
      <title>Subversion的其他属性</title>

      <para>Subversion的属性也可以在别的地方出现，就像文件和目录可能附加有任意的属性名和值，每个修订版本作为一个整体也可以附加任意的属性，也有同样的限制&mdash;可读的文本名称和任何你希望的，二进制值&mdash;除了修订版本不是版本化的，参见<xref linkend="svn.reposadmin.basics.revprops" />获得版本化的属性信息。</para>
    </sidebar>

    <para>在本小节，我们将会检验这个工具&mdash;不仅是对Subversion的用户，也对Subversion本身&mdash;对于属性的支持。你会学到与属性相关的<command>svn</command>子命令，和属性怎样影响你的普通Subversion工作流，希望你会感到Subversion的属性可以提高你的版本控制体验。</para>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.props.why">
      <title>为什么需要属性？</title>

      <para>属性可能会是工作拷贝的有益补充，实际上，Subversion本身使用属性来存放特殊的信息，作为支持特别操作的一种方法，同样，你也可以使用属性来实现自己的目的，当然，你对属性作的任何事情也可以针对普通的版本化文件，但是先考虑下面Subversion使用属性的例子。</para>

      <para>假定你希望设计一个网站存放许多数码图片，并且显示他们的标题和时间戳，现在你的图片集经常修改，所以你希望你的网站能够尽量的自动化，这些图片可能非常大，所以根据这个网站的特性，你希望在网站给用户提供图标图像。你可以用传统的文件做这件事，你可以有一个<filename>image123.jpg</filename>和一个<filename>image123-thumbnail.jpg</filename>对应在同一个目录，有时候你希望保持文件名相同，你可以使用不同的目录，如<filename>thumbnails/image123.jpg</filename>。你可以用一种相似的样式来保存你的标题和时间戳同原始图像文件分开。很快你的目录树会是一团糟，每个新图片的添加都会成倍的增加混乱。</para>

      <para>现在考虑使用Subversion文件的属性来做相同的设置，想象我们有一个单独的图像文件<filename>image123.jpg</filename>，然后这个文件的属性集包括<literal>caption</literal>、<literal>datestamp</literal>甚至<literal>thumbnail</literal>。现在你的工作拷贝目录看起来更容易管理&mdash;实际上，它看起来只有图像文件，但是你的自动化脚本知道得更多，它们知道可以用<command>svn</command>（更好的选择是使用Subversion的语言绑定&mdash;见<xref linkend="svn.developer.usingapi.otherlangs" />）来挖掘更多的站点显示需要的额外信息，而不必去阅读一个索引文件或者是玩一个路径处理的游戏。</para>

      <para>你怎样（而且如果）使用Subversion完全在你，像我们提到的，Subversion拥有它自己的属性集，我们会在后面的章节讨论，但首先，让我们讨论怎样使用<command>svn</command>的属性处理选项。</para>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.props.manip">
      <title>处理属性</title>

      <para><command>svn</command>命令提供一些方法来添加和修改文件或目录的属性，对于短的，可读的属性，最简单的添加方法是在<command>propset</command>子命令里指定正确的名称和值。</para>

      <screen>
$ svn propset copyright '(c) 2003 Red-Bean Software' calc/button.c
property 'copyright' set on 'calc/button.c'
$
</screen>
       
      <para>对于属性名称也有一些限制，属性名必须以一个字符、一个冒号(<literal>:</literal>)或下划线(<literal>_</literal>)开始，之后你可以使用数字，横线(<literal>-</literal>)和句号(<literal>.</literal>)。
          <footnote>
            <para>如果你熟悉XML，其实这就是XML的"Name"语法的ASCII子集。</para>
          </footnote>
      </para>

      <para>但是我们已经“吹嘘”过Subversion为属性值提供的灵活性，如果你计划有一个多行的可读文本，甚至是二进制文件的属性值，你通常不希望在命令行里指定，所以<command>propset</command>子命令使用<option>--file</option>（<option>-F</option>）选项来指定一个保存新属性值的文件的名字。</para>

      <screen>
$ svn propset license -F /path/to/LICENSE calc/button.c
property 'license' set on 'calc/button.c'
$
</screen>

      <para>作为<command>propset</command>命令的补充，<command>svn</command>提供了一个<command>propedit</command>命令，这个命令使用定制的编辑器程序（见<xref linkend="svn.advanced.confarea.opts.config" />）来添加和修改属性。当你运行这个命令，<command>svn</command>调用你的编辑器程序打开一个临时文件，文件中保存当前的属性值（或者是空文件，如果你正在添加新的属性）。然后你只需要修改为你想要的值，保存临时文件，然后离开编辑器程序。如果Subversion发现你已经修改了属性值，就会接受新值，如果你未作任何修改而离开，不会产生属性修改操作。</para>

      <screen>
$ svn propedit copyright calc/button.c  ### exit the editor without changes
No changes to property 'copyright' on 'calc/button.c'
$
</screen>

      <para>我们也应该注意导，像其它<command>svn</command>子命令一样，这些关联的属性可以一次添加到多个路径上，这样就可以通过一个命令修改一组文件的属性。举个例子，我们可以：</para>

      <screen>
$ svn propset copyright '(c) 2002 Red-Bean Software' calc/*
property 'copyright' set on 'calc/Makefile'
property 'copyright' set on 'calc/button.c'
property 'copyright' set on 'calc/integer.c'
&hellip;
$
</screen>

      <para>如果不能方便的得到存储的属性值，那么属性的添加和编辑操作也不会很容易，所以<command>svn</command>提供了两个子命令来显示文件和目录存储的属性名和值。<command>svn proplist</command>命令会列出路径上存在的所有属性名称，一旦你知道了某个节点的属性名称，你可以用<command>svn propget</command>获取它的值，这个命令获取给定的路径（或者是一组路径）和属性名称，打印这个属性的值到标准输出。</para>

      <screen>
$ svn proplist calc/button.c
Properties on 'calc/button.c':
  copyright
  license
$ svn propget copyright calc/button.c
(c) 2003 Red-Bean Software
</screen>

      <para>还有一个<command>proplist</command>变种命令会列出所有属性的名称和值，只需要设置<option>--verbose</option>（<option>-v</option>）选项。</para>

      <screen>
$ svn proplist --verbose calc/button.c
Properties on 'calc/button.c':
  copyright : (c) 2003 Red-Bean Software
  license : ================================================================
Copyright (c) 2003 Red-Bean Software.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions 
are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the recipe for Fitz's famous
red-beans-and-rice.
&hellip;
</screen>

      <para>最后一个与属性相关的子命令是<command>propdel</command>，因为Subversion允许属性值为空，所有不能用<command>propedit</command>或者<command>propset</command>命令删除一个属性。举个例子，这个命令<emphasis>不会</emphasis>产生预期的效果：</para>

      <screen>
$ svn propset license '' calc/button.c
property 'license' set on 'calc/button.c'
$ svn proplist --verbose calc/button.c
Properties on 'calc/button.c':
  copyright : (c) 2003 Red-Bean Software
  license : 
$
</screen>

      <para>你需要用<command>propdel</command>来删除属性，语法与其它与属性相关命令相似：</para>

      <screen>
$ svn propdel license calc/button.c
property 'license' deleted from 'calc/button.c'.
$ svn proplist --verbose calc/button.c
Properties on 'calc/button.c':
  copyright : (c) 2003 Red-Bean Software
$
</screen>

      <para>现在你已经熟悉了所有与属性相关的<command>svn</command>子命令，让我们看看属性修改如何影响Subversion的工作流。我们前面提到过，文件和目录的属性是版本化的，这一点类似于版本化的文件内容。后果之一，就是Subversion具有了同样的机制来合并&mdash;用干净或者冲突的方式&mdash;其他人的修改应用到你的修改。</para>

      <sidebar>
        <title>修改修订版本的属性</title>

        <para>还记的这些未版本化的属性？你也可以使用<command>svn</command>命令修改这些属性。只需要添加<option>--revprop</option>命令参数，并且说明希望修改属性的修订版本。因为修订版本是全局的，你不需要指定一个路径，只要你已经位于你希望修改属性的工作拷贝路径，举个例子，你希望修改一个存在版本的提交日志信息。
          <footnote>
            <para>修正提交日志信息的拼写错误，文法错误和<quote>简单的错误</quote>是<option>--revprop</option>选项最常见用例。
            </para>
          </footnote></para>

        <screen>
$ svn propset svn:log '* button.c: Fix a compiler warning.' -r11 --revprop
property 'svn:log' set on repository revision '11'
$
</screen>

        <para>注意，修改这些未版本化的属性的能力一定要明确的添加给版本库管理员（见<xref linkend="svn.reposadmin.create.hooks" />）。因为属性没有版本化，如果编辑的时候不小心，就会冒丢失信息的风险，版本库管理员可以设置方法来防范这种意外，缺省情况下，修改未版本化的属性是禁止的。</para>

      </sidebar>

      <para>就像文件内容，你的属性修改是本地修改，只有使用<command>svn commit</command>命令提交后才会保存到版本库中，属性修改也可以很容易的取消&mdash;<command>svn revert</command>命令会恢复你的文件和目录为编辑前状态，包括内容、属性和其它的信息。另外，你可以使用<command>svn status</command>和<command>svn diff</command>接受感兴趣的文件和目录属性的状态信息。</para>

      <screen>
$ svn status calc/button.c
 M     calc/button.c
$ svn diff calc/button.c
Property changes on: calc/button.c
___________________________________________________________________
Name: copyright
   + (c) 2003 Red-Bean Software

$
</screen>

      <para>注意<command>status</command>子命令显示的<literal>M</literal>在第二列而不是在第一列，这是因为我们修改了<filename>calc/button.c</filename>的属性，而不是它的文本内容，如果我们都修改了，我们也会看到<literal>M</literal>出现在第一列（见<xref linkend="svn.tour.cycle.examine.status" />）。</para>

      <sidebar>
        <title>属性冲突</title>

        <para>与文件内容一样，本地的属性修改也会同别人的提交冲突，如果你更新你的工作拷贝目录并且接收到有资源属性修改与你的修改冲突，Subversion会报告资源处于冲突状态。</para>
 
        <screen>
% svn update calc
M  calc/Makefile.in
 C calc/button.c
Updated to revision 143.
$ 
</screen>
         
        <para>Subversion也会在冲突资源的同一个目录创建一个<filename>.prej</filename>扩展名的文件，保存冲突的细节。你一定要检查这个文件的内容来决定如何解决冲突，在你解决冲突之前，你会在使用<command>svn status</command>时看到这个资源的输出的第二列是一个<literal>C</literal>，提交本地修改的尝试会失败。</para>

        <screen>
$ svn status calc
 C     calc/button.c
?      calc/button.c.prej
$ cat calc/button.c.prej 
prop 'linecount': user set to '1256', but update set to '1301'.
$
</screen>
 
        <para>为了解决属性冲突，只需要确定冲突的属性保存了它们应该的值，然后使用<command>svn resolved</command>命令告诉Subversion你已经手工解决了问题。</para>

      </sidebar>

      <para>你也许已经注意到了Subversion在显示属性时的非标准方式。你还可以运行<command>svn diff</command>并且重定向输出来产生一个有用的补丁文件，<command>patch</command>程序会忽略属性补丁&mdash;作为规则，它会忽略任何不理解的噪音。很遗憾，这意味着完全应用<command>svn diff</command>产生的补丁时，任何属性修改必须手工实施。</para>

      <para>就象你看到的，属性修改的出现并没有对典型的Subversion工作流有显著的影响，更新工作拷贝、检查文件和目录的状态、报告所作的修改和提交修改到版本库等等的工作方式完全与属性的存在与否无关。<command>svn</command>程序有一些额外的子命令用来进行属性修改，但那是唯一显而易见不对称的命令。</para>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.props.special">
      
      <title>特别属性</title>

      <para>Subversion没有关于属性的特殊政策&mdash;你可以通过它们实现自己的目的。Subversion只是要求你不要使用<literal>svn:</literal>开头的命名空间作为属性名，这是Subversion自己使用的命名空间。实际上，Subversion定义了某些特殊的属性，这些属性对它们所附加的文件和目录有特殊的影响。在本小节，我们会解开这个谜团，并且描述这些属性怎样让你的生活更加容易。</para>
      
      <sect3 id="svn.advanced.props.special.executable">
        <title><literal>svn:executable</literal></title>
 
        <para><literal>svn:executable</literal>属性用来控制一个版本化的文件自动执行文件权限设定，这个属性没有特定的值&mdash;它只是说明一个Subversion可以保留的文件权限的期望值，删除这个属性会恢复操作系统对这些权限的完全控制。</para>

        <para>在多数操作系统，执行一个文件或命令的能力是由执行位管理的，这些位通常是关闭的，必须由用户显式的指定，这意味着你必须改变文件的执行位，然后更新你的工作拷贝，燃火如果你的文件成为更新的一部分，它的执行位会被关闭，所以Subversion提供了<literal>svn:executable</literal>这个属性来保持打开执行位。</para>

        <para>这个属性对于没有可执行权限位的文件系统无效，如FAT32和NTFS。
          <footnote>
            <para>Windows文件系统使用文件扩展名（如<literal>.EXE</literal>、<literal>.BAT</literal>和<literal>.COM</literal>）来标示可执行文件。
           </para>
          </footnote>
          也就是说，尽管它没有定义的值，在设置这个属性时，Subversion会强制它的值为<literal>*</literal>，最后，这个属性只对文件有效，目录无效。
         </para>

      </sect3>

      <sect3 id="svn.advanced.props.special.mime-type">
        <title><literal>svn:mime-type</literal></title>
        
        <para><literal>svn:mime-type</literal>属性为Subversion的许多目的服务，除了保存一个文件的多用途网际邮件扩展（MIME）分类以外，这个属性值也描述了一些Subversion自己使用的行为特性。
        </para>

        <para>举个例子，如果一个文件<literal>svn:mime-type</literal>属性设置为非文本的MIME类型（通常是那些不是<literal>text/</literal>开头的类型，但也有例外），Subversion会假定这个文件保存了二进制内容&mdash;也就是不可读的&mdash;数据。一个好处就是Subversion通常在更新到工作拷贝时提供了一个前后相关的以行为基础的修改合并，但是对于保存二进制数据的文件，没有<quote>行</quote>的概念，所以对这些文件，Subversion不会在更新时尝试执行合并操作，相反，任何时候你在本地修改的一个二进制文件有了更新，你的文件会被重命名为<filename>.orig</filename>为扩展名，然后Subversion保存一个新的工作拷贝文件，保存更新时得到的修改，但原来的文件名已经不是你自己的本地修改。这个行为模式是用来保护用户在对不可文本合并的文件尝试执行文本的合并时失败的情形。</para>

        <para>另外，如果<literal>svn:mime-type</literal>属性被设置，Subversion的Apache模块会使用这个值来在HTTP头里输入<literal>Content-type:</literal>，这给了web浏览器如何显示一个文件提供了至关重要的线索。
        </para>

      </sect3>

      <sect3 id="svn.advanced.props.special.ignore">
        <title><literal>svn:ignore</literal></title>

        <para>这个<literal>svn:ignore</literal>属性保存了一个Subversion特定操作忽略的文件模式列表，或许这个是最常用的属性，它可以与<literal>global-ignores</literal>运行配置选项配合使用（见<xref linkend="svn.advanced.confarea.opts.config" />）来过滤<command>svn status</command>、<command>svn add</command>和<command>svn import</command>命令中操作的未版本化文件。</para>

        <para><literal>svn:ignore</literal>背后的基本原理很容易解释，Subversion不会假定工作拷贝中的所有文件或子目录是版本控制的一部分，资源必须被显式的使用<command>svn add</command>或者<command>svn import</command>放到Subversion的管理控制之下，作为结果，经常有许多工作拷贝的资源并没有版本化。</para>

        <para>现在，<command>svn status</command>命令会的显示会包括所有未纳入版本控制且没有用<literal>global-ignores</literal>（或是内置的缺省值）过滤掉的文件和子目录，这样可以帮助用户查看是否忘记了把某些自愿加入到版本控制。</para>

        <para>但是Subversion不可能猜测到每个需要忽略的资源的名字，但是也有一些资源是<emphasis>所有</emphasis>特定版本库的工作拷贝都有忽略的，强制版本库的每个用户来添加这些模式到他们的运行配置区域不仅仅是一个负担，也会与用户取出的其他工作拷贝配置需要存在潜在的冲突。</para>

        <para>解决方案是保存的忽略模式必须对出现在给定目录和这个目录本身的资源是独立的，一个常见的例子就是一个未版本化资源对一个目录来说是唯一的，会出现在那个位置，包括程序编译的输出，或者是&mdash;用一个本书的例子&mdash;DocBook的文件生成的HTML、PDF或者是PostScript文件。</para>

        <sidebar>
          <title>CVS用户的忽略模式</title>
        
          <para>Subversion的<literal>svn:ignore</literal>属性与CVS的<filename>.cvsignore</filename>文件的语法和功能非常类似，实际上，如果你移植一个CVS的工作拷贝到Subversion，你可以直接使用<filename>.cvsignore</filename>作为<command>svn propset</command>输入文件参数：
         </para>
       
          <screen>
$ svn propset svn:ignore -F .cvsignore .
property 'svn:ignore' set on '.'
$
</screen>        

          <para>但是CVS和Subversion处理忽略模式的方式有一些不同，这两个系统在不同的时候使用忽略模式，忽略模式应用的对象也由微小的不同，但是Subversion不会识别重置回到没有忽略模式的<literal>!</literal>模式的使用。
         </para>

        </sidebar>

        <para>为了这个目的，<literal>svn:ignore</literal>属性是解决方案，它的值是一个多行的文件模式集，一行一个模式，这个属性已经设置到这个你希望应用模式的目录。
          <footnote>
            <para>这个模式对那个目录是严格的&mdash;不会迭代的应用到子目录。
            </para>
          </footnote>
          举个例子，你的<command>svn status</command>有如下的输出：
          </para>

        <screen>
$ svn status calc
 M     calc/button.c
?      calc/calculator
?      calc/data.c
?      calc/debug_log
?      calc/debug_log.1
?      calc/debug_log.2.gz
?      calc/debug_log.3.gz
</screen>

        <para>在这个例子里，你对<filename>button.c</filename>文件作了一些属性修改，但是你的工作拷贝也有一些未版本化的文件：你从源代码编译的最新的<filename>计算器</filename>程序是<filename>data.c</filename>，一系列调试输出日志文件，现在你知道你的编译系统会编译生成<filename>计算器</filename>程序。
          <footnote>
            <para>这不是编译系统的基本功能吗？</para>
          </footnote>
          就像你知道的，你的测试组件总是会留下这些调试日志，这对所有的工作拷贝都是一样的，不仅仅使你的。你也知道你不会有兴趣在<command>svn status</command>命令中显示这些信息，所以使用<command>svn propedit svn:ignore calc</command>来为<filename>calc</filename>目录增加一些忽略模式，举个例子，你或许会添加如下的值作为<literal>svn:ignore</literal>属性：</para>

        <programlisting>
calculator
debug_log*
</programlisting>

        <para>当你添加完这些属性，你会在<filename>calc</filename>目录有一个本地修改，但是注意你的<command>svn status</command>输出有什么其他的不同：</para>

        <screen>
$ svn status
 M     calc
 M     calc/button.c
?      calc/data.c
</screen>

        <para>现在，所有多余的输出不见了！当然，这些文件还在工作拷贝中，Subversion仅仅是不再提醒你它们的存在和未版本化。现在所有讨厌的噪音都已经删除了，你留下了更加感兴趣的项目&mdash;如你忘记添加到版本控制的源代码文件。
       </para>

        <para>如果想查看被忽略的文件，可以设置Subversion的<option>--no-ignore</option>选项：</para>

<screen>
$ svn status --no-ignore
 M     calc/button.c
I      calc/calculator
?      calc/data.c
I      calc/debug_log
I      calc/debug_log.1
I      calc/debug_log.2.gz
I      calc/debug_log.3.gz
</screen>

        <para><command>svn add</command>和<command>svn import</command>也会使用这个忽略模式列表，这两个操作都包括了询问Subversion来开始管理一组文件和目录。比强制用户挑拣目录树中那个文件要纳入版本控制的方式更好，Subversion使用忽略模式来检测那个文件不应该在大的迭代添加和导入操作中进入版本控制系统。</para>
   
      </sect3>

      <sect3 id="svn.advanced.props.special.keywords">
        <title><literal>svn:keywords</literal></title>

        <para>Subversion具备有添加<firstterm>关键字</firstterm>的能力&mdash;一些有用的，关于版本化的文件动态信息的片断&mdash;不必直接添加到文件本身。关键字通常会用来描述文件最后一次修改的一些信息，因为这些信息每次都有改变，更重要的一点，这是在文件修改<emphasis>之后</emphasis>，除了版本控制系统，对于任何处理完全保持最新的数据都是一场争论，作为人类作者，信息变得陈旧是不可避免的。</para>

        <para>举个例子，你有一个文档希望显示最后修改的日期，你需要麻烦每个作者提交之前做这件事情，同时会改变描述这部分细细的部分，但是迟早会有人忘记做这件事，不选择简单的告诉Subversion来执行替换<literal>LastChangedDate</literal>关键字的操作，在你的文档需要放置这个关键字的地方放置一个<firstterm>keyword anchor</firstterm>，这个anchor只是一个格式为<literal>$</literal><replaceable>KeywordName</replaceable><literal>$</literal>字符串。
       </para>

        <para>所有作为anchor出现在文件里的关键字是大小写敏感的：为了关键字的扩展，你必须使用正确的按顺序大写。你必须考虑<literal>svn:keywords</literal>的属性值也是大小写敏感&mdash;特定的关键字名会忽略大小写，但是这个特性已经被废弃了。</para>

        <para>Subversion定义了用来替换的关键字列表，这个列表保存了如下五个关键字，有一些也包括了可用的别名：
       </para>

        <variablelist>
          <varlistentry>
            <term><literal>Date</literal></term>
            <listitem>
              <para>这个关键字保存了文件最后一次在版本库修改的日期，看起来类似于<literal>$Date:
                2002-07-22 21:42:37 -0700 (Mon, 22 Jul 2002)
                $</literal>，它也可以用<literal>LastChangedDate</literal>来指定。
                </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>Revision</literal></term>
            <listitem>
              <para>这个关键字描述了这个文件最后一次修改的修订版本，看起来像<literal>$Revision: 144 $</literal>，也可以通过<literal>LastChangedRevision</literal>或者<literal>Rev</literal>引用。
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>Author</literal></term>
            <listitem>
              <para>这个关键字描述了最后一个修改这个文件的用户，看起来类似<literal>$Author: harry $</literal>，也可以用<literal>LastChangedBy</literal>来指定。
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>HeadURL</literal></term>
            <listitem>
              <para>这个关键字描述了这个文件在版本库最新的版本的完全URL，看起来类似<literal>$HeadURL:
                http://svn.collab.net/repos/trunk/README $</literal>，可以缩写为<literal>URL</literal>。
                </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>Id</literal></term>
            <listitem>
              <para>这个关键字是其他关键字一个压缩组合，它看起来就像<literal>$Id: calc.c 148 2002-07-28 21:30:43Z sally
                $</literal>，可以解释为文件<filename>calc.c</filename>上一次修改的修订版本号是148，时间是2002年7月28日，作者是<literal>sally</literal>。
               </para>
            </listitem>
          </varlistentry>
        </variablelist>

        <para>只在你的文件增加关键字anchor不会做什么特别的事情，Subversion不会尝试对你的文件内容执行文本替换，除非明确的被告知这样做，毕竟，你可以撰写一个文档
          <footnote>
            <para>&hellip; 或者可能是一本书的一个小节 &hellip; </para>
          </footnote> 
          关于如何使用关键字，你希望Subversion不会替代你漂亮的关于不需要替换的关键字anchor实例！
          </para>

        <para>为了告诉Subversion是否替代某个文件的关键字，我们要再次求助于属性相关的子命令，当<literal>svn:keywords</literal>属性设置到一个版本化的文件，这些属性控制了那些关键字将会替换到那个文件。这个值是空格分隔的前面列表的名称或是别名列表。</para>

        <para>举个例子，假定你有一个版本化的文件<filename>weather.txt</filename>，内容如下：
        </para>

        <programlisting>
Here is the latest report from the front lines.
$LastChangedDate$
$Rev$
Cumulus clouds are appearing more frequently as summer approaches.
</programlisting>
        
        <para>当没有<literal>svn:keywords</literal>属性设置到这个文件，Subversion不会有任何特别操作，现在让我们允许<literal>LastChangedDate</literal>关键字的替换。
       </para>

        <screen>
$ svn propset svn:keywords "Date Author" weather.txt
property 'svn:keywords' set on 'weather.txt'
$
</screen>        

        <para>现在你已经对<filename>weather.txt</filename>的属性作了修改，你会看到文件的内容没有改变（除非你之前做了一些属性设置），注意这个文件包含了<literal>Rev</literal>的关键字anchor，但我们没有在属性值中包括这个关键字，Subversion会高兴的忽略替换这个文件中的关键字，也不会替换<literal>svn:keywords</literal>属性中没有出现的关键字。
       </para>

        <sidebar>
          <title>关键字和虚假的差异</title>

          <para>用户可见的关键字替换会让你以为每一个具有此特性的文件的每个版本都会与前一个版本至少在关键字替换的地方不同，但是实际上并不是如此，当用<command>svn
            diff</command>检查本地修改时，或者是在使用<command>svn commit</command>传输修改之前，Subversion不会<quote>取消替换</quote>任何上次替换的关键字，结果就是版本库保存的文件只保存用户实际做的修改。
            </para>

        </sidebar>

        <para>在你提交了属性修改后，Subversion会立刻更新你的工作文件为新的替代文本，你将无法找到<literal>$LastChangedDate$</literal>的关键字anchor，你会看到替换的结果，这个结果也保存了关键字的名字，与美元符号（<literal>$</literal>）绑定在一起，而且我们预测的，<literal>Rev</literal>关键字不会被替换，因为我们没有要求这样做。</para>

        <para>注意我们设置<literal>svn:keywords</literal>属性为<quote>Date Author</quote>，关键字anchor使用别名<literal>$LastChangedDate$</literal>并且正确的扩展。
        </para>

        <screen>
Here is the latest report from the front lines.
$LastChangedDate: 2002-07-22 21:42:37 -0700 (Mon, 22 Jul 2002) $
$Rev$
Cumulus clouds are appearing more frequently as summer approaches.
</screen>
        
        <para>如果有其他人提交了<filename>weather.txt</filename>的修改，你的此文件的拷贝还会显示同样的替换关键字值&mdash;直到你更新你的工作拷贝，此时你的<filename>weather.txt</filename>重的关键字将会被替换来反映最新的提交信息。
       </para>
<para>Subversion 1.2引入了另一种关键字的语法，提供了额外和有用的，尽管是非典型的功能。你现在可以告诉Subversion为替代的关键字维护一个固定长度（从消耗字节的观点），通过在关键字名后使用双冒号（<literal>::</literal>），然后紧跟一组空格，你就定义了固定宽度。当Subversion使用替代值代替你的关键字，只会替换这些空白字符，保持关键字字段长度保持不变，如果替代值比定义的字段短，会有替代字段后保留空格；如果替代值太长，就会在最后的美元符号终止符前用井号（<literal>#</literal>）截断。
</para>

        <para>例如，你有一篇文档，其中一段是一些反映Subversion关键字的表格数据，使用原始的Subversion关键字替换语法，你的文件或许像这样：
       </para>

        <screen>
$Rev$:     Revision of last commit
$Author$:  Author of last commit
$Date$:    Date of last commit
</screen>

        <para>现在，表格看起来佷漂亮，但是当你提交文件（当然，关键字替换功能已打开），你会看到：
        </para>

        <screen>
$Rev: 12 $:     Revision of last commit
$Author: harry $:  Author of last commit
$Date: 2006-03-15 02:33:03 -0500 (Wed, 15 Mar 2006) $:    Date of last commit
</screen>

        <para>结果并不漂亮，你可能会尝试重新调整文件使之更像一个列表。只有关键字的长度是相同的时候才能保证保持样式，如果进入另一个修订版本（如从99到100），或者是另一个有较长用户名的人提交了文件，表格又会变形。然而，如果你使用Subversion 1.2，你可以使用新的固定长度的关键字语法，定义合适的字段宽度，然后你的文件可能如此：
       </para>

        <screen>
$Rev::               $:  Revision of last commit
$Author::            $:  Author of last commit
$Date::              $:  Date of last commit
</screen>

        <para>你提交这个文件的修改，这一次Subversion注意到了新的固定长度的关键字语法，根据你在双冒号之间指定的空格长度调整格式，并且紧跟一个美元符号。经过替换，字段的长度没有发生变化&mdash;<literal>Rev</literal>和<literal>Author</literal>多了一些空格，而较长的<literal>Date</literal>字段被一个井号截断：
       </para>

        <screen>
$Rev:: 13            $:  Revision of last commit
$Author:: harry      $:  Author of last commit
$Date:: 2006-03-15 0#$:  Date of last commit
</screen>
       
        <para>固定长度关键字在执行复杂文件格式的替换中非常易用，也可以处理那些很难通过其他程序（例如Microsoft Office文档）进行修改的文件。
        </para>

        <warning>
          <para>需要意识到，因为关键字字段的长度是以字节为单位，可能会破坏多字节值，例如一个用户名包含多字节的UTF-8字符，可能会遭遇从某个字符中间截断的情况，从字节角度看仅仅是一种截断，但是从UTF-8字符串角度看可能是错误和曲解的，当载入文件时，破坏的UTF-8文本可能导致整个文件的破坏，整个文件无法操作。
          </para> 
        </warning>
      </sect3>

      <sect3 id="svn.advanced.props.special.eol-style">
        <title><literal>svn:eol-style</literal></title>

        <para>不像我们说过的版本化文件的<literal>svn:mime-type</literal>属性，Subversion假定这个文件保存了可读的数据，一般来讲，Subversion因为这个属性来判断一个文件是否可以用上下文区别报告，否则，对Subversion来说只是字节。</para>
        
        <para>这意味着缺省情况下，Subversion不会关注任何<firstterm>行结束标记（end-of-line，EOL）</firstterm>，不幸的是不同的操作系统在文本文件使用不同的行结束标志，举个例子，Windows平台下的A编辑工具使用一对SCII控制字符&mdash;回车（<literal>CR</literal>）和一个移行（<literal>LF</literal>）。Unix软件，只使用一个<literal>LF</literal>来表示一个行的结束。
        </para>

        <para>并不是所有操作系统的工具准备好了理解与<firstterm>本地行结束样式</firstterm>不一样的行结束格式，一个常见的结果是Unix程序会把Windows文件中的<literal>CR</literal>当作一个不同的字符（通常表现为<literal>^M</literal>），而Windows程序会把Unix文件合并为一个非常大的行，因为没有发现标志行结束的回车加换行（或者是<literal>CRLF</literal>）字符。</para>

        <para>对外来EOL标志的敏感会让在各个操作系统分享文件的人们感到沮丧，例如，考虑有一个源代码文件，开发者会在Windows和Unix系统上编辑这个文件，如果所有的用户使用的工具可以展示文件的行结束，那就没有问题。</para>

        <para>但实践中，许多常用的工具不会正确的读取外来的EOL标志，或者是将文件的行结束转化为本地的样式，如果是前者，他需要一个外部的转化工具（如<command>dos2unix</command>或是他的伴侣，<command>unix2dos</command>）来准备需要编辑的文件。后一种情况不需要额外的准备工作，两种方法都会造成文件会与原来的文件在每一行上都不一样！在提交之前，用户有两个选择，或者选择用一个转化工具恢复文件的行结束样式，或者是简单的提交文件&mdash;包含新的EOL标志。
       </para>

        <para>这个情景的结局看起来像是要浪费时间对提交的文件作不必要的修改，浪费时间是痛苦的，但是如果提交修改了文件的每一行，判断那个文件是通过正常的方式修改的会是一件复杂的工作，bug在那一行修正的？那一行引入了语法错误？
       </para>

        <para>这个问题的解决方案是<literal>svn:eol-style</literal>属性，当这个属性设置为一个正确的值，Subversion使用它来判断针对行结束样式执行何种特殊的操作，而不会因为多种操作系统的每次提交发生震荡。正确的值有：</para>

        <variablelist>
          <varlistentry>
            <term><literal>native</literal></term>
            <listitem>
              <para>这会导致保存EOL标志的文件使用Subversion运行的操作系统的本地编码，换句话说，如果一个Windows用户取出一个工作拷贝包含的一个文件有<literal>svn:eol-style</literal>的属性设置为<literal>native</literal>，这个文件会使用<literal>CRLF</literal>的EOL标志，一个Unix用户取出相同的文件会看到他的文件使用<literal>LF</literal>的EOL标志。
             </para>

              <para>注意Subversion实际上使用<literal>LF</literal>的EOL标志，而不会考略操作系统，尽管这对用户来说是透明的。
             </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>CRLF</literal></term>
            <listitem>
              <para>这会导致这个文件使用<literal>CRLF</literal>序列作为EOL标志，不管使用何种操作系统。
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>LF</literal></term>
            <listitem>
              <para>这会导致文件使用<literal>LF</literal>字符作为EOL标志，不管使用何种操作系统。
              </para>
            </listitem>
          </varlistentry>
          <varlistentry>
            <term><literal>CR</literal></term>
            <listitem>
              <para>这会导致文件使用<literal>CR</literal>字符作为EOL标志，不管使用何种操作系统。这种行结束样式不是很常见，它用在一些老的苹果机（Subversion不会运行的机器上）。
             </para>
            </listitem>
          </varlistentry>
        </variablelist>
        
      </sect3>

      <sect3 id="svn.advanced.props.special.externals">
        <title><literal>svn:externals</literal></title>

        <para><literal>svn:externals</literal>属性保存了指导Subversion从一个或多个取出的工作拷贝移出目录的指示，关于这个关键字的更多信息，见<xref linkend="svn.advanced.externals"/>。
       </para>

      </sect3>

      <sect3 id="svn.advanced.props.special.special">
        <title><literal>svn:special</literal></title>

        <para><literal>svn:special</literal>是唯一一个不是用户直接设置和修改的<literal>svn:</literal>属性，当<quote>特别的</quote>对象如一个对象链接计划加入到版本库，Subversion会自动设置这个属性。版本库像普通文件一样保存<literal>svn:special</literal>对象，然而，当一个客户端在检出和更新操作时看到这个属性时，就会翻译这个文件的内容，并且将文件转化为特殊类型的对象，在Subversion目前的版本中，只有版本化的符号链接有这个属性附加，但在以后的版本中其它特殊的节点也有可能使用这个属性。</para>

        <para>注意：Windows客户端不会有符号链接，因此会忽略含有<literal>svn:special</literal>声明为符号链的文件，在Windows，用户会以一个工作拷贝中的版本化的文件作为结束。
       </para>
      </sect3>

      <sect3 id="svn.advanced.props.special.needs-lock">
        <title><literal>svn:needs-lock</literal></title>

        <para>这个属性用来标示这个文件在编辑之前需要锁定，属性值无所谓，Subversion会规格化为<literal>*</literal>。当有这个值时，<emphasis>除非</emphasis>用户锁定这个文件，否则文件一直是只读的。当得到一个锁定令牌（运行<command>svn lock</command>的结果），文件变成可读写，当释放这个锁后，文件又变成只读。
       </para>

        <para>为什么要使用这个属性的更多信息见<xref
          linkend="svn.advanced.locking.lock-communication"/>。</para>
      </sect3>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.props.auto">
      <title>自动属性设置</title>

      <para>属性是Subversion一个强大的特性，成为本章和其它章讨论的许多Subversion特性的关键组成部分&mdash;文本区别和合并支持、关键字替换、新行的自动转换等等。但是为了从属性得到完全的利益，他们必须设置到正确的文件和目录。不幸的是，在日常工作中很容易忘记这一步工作，特别是当没有设置属性不会引起明显的错误时（至少相对与未能添加一个文件到版本控制这种操作），为了帮助你在需要添加属性的文件上添加属性，Subversion提供了一些简单但是有用的特性。</para>

      <para>当你使用<command>svn add</command>或是<command>svn import</command>准备加入一个版本控制的文件时，Subversion会运行一个基本探测来检查文件是包括了可读还是不可读的内容，如果Subversion猜测错误，或者是你希望使用<literal>svn:mime-type</literal>属性更精确的设置&mdash;或许是<literal>image/png</literal>或者<literal>application/x-shockwave-flash</literal>&mdash;你可以一直删除或编辑那个属性。</para>

      <para>Subversion也提供了自动属性特性，允许你创建文件名到属性名称与值影射，这个影射在你的运行配置区域设置，它们会影响添加和导入操作，而且不仅仅会覆盖Subversion所有缺省的MIME类型判断操作，也会设置额外的Subversion或者自定义的属性。举个例子，你会创建一个影射文件说在任何时候你添加了一个JPEG文件&mdash;一些符合<literal>*.jpg</literal>的文件&mdash;Subversion一定会自动设置它们的<literal>svn:mime-type</literal>属性为<literal>image/jpeg</literal>。或者是任何匹配<literal>*.cpp</literal>的文件，必须把<literal>svn:eol-style</literal>设置为<literal>native</literal>，并且<literal>svn:keywords</literal>设置为<literal>Id</literal>。自动属性支持是Subversion工具箱中属性相关最垂手可得的工具，见<xref linkend="svn.advanced.confarea.opts.config"/>来查看更多的配置支持。</para>

    </sect2>     
  </sect1>


  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.advanced.locking">
    <title>锁定</title>

    <para>Subversion的<quote>拷贝-修改-合并</quote>模型是以行为基础（如程序源代码 ）的项目最佳的协调方式，然而就像<xref
      linkend="svn.basic.vsn-models.copy-merge.sb-1"/>里说过的，有时候有些人需要<quote>锁定-修改-解锁</quote>模型来代替Subversion的标准模型，当一个文件由二进制数据组成，合并两个修改通常是困难的，Subversion 1.2和以后的版本提供了<firstterm>锁定</firstterm>特性，也就是别的版本控制系统常说的<quote>保留检出</quote>。
      </para>

    <para>Subversion的锁定特性有两个目标：</para>

    <itemizedlist>
      <listitem><para><emphasis>顺序访问资源</emphasis>。允许用户得到一个排他的修改文件权，如果Harry保留了修改<filename>foo.jpg</filename>的权利，Sally将不可以提交这个文件的修改。
     </para>
      </listitem>
      <listitem><para><emphasis>辅助交流</emphasis>。防止用户进行不可合并的修改，如果Harry已经保留了修改<filename>foo.jpg</filename>的权利，然后Sally会很容易注意到这个事实而不会去修改这个文件。</para>
      </listitem>
    </itemizedlist>

    <para>Subversion的锁定特性当前限制到文件&mdash;还没有可能限制整个目录树的访问。</para>

<sidebar id="svn.advanced.locking.meanings">
      <title><quote>锁定</quote>的三种含义</title>

      <para>在本小节，和几乎本书的每一个地方<quote>lock</quote>和<quote>locking</quote>描述了一种避免用户之间冲突提交的排他机制，但是佷不幸，Subversion中还有另外两种锁，因此需要在本书格外关心。
     </para>

      <itemizedlist>

        <listitem><para><firstterm>工作拷贝锁</firstterm>，Subversion内部用来防止不同客户端同时操作同一份工作拷贝的锁，这种锁使用<command>svn status</command>输出中第三列出现的<computeroutput>L</computeroutput>表示，可以使用<command>svn cleanup</command>删除（<xref 
       linkend="svn.tour.other.cleanup"/>有介绍）。</para>
        </listitem>

        <listitem><para><firstterm>数据库锁</firstterm>，在Berkeley DB后端内部使用，防止多个程序访问数据库发生冲突，一个导致版本库<quote>楔住</quote>的错误发生后产生（<xref 
        linkend="svn.reposadmin.maint.recovery"/>有描述）。
        </para>
        </listitem>

      </itemizedlist>

      <para>在发生问题之前你可以忘记上面两种锁，在本书，<quote>锁定</quote>意味着第一种锁，除非是清除不合理的上下文或明确的状态。
     </para>
    </sidebar>
    <!-- =============================================================== -->
    <sect2 id="svn.advanced.locking.creation">
      <title>创建锁定</title>
      
      <para>在Subversion的版本库，一个<firstterm>锁</firstterm>是一份元数据，可以排它赋予某个用户修改权，这个用户被称作<firstterm>锁的拥有者</firstterm>。每个锁都有一个唯一标识，通常是一长串字符，叫做<firstterm>锁令牌</firstterm>。版本库在一个单独的表里管理锁，提交时强制锁定检查，如果操作会修改或删除文件（或者是删除文件父目录），版本库会要求两份信息：
     </para>
      
      <orderedlist>
        <listitem><para><emphasis role="bold">用户认证</emphasis>。执行提交的客户端必须认证是锁的拥有者。</para>
        </listitem>
        <listitem><para><emphasis role="bold">软件授权</emphasis>。用户的工作拷贝必须将锁令牌提交，证明它清楚地知道使用那个锁。
        </para>
        </listitem>
      </orderedlist>
      
      <para>一个例子是按照顺序。为了描述方便，假定Harry决定修改一个JPEG图像，为了防止其他用户提交这个文件的修改，他使用<command>svn lock</command>命令锁定了版本库的这个文件：</para>

      <screen>
$ svn lock banana.jpg --message "Editing file for tomorrow's release."
'banana.jpg' locked by user 'harry'.

$ svn status
     K banana.jpg

$ svn info banana.jpg
Path: banana.jpg
Name: banana.jpg
URL: http://svn.example.com/repos/project/banana.jpg
Repository UUID: edb2f264-5ef2-0310-a47a-87b0ce17a8ec
Revision: 2198
Node Kind: file
Schedule: normal
Last Changed Author: frank
Last Changed Rev: 1950
Last Changed Date: 2005-03-15 12:43:04 -0600 (Tue, 15 Mar 2005)
Text Last Updated: 2005-06-08 19:23:07 -0500 (Wed, 08 Jun 2005)
Properties Last Updated: 2005-06-08 19:23:07 -0500 (Wed, 08 Jun 2005)
Checksum: 3b110d3b10638f5d1f4fe0f436a5a2a5
Lock Token: opaquelocktoken:0c0f600b-88f9-0310-9e48-355b44d4a58e
Lock Owner: harry
Lock Created: 2005-06-14 17:20:31 -0500 (Tue, 14 Jun 2005)
Lock Comment (1 line):
Editing file for tomorrow's release.

</screen>

      <para>前一个例子描述了许多新事物，第一，注意Harry在<command>svn
        lock</command>中使用了<option>--message</option>选项，类似于<command>svn 
        commit</command>，<command>svn lock</command>命令可以有描述锁定原因的注释（通过<option>--message 
        (-m)</option>或<option>--file (-F)</option>）。然而不像<command>svn 
        commit</command>，<command>svn
        lock</command>不会自动强制启动你喜欢的编辑器，锁定注释是可选的，但是为了方便交流我们还是推荐使用。
        </para>

      <para>第二，锁定成功了。这意味着文件已经锁定了，Harry有了文件最新的版本，如果Harry的工作拷贝文件不是最新的，版本库会拒绝请求，强制Harry执行<command>svn update</command>并重新运行锁定命令。</para>

      <para>也要注意到在创建版本库的锁定之后，工作拷贝也缓存了锁定的信息&mdash;最重要的是锁定令牌。有锁定令牌是非常重要的，这给了工作拷贝权利利用这个锁的能力。<command>svn status</command>会在文件后面显示一个<literal>K</literal>（locKed的缩写），表明了拥有锁定令牌。
     </para>

      <sidebar>
        <title>关于锁定令牌</title>

        <para>一个锁定令牌不是一个认证令牌，也不是<emphasis>授权</emphasis>令牌。实际上，任何用户可以运行<command>svn info
          URL</command>发现锁定唯一令牌。</para>

        <para>一个锁定令牌只有在工作拷贝中才有特别的意义，它是锁定建立在这个工作拷贝的证据，而不是其它用户在其他地方。仅仅检验锁定拥有者还不能防止事故。</para>

        <para>例如：你在办公室电脑上锁定了一个文件，或许修改正在进行中。很有可能在你的家用计算机上的一个工作拷贝（或别的Subversion客户端）里你又不小心修改了同一个文件，仅仅因为检验了你就是锁定的拥有者。换句话说，锁定令牌防止你通过一个Subversion相关软件的工作破坏另一个。（在我们的例子里，如果你真的需要在另一个工作拷贝修改这个文件，你必须打破锁定再重新锁定文件。）
       </para>
      </sidebar>

      <para>现在Harry已经锁定了<filename>banana.jpg</filename>，Sally不能修改或删除这个文件：</para>

      <screen>
$ whoami
sally

$ svn delete banana.jpg
D         banana.jpg

$ svn commit -m "Delete useless file."
Deleting       banana.jpg
svn: Commit failed (details follow):
svn: DELETE of
'/repos/project/!svn/wrk/64bad3a9-96f9-0310-818a-df4224ddc35d/banana.jpg':
423 Locked (http://svn.example.com)

</screen>

      <para>但是，当完成了香蕉的黄色渐变，就可以提交文件的修改，因为认证为锁定的拥有者，也因为他的工作拷贝有正确的锁定令牌：
     </para>

      <screen>
$ whoami
harry

$ svn status
M    K banana.jpg

$ svn commit -m "Make banana more yellow"
Sending        banana.jpg
Transmitting file data .
Committed revision 2201.

$ svn status
$
</screen>

      <para>需要注意到提交之后，<command>svn
          status</command>显示工作拷贝已经没有锁定令牌了，这是<command>svn commit</command>的标准行为方式：它会遍历工作拷贝（或者从目标列表，如果有列表的话），并且作为提交的一部分发送所有遇到的锁定令牌到服务器。当提交完全成功，前面用到的所有版本库锁定都会被释放&mdash;<emphasis>即使是没有提交的文件。</emphasis>这样的原因是不鼓励用户滥用锁定，或者是长时间的保持锁定。例如，假定Harry不小心锁定了<filename>images</filename>目录的30个文件，因为他不确定要修改什么文件，他最后只修改了四个文件，当他运行<command>svn commit
          images</command>，会释放所有的30个锁定。
          </para>

      <para>自动释放锁定的特性可以通过<command>svn commit</command>的<option>--no-unlock</option>选项关闭，当你要提交文件，同时期望继续修改而必须保留锁定时非常有用。这个特性也可以半永久性的设定，方法是设置运行中<filename>config</filename>文件（见<xref
          linkend="svn.advanced.confarea"/>）的<literal>no-unlock = yes</literal>。
          </para>

      <para>当然，锁定一个文件不会强制一个人要提交修改，任何时候都可以通过运行<command>svn unlock</command>命令释放锁定：
      </para>

      <screen>
$ svn unlock banana.c
'banana.c' unlocked.
</screen>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.locking.discovery">
      <title>发现锁定</title>

      <para>最明显的方式就是因为锁定而不能提交一个文件，最简单的方式是<command>svn status --show-updates</command>：
      </para>

      <screen>
$ whoami
sally

$ svn status --show-updates
M              23   bar.c
M    O         32   raisin.jpg
       *       72   foo.h
Status against revision:     105
</screen>

      <para>在这个例子里，Sally可以见到不仅她的<filename>foo.h</filename>是过期的，而且发现两个计划要提交的文件被锁定了。<literal>O</literal>符号表示其他人所订了文件。如果她尝试提交，<filename>raisin.jpg</filename>的锁定会阻止她，Sally会纳闷谁锁定了文件，什么时候，为什么。再一次，<command>svn info</command>拥有答案：
     </para>

      <screen>
$ svn info http://svn.example.com/repos/project/raisin.jpg
Path: raisin.jpg
Name: raisin.jpg
URL: http://svn.example.com/repos/project/raisin.jpg
Repository UUID: edb2f264-5ef2-0310-a47a-87b0ce17a8ec
Revision: 105
Node Kind: file
Last Changed Author: sally
Last Changed Rev: 32
Last Changed Date: 2005-01-25 12:43:04 -0600 (Tue, 25 Jan 2005)
Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Lock Owner: harry
Lock Created: 2005-02-16 13:29:18 -0500 (Wed, 16 Feb 2005)
Lock Comment (1 line):
Need to make a quick tweak to this image.
</screen>

      <para>就像<command>svn info</command>可以检验工作拷贝的对象，它也可以检验版本库的对象，如果<command>svn info</command>的主要参数工作拷贝路径，所有工作拷贝的缓存信息都会显示，发现了锁定就意味着工作拷贝拥有锁定令牌（如果一个文件被另一个用户在另一个工作拷贝锁定，工作拷贝路径上运行<command>svn info</command>不会显示锁定信息）。如果<command>svn info</command>的主参数是URL，就会反映版本库中最新版本的对象信息，任何对锁定的提及描述了当前对象的锁定。
      </para>

      <para>所以在这个特定的例子里，Sally可以看到Harry在二月十六日为了<quote>做修改</quote>而锁定了这个文件，现在已经六月了，她怀疑他可能是忘记了这个锁定，她会打电话给Harry去询问他应该释放这个锁定，如果他不再，她就要自己强制解除这个锁定或者是找管理员去做。
     </para>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.locking.break-steal">
      <title>解除和偷窃锁定</title>

      <para>版本库锁定并不是神圣不可侵犯的，不只是创建者可以释放锁定，任何人都可以。当有其他人期望消灭锁定时，我们称之为<firstterm>打破</firstterm>锁定。
     </para>

      <para>从管理员的位子上很容易打破锁定，<command>svnlook</command>和<command>svnadmin</command>程序都有能力从版本库直接显示和删除锁定。（关于这些工具的信息可以看<xref linkend="svn.reposadmin.maint.tk"/>。）
     </para>

      <screen>
$ svnadmin lslocks /usr/local/svn/repos
Path: /project2/images/banana.jpg
UUID Token: opaquelocktoken:c32b4d88-e8fb-2310-abb3-153ff1236923
Owner: frank
Created: 2005-06-15 13:29:18 -0500 (Wed, 15 Jun 2005)
Expires: 
Comment (1 line):
Still improving the yellow color.

Path: /project/raisin.jpg
UUID Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Owner: harry
Created: 2005-02-16 13:29:18 -0500 (Wed, 16 Feb 2005)
Expires: 
Comment (1 line):
Need to make a quick tweak to this image.

$ svnadmin rmlocks /usr/local/svn/repos /project/raisin.jpg
Removed lock on '/project/raisin.jpg'.
</screen>

      <para>更有趣的选项是允许用户互相打破锁定，为此只需要使用unlock命令的<option>--force</option>选项：</para>

      <screen>
$ whoami
sally

$ svn status --show-updates
M              23   bar.c
M    O         32   raisin.jpg
       *       72   foo.h
Status against revision:     105

$ svn unlock raisin.jpg
svn: 'raisin.jpg' is not locked in this working copy

$ svn info raisin.jpg | grep URL
URL: http://svn.example.com/repos/project/raisin.jpg

$ svn unlock http://svn.example.com/repos/project/raisin.jpg
svn: Unlock request failed: 403 Forbidden (http://svn.example.com)

$ svn unlock --force http://svn.example.com/repos/project/raisin.jpg
'raisin.jpg' unlocked.
</screen>

      <para>Sally初始的unlock命令失败了，因为她直接在自己的工作拷贝上运行了<command>svn 
      unlock</command>，而这里没有锁定令牌。为了直接从版本库删除锁定，她需要给<command>svn 
      unlock</command>传递URL参数，她的这一次尝试又失败了，因为她不是锁定的拥有者（也没有锁定令牌）。当她使用了<option>--force</option>选项后，认证和授权的要求被忽略了，远程的锁定被打破了。
     </para>
        
      <para>当然，简单的打破锁定也许还不够，在这个例子里，Sally不仅想要打破Harry遗忘的锁定，她也希望自己重新锁定。她可以通过运行<command>svn unlock
        --force</command>紧接着<command>svn lock</command>，但是有可能有人在这两次命令之间锁定了文件，最简单的方式是<firstterm>窃取</firstterm>这个锁定，将打破和重新锁定变成一种原子操作，为此需要运行<command>svn lock</command>加<option>--force</option>选项：
        </para>

        <screen>
$ svn lock raisin.jpg
svn: Lock request failed: 423 Locked (http://svn.example.com)

$ svn lock --force raisin.jpg
'raisin.jpg' locked by user 'sally'.
</screen>

        <para>在任何情况下，无论锁定被打破还是窃取，Harry都会感到惊讶。Harry的工作拷贝还保留有原来的锁定令牌，但是锁定已经不存在了，锁定令牌可以说已经<firstterm>死掉了</firstterm>。锁定令牌指代的锁定被打破（版本库中不再存在）或者是窃取了（被另一个锁定代替了），任何一种情况下，Harry都可以使用<command>svn
          status</command>询问版本库：
          </para>

        <screen>
$ whoami
harry

$ svn status
     K raisin.jpg

$ svn status --show-updates
     B         32   raisin.jpg

$ svn update
  B  raisin.jpg

$ svn status

$
</screen>

        <para>如果版本库锁定被打破了，<command>svn
            status --show-updates</command>会在文件旁边显示一个<literal>B</literal> 
            (Broken)。如果有一个新的锁，就会显示一个<literal>T</literal> 
            (sTolen)符号。最终，<command>svn update</command>会注意到所有死掉的锁定并且把它们从工作拷贝中删除掉。
            </para>

        <sidebar>
          <title>锁定政策</title>
        
          <para>不同的系统有不同的锁定限制程度的观念。有些人认为锁定必须不顾任何代价的严格执行，只有原始的创建者和管理员可以释放。他们认为如果有人打破了锁定，混乱就会放任，锁定就完全失去了意义。这些人认为锁定是第一个和最首要的交流工具，如果用户经常的打破别人的锁定，代表了团队的文化失败和软件之外的问题。
         </para>

          <para>Subversion缺省是比较<quote>宽松的</quote>方式，但也允许管理员创建钩子脚本来建立严格的控制策略。具体来说，<filename>pre-lock</filename>和<filename>pre-unlock</filename>钩子允许管理员决定什么时候创建和释放锁定。根据锁定是否已经存在，这两个钩子脚本可以决定是否允许特定用户打破或窃取锁定。也有<filename>post-lock</filename>和<filename>post-unlock</filename>钩子，可以用来发送锁定动作的通知邮件。
         </para>

          <para>关于版本库钩子的更多信息可以看<xref 
          linkend="svn.reposadmin.create.hooks"/>。</para>
        </sidebar>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.locking.lock-communication">
      <title>锁定交流</title>

      <para>我们已经见到了如何利用<command>svn 
      lock</command>和<command>svn unlock</command>来创建、释放、打破和窃取锁定，这就满足了顺序访问文件的要求，但是浪费时间这个大问题该如何呢？</para>

      <para>例如，假定Harry锁定了一个图片，并开始编辑。同时，几英里之外的Sally希望做同样的工作，她没想到运行<command>svn status
        --show-updates</command>，她不知道Harry已经锁定了文件。她花费了数小时来修改文件，当她真被提交时发现文件已经被锁定或者是她的文件已经过期了。她的修改不能和Harry的合并，他们中的一人需要抛弃自己的工作，许多时间被浪费了。
        </para>
      
      <para>Subversion的策略是提供一种机制，能够提醒用户在开始编辑之前注意到文件可能已经锁定了。
      </para>

      <para>这个机制就是一种特殊的属性，<literal>svn:needs-lock</literal>。如果这个属性附加到一个文件上（属性值无关紧要），这个文件将只读，当用户锁定了文件，并且接受了锁定令牌，文件成了可读写。当锁定被释放&mdash;明确的解锁或提交后的释放&mdash;文件重新回到只读状态。
      </para>
      
      <para>根据这个原理，如果一个图像文件有这个属性，Sally就会立刻注意到打开的文件有些特别，她的程序将不能保存修改，或者（更好的情况）是告诉她文件只读，这提醒了她编辑之前需要锁定文件，这样她就发现了原来存在的锁定：
     </para>

      <screen>
$ /usr/local/bin/gimp raisin.jpg
gimp: error: file is read-only!

$ ls -l raisin.jpg
-r--r--r--   1 sally   sally   215589 Jun  8 19:23 raisin.jpg

$ svn lock raisin.jpg
svn: Lock request failed: 423 Locked (http://svn.example.com)

$ svn info http://svn.example.com/repos/project/raisin.jpg | grep Lock
Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Lock Owner: harry
Lock Created: 2005-06-08 07:29:18 -0500 (Thu, 08 June 2005)
Lock Comment (1 line):
Making some tweaks.  Locking for the next two hours.

</screen>

        <para>作为一个<quote>最佳实践</quote>，用户和管理员都应该给不能根据上下文的文件添加<literal>svn:needs-lock</literal>属性，这是鼓励好的锁定习惯和防止浪费的主要技术手段。
        </para>

        <para>需要注意到这个属性是依赖于锁定系统的交流工具，不管是否有这个属性，文件都可以锁定。相反的，无论有没有这个属性，并不会要求提交需要首先锁定文件。
       </para>

        <para>这个系统并不是毫无瑕疵，即使有这个属性，只读提醒也有可能失效。有些程序<quote>偷偷的篡改了</quote>文件的只读属性，悄无声息的允许用户编辑和保存文件，不幸的是，Subversion对此无能为力。
       </para>

    </sect2>

  </sect1>


  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.advanced.pegrevs">
    <title>Peg和实施修订版本</title>

    <para>文件和目录的拷贝、改名和移动能力使你可以创建一个项目，然后删除它，然后在同一个位置添加一个新的&mdash;这是在我们的计算机中经常发生的操作，我们认为这些功能都是必然有的，Subversion很高兴你也是这么认为的，Subversion的文件管理操作是这样的开放，提供了几乎和普通文件一样的操作版本化文件的灵活性，但是灵活意味着在整个版本库的生命周期中，一个给定的版本化的资源可能会出现在许多不同的路径，一个给定的路径会展示给我们许多完全不同的版本化资源。当然这些功能也增加了你与这些路径和资源交互的难度。
   </para>

    <para>Subversion可以非常聪明的注意到一个对象的版本历史变化包括一个<quote>地址改变</quote>，举个例子，如果你询问一个曾经上周改过名的文件的所有的日志信息，Subversion会很高兴提供所有的日志&mdash;重命名发生的修订版本，外加相关版本之前和之后的修订版本日志，所以大多数时间里，你不需要考虑这些事情，但是偶尔，Subversion会需要你的帮助来清除混淆。</para>

    <para>这个最简单的例子发生在当一个目录或者文件从版本控制中删除时，然后一个新的同样名字目录或者文件添加到版本控制，清除了你删除的东西，然后你添加的不是同样的东西，它们仅仅是有同样的路径，我们会把它叫做<filename>/trunk/object</filename>。什么，这意味着询问Subversion来查看<filename>/trunk/object</filename>的历史？你是询问当前这个位置的东西还是你在这个位置删除的那个对象？你是希望询问对这个对象的所有操作还是这个路径的所有对象？很明显，Subversion需要线索知道你真实的想法。
   </para>

    <para>由于移动，版本化资源历史会变得非常扭曲。举个例子，你会有一个目录叫做<filename>concept</filename>，保存了一些你用来试验的初生的软件项目，最终，这个项目变得足够成熟，说明这个注意确实需要一些翅膀了，所以你决定给这个项目一个名字。
      <footnote>
        <para><quote>你不是被期望去命名它，一旦你取了名字，你开始与之联系在一起。</quote> &mdash; Mike
          Wazowski</para>
      </footnote>
      假定你叫你的软件为Frabnaggilywort，此刻，有必要把你的目录命名为反映项目名称的名字，所以<filename>concept</filename>改名为<filename>frabnaggilywort</filename>。生活还在继续，Frabnaggilywort发布了1.0版本，并且被许多希望改进他们生活的分散用户天天使用。</para>
    
    <para>这是一个美好的故事，但是没有在这里结束，作为主办人，你一定想到了另一件事，所以你创建了一个目录叫做<filename>concept</filename>，周期重新开始。实际上，这个循环在几年里开始了多次，每一个想法从使用旧的<filename>concept</filename>目录开始，然后有时在想法成熟之后重新命名，有时你放弃了这个注意而删除了这个目录。或者更加变态一点，或许你把<filename>concept</filename>改成其他名字之后又因为一些原因重新改回<filename>concept</filename>。
   </para>

    <para>当这样的情景发生时，指导Subversion工作在重新使用的路径上的尝试就像指导一个芝加哥西郊的乘客驾车到东面的罗斯福路并且左转到主大道。仅仅20分钟，你可以穿过惠顿、格伦埃林何朗伯德的<quote>主大道</quote>，但是它们不是一样的街道，我们的乘客&mdash;和我们的Subversion&mdash;需要更多的细节来做正确的事情。</para>

    <para>在1.1版本，Subversion提供了一种方法来说明你所指是哪一个街道，叫做<firstterm>peg修订版本</firstterm>，这是一个提供给Subversion的一个区别一个独立历史线路的单独目的修订版本，因为一个版本化的文件会在任何时间占用某个路径&mdash;路径和peg修订版本的合并是可以指定一个历史的特定线路。Peg修订版本可以在Subversion命令行客户端中用<firstterm>at语法</firstterm>指定，之所以使用这个名称是因为会在关联的修订版本的路径后面追加一个<quote>at符号</quote>（<literal>@</literal>）。
      </para>

    <para>但是我们在本书多次提到的<option>--revision (-r)</option>到底是什么？修订版本（或者是修订版本集）叫做<firstterm>实施的修订版本</firstterm>（或者叫做<firstterm>实施的修订版本范围</firstterm>），一旦一个特定历史线路通过一个路径和peg修订版本指定，Subversion会使用实施的修订版本执行要求的操作。类似的，为了指出这个到我们芝加哥的道路，如果我们被告知到惠顿主大道606号， 
      <footnote>
        <para>伊利诺伊州惠顿主大道606号市惠顿离市中心，让它作为&mdash;<quote>历史中心</quote>？看起来是恰当的&hellip;。
       </para>
      </footnote>
      我们可以把<quote>主大道</quote>看作路径，把<quote>惠顿</quote>当作我们的peg修订版本。这两段信息确认了我们可以旅行（主大道的北方或南方）的唯一路径，也会保持我们不会在前前后后寻找目标时走到错误的主大道。现在我们把<quote>606 N.</quote>作为我们实施的修订版本，我们<emphasis>精确的</emphasis>知道到哪里。
      </para>

    <sidebar>
      <title>"peg-revision"算法</title>
      
      <para>当命令行客户端见到这样形式的命令：</para>

      <screen>
$ svn <replaceable>command</replaceable> -r <replaceable>OPERATIVE-REV</replaceable> item@<replaceable>PEG-REV</replaceable>
</screen>
      
      <para>...它会执行下面的算法：</para>

      <itemizedlist>
        
        <listitem>
          <para>来到修订版本<replaceable>PEG-REV</replaceable>，找到<replaceable>item</replaceable>，在版本库定位到一个唯一的对象。
          </para>
        </listitem>

        <listitem>
          <para>追踪对象的历史背景（通过任何可能的改名）来到修订版本<replaceable>OPERATIVE-REV</replaceable>的祖先。</para>
        </listitem>

        <listitem>
          <para>对那个祖先执行请求的动作，无论它的位置，无论它是什么名字。</para>
        </listitem>

      </itemizedlist>

      <para>记住，即使你不会明确的提供peg修订版本，它依然存在，缺省是工作拷贝条目的BASE和URL的HEAD。
      </para>
        
    </sidebar>

    <para>也就是说很久以前我们创建了我们的版本库，在修订版本1添加我们第一个<filename>concept</filename>目录，并且在这个目录增加一个<filename>IDEA</filename>文件与concept相关，在几个修订版本之后，真实的代码被添加和修改，我们在修订版本20，修改这个目录为<filename>frabnaggilywort</filename>。通过修订版本27，我们有了一个新的概念，所以一个新的<filename>concept</filename>目录用来保存这些东西，一个新的<filename>IDEA</filename>文件来描述这个概念，然后经过5年20000个修订版本，就像他们都有一个非常浪漫的历史。
   </para>

    <para>现在，一年之后，我们想知道<filename>IDEA</filename>在修订版本1时是什么样子，但是Subversion需要知道我们是想询问<emphasis>当前</emphasis>文件在修订版本1时的样子，还是希望知道<filename>concepts/IDEA</filename>在修订版本1时的那个文件？确定这些问题有不同的答案，并且因为peg修订版本，你可以用两种方式询问。为了知道当前的<filename>IDEA</filename>文件在旧版本1的样子，我们可以运行：</para>

    <screen>
$ svn cat -r 1 concept/IDEA 
subversion/libsvn_client/ra.c:775: (apr_err=20014)
svn: Unable to find repository location for 'concept/IDEA' in revision 1
</screen>

    <para>当然，在这个例子里，当前的<filename>IDEA</filename>文件在修订版本1中并不存在，所以Subversion给出一个错误，这个上面的命令是长的peg修订版本命令一个缩写，扩展的写法是：
   </para>

    <screen>
$ svn cat -r 1 concept/IDEA@BASE
subversion/libsvn_client/ra.c:775: (apr_err=20014)
svn: Unable to find repository location for 'concept/IDEA' in revision 1
</screen>

    <para>当执行时会有预料中的结果，当应用到工作拷贝路径时，Peg修订版本通常缺省值是<literal>BASE</literal>（在当前工作拷贝现在的修订版本），当应用到URL时，缺省值是<literal>HEAD</literal>。
    </para>

    <para>然后让我们询问另一个问题&mdash;在修订版本1 ，占据<filename>concepts/IDEA</filename>路径的文件的内容到底是什么？我们会使用一个明确的peg修订版本来帮助我们完成。
    </para>

    <screen>
$ svn cat concept/IDEA@1
The idea behind this project is to come up with a piece of software
that can frab a naggily wort.  Frabbing naggily worts is tricky
business, and doing it incorrectly can have serious ramifications, so
we need to employ over-the-top input validation and data verification
mechanisms.
</screen>

    <para>这看起来是正确的输出，这些文本甚至提到“frabbing naggily worts”，所以这就是现在叫做Frabnaggilywort项目的那个文件，实际上，我们可以使用显示的peg修订版本和实施修订版本的组合核实这一点。我们知道在<literal>HEAD</literal>，Frabnaggilywort项目坐落在<filename>frabnaggilywort</filename>目录，所以我们指定我们希望看到<literal>HEAD</literal>的<filename>frabnaggilywort/IDEA</filename>路经在历史上的修订版本1的内容。
    </para>

    <screen>
$ svn cat -r 1 frabnaggilywort/IDEA@HEAD
The idea behind this project is to come up with a piece of software
that can frab a naggily wort.  Frabbing naggily worts is tricky
business, and doing it incorrectly can have serious ramifications, so
we need to employ over-the-top input validation and data verification
mechanisms.
</screen>

    <para>而且peg修订版本和实施修订版本也不需要这样琐碎，举个例子，我们的<filename>frabnaggilywort</filename>已经在<literal>HEAD</literal>删除，但我们知道在修订版本20它是存在的，我们希望知道<filename>IDEA</filename>从修订版本4到10的区别，我们可以使用peg修订版本20和<filename>IDEA</filename>文件的修订版本20的URL的组合，然后使用4到10作为我们的实施修订版本范围。</para>

    <screen>
$ svn diff -r 4:10 http://svn.red-bean.com/projects/frabnaggilywort/IDEA@20
Index: frabnaggilywort/IDEA
===================================================================
--- frabnaggilywort/IDEA	(revision 4)
+++ frabnaggilywort/IDEA	(revision 10)
@@ -1,5 +1,5 @@
-The idea behind this project is to come up with a piece of software
-that can frab a naggily wort.  Frabbing naggily worts is tricky
-business, and doing it incorrectly can have serious ramifications, so
-we need to employ over-the-top input validation and data verification
-mechanisms.
+The idea behind this project is to come up with a piece of
+client-server software that can remotely frab a naggily wort.
+Frabbing naggily worts is tricky business, and doing it incorrectly
+can have serious ramifications, so we need to employ over-the-top
+input validation and data verification mechanisms.
</screen>

    <para>幸运的是，几乎所有的人不会面临如此复杂的情形，但是如果是，记住peg修订版本是帮助Subversion清除混淆的额外提示。</para>

  </sect1>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.advanced.externals">
    <title>外部定义</title>
    
    <para>有时候创建一个由多个不同检出得到的工作拷贝是非常有用的，举个例子，你或许希望不同的子目录来自不同的版本库位置，或者是不同的版本库。你可以手工设置这样一个工作拷贝&mdash;使用<command>svn checkout</command>来创建这种你需要的嵌套的工作拷贝结构。但是如果这个结构对所有的用户是很重要的，每个用户需要执行同样的检出操作。</para>

    <para>很幸运，Subversion提供了<firstterm>外部定义</firstterm>的支持，一个外部定义是一个本地路经到URL的影射&mdash;也有可能一个特定的修订版本&mdash;一些版本化的资源。在Subversion你可以使用<literal>svn:externals</literal>属性来定义外部定义，你可以用<command>svn propset</command>或<command>svn
      propedit</command>（见<xref linkend="svn.advanced.props.why"/>）创建和修改这个属性。它可以设置到任何版本化的路经，它的值是一个多行的子目录和完全有效的Subversion版本库URL的列表（相对于设置属性的版本化目录）。
      </para>

    <screen>
$ svn propget svn:externals calc
third-party/sounds             http://sounds.red-bean.com/repos
third-party/skins              http://skins.red-bean.com/repositories/skinproj
third-party/skins/toolkit -r21 http://svn.red-bean.com/repos/skin-maker
</screen>

    <para><literal>svn:externals</literal>的方便之处是这个属性设置到版本化的路径后，任何人可以从那个目录取出一个工作拷贝，同样得到外部定义的好处。换句话说，一旦一个人努力来定义这些嵌套的工作拷贝检出，其他任何人不需要再麻烦了&mdash;Subversion会在原先的工作拷贝检出之后，也会检出外部工作拷贝。</para>

    <para>注意前一个外部定义实例，当有人取出了一个<filename>calc</filename>目录的工作拷贝，Subversion会继续来取出外部定义的项目。</para>

    <screen>
$ svn checkout http://svn.example.com/repos/calc
A  calc
A  calc/Makefile
A  calc/integer.c
A  calc/button.c
Checked out revision 148.

Fetching external item into calc/third-party/sounds
A  calc/third-party/sounds/ding.ogg
A  calc/third-party/sounds/dong.ogg
A  calc/third-party/sounds/clang.ogg
&hellip;
A  calc/third-party/sounds/bang.ogg
A  calc/third-party/sounds/twang.ogg
Checked out revision 14.

Fetching external item into calc/third-party/skins
&hellip;
</screen>

    <para>如果你希望修改外部定义，你可以使用普通的属性修改子命令，当你提交一个<literal>svn:externals</literal>属性修改后，当你运行<command>svn update</command>时，Subversion会根据修改的外部定义同步检出的项目，同样的事情也会发生在别人更新他们的工作拷贝接受你的外部定义修改时。</para>

    <para><command>svn status</command>命令也认识外部定义，会为外部定义的子目录显示<literal>X</literal>状态码，然后迭代这些子目录来显示外部项目的子目录状态信息。</para>

<tip>
      <para>你一定要要考虑在所有的外部定义中使用明确的修订版本，这样做意味着你已经决定了何时拖出外部信息不同的快照，和精确的拖出哪个快照。除了不会受到第三方版本库的意外修改的影响以外，当你的工作拷贝回溯到以前的版本库时，使用明确的修订版本号会让外部定义回到以前的那个修订版本，也意味着外部定义的工作拷贝更新会匹配以前修订版本的样子。对于软件项目，这可能是编译复杂代码基的老快照成功和失败的区别。
     </para>
    </tip>
    <para>Subversion目前对外部定义的支持可能会引起误导，首先，一个外部定义只可以指向目录，而不是文件。第二，外部定义不可以指向相对路径（如<filename>../../skins/myskin</filename>）。第三，通过外部定义创建的工作拷贝与主工作拷贝没有连接，所以举个例子，如果你希望提交一个或多个外部定义的拷贝，你必须在这些工作拷贝显示的运行<command>svn commit</command>&mdash;对主工作拷贝的提交不会迭代到外部定义的部分。</para>

    <para>另外，因为定义本身使用绝对路径，移动和拷贝路径他们附着的路径不会影响他们作为外部的检出（尽管相对的本地目标子目录会这样，当然，根据重命名的目录）。这看起来有些迷惑&mdash;甚至让人沮丧&mdash;在特定情形。举个例子，如果你在<filename>/trunk</filename>开发线对一个目录使用外部定义，指向同一条线上的其他区域，然后使用<command>svn copy</command>把分支开发线拷贝到<filename>/branches/my-branch</filename>这个新位置，这个项目新分支的外部定义仍然指向<filename>/trunk</filename>版本化资源。另外，需要意识到如果你需要一个重新规划你的工作拷贝的父目录（使用<command>svn switch --relocate</command>），外部定义<emphasis>不</emphasis>会重新选择父目录。</para>

    <para>最后，你或许经常希望<command>svn</command>子命令不会识别或其它作为外部定义处理的结果的外部工作拷贝上的操作，在这种情况下，你可以对子命令使用<option>--ignore-externals</option>选项。
   </para>

  </sect1>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.advanced.vendorbr">
    <title>卖主分支</title>

    <para>当开发软件时有这样一个情况，你版本控制的数据可能关联于或者是依赖于其他人的数据，通常来讲，你的项目的需要会要求你自己的项目对外部实体提供的数据保持尽可能最新的版本，同时不会牺牲稳定性，这种情况总是会出现&mdash;只要某个小组的信息对另一个小组的信息有直接的影响。</para>
 
    <para>举个例子，软件开发者会工作在一个使用第三方库的应用，Subversion恰好是和Apache的Portable Runtime library（见<xref linkend="svn.developer.usingapi.apr" />）有这样一个关系。Subversion源代码依赖于APR库来实现可移植需求。在Subversion的早期开发阶段，项目紧密地追踪APR的API修改，经常在库代码的<quote>流血的边缘</quote>粘住，现在APR和Subversion都已经成熟了，Subversion只尝试同步APR的经过良好测试的，稳定的API库。</para>

    <para>现在，如果你的项目依赖于其他人的信息，有许多方法可以用来尝试同步你的信息，最痛苦的，你可以为项目所有的贡献者发布口头或书写的指导，告诉他们确信他们拥有你们的项目需要的特定版本的第三方信息。如果第三方信息是用Subversion版本库维护，你可以使用Subversion的外部定义来有效的<quote>强制</quote>特定的版本的信息在你的工作拷贝的的位置（见<xref linkend="svn.advanced.externals" />）。</para>

    <para>但是有时候，你希望在你自己的版本控制系统维护一个针对第三方数据的自定义修改，回到软件开发的例子，程序员为了他们自己的目的会需要修改第三方库，这些修改会包括新的功能和bug修正，在成为第三方工具官方发布之前，只是内部维护。或者这些修改永远不会传给库的维护者，只是作为满足软件开发需要的单独的自定义修改存在。</para>

    <para>现在你会面对一个有趣的情形，你的项目可以用某种脱节的样式保持它关于第三方数据自己的修改，如使用补丁文件或者是完全的可选版本的文件和目录。但是这很快会成为维护的头痛的事情，需要一种机制来应用你对第三方数据的自定义修改，并且迫使在第三方数据的后续版本重建这些修改。</para>

    <para>这个问题的解决方案是使用<firstterm>卖主分支</firstterm>，一个卖主分支是一个目录树保存了第三方实体或卖主的信息，每一个卖主数据的版本吸收到你的项目叫做<firstterm>卖主drop</firstterm>。</para>

    <para>卖主分支提供了两个关键的益处，第一，通过在我们的版本控制系统保存现在支持的卖主drop，你项目的成员不需要指导他们是否有了正确版本的卖主数据，他们只需要作为不同工作拷贝更新的一部份，简单的接受正确的版本就可以了。第二，因为数据存在于你自己的Subversion版本库，你可以在恰当的位置保存你的自定义修改&mdash;你不需要一个自动的（或者是更坏，手工的）方法来交换你的自定义行为。</para>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.vendorbr.general">
      <title>常规的卖主分支管理过程</title>

      <para>管理卖主分支通常会像这个样子，你创建一个顶级的目录（如<filename>/vendor</filename>）来保存卖主分支，然后你导入第三方的代码到你的子目录。然后你将拷贝这个子目录到主要的开发分支（例如<filename>/trunk</filename>）的适当位置。你一直在你的主要开发分支上做本地修改，当你的追踪的代码有了新版本，你会把带到卖主分支并且把它合并到你的<filename>/trunk</filename>，解决任何你的本地修改和他们的修改的冲突。</para>

      <para>也许一个例子有助于我们阐述这个算法，我们会使用这样一个场景，我们的开发团队正在开发一个计算器程序，与一个第三方的复杂数字运算库libcomplex关联。我们从卖主分支的初始创建开始，并且导入卖主drop，我们会把每株分支目录叫做<filename>libcomplex</filename>，我们的代码drop会进入到卖主分支的子目录<filename>current</filename>，并且因为<command>svn import</command>创建所有的需要的中间父目录，我们可以使用一个命令完成这一步。</para>

      <screen>
$ svn import /path/to/libcomplex-1.0 \
             http://svn.example.com/repos/vendor/libcomplex/current \
             -m 'importing initial 1.0 vendor drop'
&hellip;
</screen>
    
      <para>我们现在在<filename>/vendor/libcomplex/current</filename>有了libcomplex当前版本的代码，现在我们为那个版本作标签（见<xref linkend="svn.branchmerge.tags" />），然后拷贝它到主要开发分支，我们的拷贝会在<filename>calc</filename>项目目录创建一个新的目录<filename>libcomplex</filename>，它是这个我们将要进行自定义的卖主数据的拷贝版本。</para>
    
      <screen>
$ svn copy http://svn.example.com/repos/vendor/libcomplex/current  \
           http://svn.example.com/repos/vendor/libcomplex/1.0      \
           -m 'tagging libcomplex-1.0'
&hellip;
$ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0  \
           http://svn.example.com/repos/calc/libcomplex        \
           -m 'bringing libcomplex-1.0 into the main branch'
&hellip;
</screen>

      <para>我们取出我们项目的主分支&mdash;现在包括了第一个卖主drop的拷贝&mdash;我们开始自定义libcomplex的代码，我们知道，我们的libcomplex修改版本是已经与我们的计算器程序完全集成。
     
        <footnote>
          <para>而且完全没有bug，当然！</para>
        </footnote>
      </para>

      <para>几周之后，libcomplex得开发者发布了一个新的版本&mdash;版本1.1&mdash;包括了我们很需要的一些特性和功能。我们很希望升级到这个版本，但不希望失去在当前版本所作的修改。我们本质上会希望把我们当前基线版本是的libcomplex1.0的拷贝替换为libcomplex 1.1，然后把前面自定义的修改应用到新的版本。但是实际上我们通过一个相反的方向解决这个问题，应用libcomplex从版本1.0到1.1的修改到我们修改的拷贝。</para>
      
      <para>为了执行这个升级，我们取出一个我们卖主分支的拷贝，替换<filename>current</filename>目录为新的libcomplex 1.1的代码，我们只是拷贝新文件到存在的文件上，或者是解压缩libcomplex 1.1的打包文件到我们存在的文件和目录。此时的目标是让我们的<filename>current</filename>目录只保留libcomplex 1.1的代码，并且保证所有的代码在版本控制之下，哦，我们希望在最小的版本控制历史扰动下完成这件事。</para>

      <para>完成了这个从1.0到1.1的代码替换，<command>svn status</command>会显示文件的本地修改，或许也包括了一些未版本化或者丢失的文件，如果我们做了我们应该做的事情，未版本化的文件应该都是libcomplex在1.1新引入的文件&mdash;我们运行<command>svn add</command>来将它们加入到版本控制。丢失的文件是存在于1.1但是不是在1.1，在这些路径我们运行<command>svn delete</command>。最终一旦我们的<filename>current</filename>工作拷贝只是包括了libcomplex1.1的代码，我们可以提交这些改变目录和文件的修改。</para>

      <para>我们的<filename>current</filename>分支现在保存了新的卖主drop，我们为这个新的版本创建一个新的标签（就像我们为1.0版本drop所作的），然后合并这从个标签前一个版本的区别到主要开发分支。</para>

      <screen>
$ cd working-copies/calc
$ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0      \
            http://svn.example.com/repos/vendor/libcomplex/current  \
            libcomplex
&hellip; # resolve all the conflicts between their changes and our changes
$ svn commit -m 'merging libcomplex-1.1 into the main branch'
&hellip;
</screen>

      <para>在这个琐碎的用例里，第三方工具的新版本会从一个文件和目录的角度来看，就像前一个版本。没有任何libcomplex源文件会被删除、被改名或是移动到别的位置&mdash;新的版本只会保存针对上一个版本的文本修改。在完美世界，我们对呢修改会干净得应用到库的新版本，不会产生任何并发和冲突。</para>

      <para>但是事情总不是这样简单，实际上源文件在不同的版本间的移动是很常见的，这种过程复杂性可以确保我们的修改会一直对新的版本代码有效，可以很快使形势退化到我们需要在新版本手工的重新创建我们的自定义修改。一旦Subversion知道了给定文件的历史&mdash;包括了所有以前的位置&mdash;合并到新版本的进程就会很简单，但是我们需要负责告诉Subversion卖主drop之间源文件布局的改变。</para>

    </sect2>

    <!-- TODO: Try to clarify some of the steps for svn_load_dirs.pl
         (Garrett sez they've been "glossed over".  Also, consider
         another section on bypassing svn_load_dirs.pl altogether and
         running with just svn merge, now that it ignores ancestry. -->

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.vendorbr.svn_load_dirs">
      <title><command>svn_load_dirs.pl</command></title>

      <para>不仅仅包含一些删除、添加和移动的卖主drops使得升级第三方数据后续版本的过程变得复杂，所以Subversion提供了一个<command>svn_load_dirs.pl</command>脚本来辅助这个过程，这个脚本自动进行我们前面提到的常规卖主分支管理过程的导入步骤，从而使得错误最小化。你仍要负责使用合并命令合并第三方的新
版本数据合并到主要开发分支，但是<command>svn_load_dirs.pl</command>帮助你快速到达这一步骤。</para>

      <para>一句话，<command>svn_load_dirs.pl</command>是一个增强的<command>svn import</command>，具备了许多重要的特性：</para>

      <itemizedlist>
        <listitem>
          <para>它可以在任何有一个存在的版本库目录与一个外部的目录匹配时执行，会执行所有必要的添加和删除并且可以选则执行移动。</para>
        </listitem>
        <listitem>
          <para>它可以用来操作一系列复杂的操作，如那些需要一个中间媒介的提交&mdash;如在操作之前重命名一个文件或者目录两次。</para>
        </listitem>
        <listitem>
          <para>它可以随意的为新导入目录打上标签。</para>
        </listitem>
        <listitem>
          <para>它可以随意为符合正则表达式的文件和目录添加任意的属性。</para>
        </listitem>
      </itemizedlist>

      <para><command>svn_load_dirs.pl</command>利用三个强制的参数，第一个参数是Subversion工作的基本目录URL，第二个参数在URL之后&mdash;相对于第一个参数&mdash;指向当前的卖主分支将会导入的目录，最后，第三个参数是一个需要导入的本地目录，使用前面的例子，一个典型的<command>svn_load_dirs.pl</command>调用看起来如下：</para>

      <screen>
$ svn_load_dirs.pl http://svn.example.com/repos/vendor/libcomplex \
                   current                                        \
                   /path/to/libcomplex-1.1
&hellip;
</screen>

      <para>你可以说明你会希望<command>svn_load_dirs.pl</command>同时打上标签，这使用<option>-t</option>命令行选项，需要制定一个标签名。这个标签是第一个参数的一个相对URL。</para>

      <screen>
$ svn_load_dirs.pl -t libcomplex-1.1                              \
                   http://svn.example.com/repos/vendor/libcomplex \
                   current                                        \
                   /path/to/libcomplex-1.1
&hellip;
</screen>

      <para>当你运行<command>svn_load_dirs.pl</command>，它会检验你的存在的<quote>current</quote>卖主drop，并且与提议的新卖主drop比较，在这个琐碎的例子里，没有文件只出现在一个版本里，脚本执行新的导入而不会发生意外。然而如果版本之间有了文件布局的区别，<command>svn_load_dirs.pl</command>会询问你如何解决这个区别，例如你会有机会告诉脚本libcomplex版本1.0的<filename>math.c</filename>文件在1.1已经重命名为<filename>arithmetic.c</filename>，任何没有解释为移动的差异都会被看作是常规的添加和删除。</para>

      <para>这个脚本也接受单独配置文件用来为<emphasis>添加到</emphasis>版本库的文件和目录设置匹配正则表达式的属性。配置文件通过<command>svn_load_dirs.pl</command>的<option>-p</option>命令行选项指定，这个配置文件的每一行都是一个空白分割的两列或者四列值：一个Perl样式的正则表达式来匹配添加的路径、一个控制关键字（<literal>break</literal>或者是<literal>cont</literal>）和可选的属性名和值。</para>

      <screen>
\.png$              break   svn:mime-type   image/png
\.jpe?g$            break   svn:mime-type   image/jpeg
\.m3u$              cont    svn:mime-type   audio/x-mpegurl
\.m3u$              break   svn:eol-style   LF
.*                  break   svn:eol-style   native
</screen>

      <para>对每一个添加的路径，会按照顺序为匹配正则表达式的文件配置属性，除非控制标志是<literal>break</literal>（意味着不需要更多的路径匹配应用到这个路径）。如果控制说明是<literal>cont</literal>&mdash;<literal>continue</literal>的缩写&mdash;然后匹配工作会继续到配置文件的下一行。</para>

      <para>任何正则表达式，属性名或者属性值的空格必须使用单引号或者双银行环绕，你可以使用反斜杠（<literal>\</literal>）换码符来回避引号，反斜杠只会在解析配置文件时回避引号，所以不要保护对正则表达式不需要的其它字符。</para>

    </sect2>
  </sect1>

  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.advanced.l10n">
    <title>本地化</title>

    <para><firstterm>本地化</firstterm>是让程序按照地区特定方式运行的行为，如果一个程序的格式、数字或者是日期是你的本地方式，或者是打印的信息（或者是接受的输入）是你本地的语言，这个程序被叫做已经<firstterm>本地化了</firstterm>，这部分描述了针对本地化的Subversion的步骤。</para>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.l10n.understanding">
      <title>理解地区</title>

      <para>许多现代操作系统都有一个<quote>当前地区</quote>的概念&mdash;也就是本地化习惯服务的国家和地区。这些习惯&mdash;通常是被一些运行配置机制选择&mdash;影响程序展现数据的方式，也有接受用户输入的方式。</para>

      <para>在类Unix的系统，你可以运行<command>locale</command>命令来检查本地关联的运行配置的选项值：</para>

      <screen>
$ locale
LANG=
LC_COLLATE="C"
LC_CTYPE="C"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL="C"
</screen>

      <para>输出是一个本地相关的环境变量和它们的值，在这个例子里，所有的变量设置为缺省的<literal>C</literal>地区，但是用户可以设置这些变量为特定的国家/语言代码组合。举个例子，如果有人设置<literal>LC_TIME</literal>变量为<literal>fr_CA</literal>，然后程序会知道使用讲法语的加拿大期望的格式来显示时间和日期信息。如果一个人会设置<literal>LC_MESSAGES</literal>变量为<literal>zh_TW</literal>，程序会知道使用繁体中文显示可读信息。如果设置<literal>LC_ALL</literal>的效果同分别设置所有的位置变量为同一个值有相同的效果。<literal>LANG</literal>用来作为没有设置地区变量的缺省值，为了查看Unix系统所有的地区列表，运行<command>locale -a</command>命令。</para>

      <para>在Windows，地区配置是通过<quote>地区和语言选项</quote>控制面板管理的，可以从已存在的地区查看选择，甚至可以自定义（会是个很讨厌的复杂事情）许多显示格式习惯。</para>

    </sect2>

    <!-- =============================================================== -->
    <sect2 id="svn.advanced.l10n.svnuse">
      <title>Subversion对地区的支持</title>

      <para>Subversion客户端，<command>svn</command>通过两种方式支持当前的地区配置。首先，它会注意<literal>LC_MESSAGES</literal>的值，然后尝试使用特定的语言打印所有的信息，例如：</para>

      <screen>
$ export LC_MESSAGES=de_DE
$ svn help cat
cat: Gibt den Inhalt der angegebenen Dateien oder URLs aus.
Aufruf: cat ZIEL[@REV]...
&hellip;
</screen>

      <para>这个行为在Unix和Windows上同样工作，注意，尽管有时你的操作系统支持某个地区，Subversion客户端可能不能讲特定的语言。为了制作本地化信息，志愿者可以提供各种语言的翻译。翻译使用GNU gettext包编写，相关的翻译模块使用<filename>.mo</filename>作为后缀名。举个例子，德国翻译文件为<filename>de.mo</filename>。翻译文件安装到你的系统的某个位置，在Unix它们会在<filename>/usr/share/locale/</filename>，而在Windows它们通常会在Subversion安装的<filename>\share\locale\</filename>目录。一旦安装，一个命名在程序后面的模块会为此提供翻译。举个例子，<filename>de.mo</filename>会最终安装到<filename>/usr/share/locale/de/LC_MESSAGES/subversion.mo</filename>，通过查看安装的<filename>.mo</filename>文件，我们可以看到Subversion支持的语言。</para>

      <para>第二种支持地区设置的方式包括<command>svn</command>怎样解释你的输入，版本库使用UTF-8保存了所有的路径，文件名和日志信息。在这种情况下，版本库是<firstterm>国际化的</firstterm>&mdash;也就是版本库准备接受任何人类的语言。这意味着，无论如何Subversion客户端要负责发送UTF-8的文件名和日志信息到版本库，为此，必须将数据从本地位置转化为UTF-8。</para>

      <para>举个例子，你创建了一个文件叫做<filename>caffè.txt</filename>，然后提交了这个文件，你写的日志信息是<quote>Adesso il caffè è più forte</quote>，文件名和日志信息都包含非ASCII字符，但是因为你的位置设置为<literal>it_IT</literal>，Subversion知道把它们作为意大利语解释，在发送到版本库之前，它用一个意大利字符集转化数据为UTF-8。</para>

      <para>注意当版本库要求UTF-8文件名和日志信息时，它<emphasis>不会</emphasis>注意到文件的内容，Subversion会把文件内容看作字节串，没有任何客户端和服务器会尝试理解或是编码这些内容。</para>

      <sidebar>
        <title>字符集转换错误</title>

        <para>当使用Subversion，你或许会碰到一个字符集转化关联的错误：</para>

        <screen>
svn: Can't convert string from native encoding to 'UTF-8':
&hellip;
svn: Can't convert string from 'UTF-8' to native encoding:
&hellip;
</screen>
    
        <para>这个错误信息通常会发生在Subversion客户端从版本库接收到一个UTF-8串，但字符不能转化为当前的地区文字时，举个例子，如果你的地区设置是<literal>en_US</literal>，但是一个写作者使用日本文件名提交，你会在<command>svn
          update</command>接受文件时会看到这个错误。</para>

        <para>解决方案或者是设置你的地区为<emphasis>可以</emphasis>表示即将到来的UTF-8数据，或者是修改版本库的文件名或信息。（不要忘记和你的合作者拍拍手&mdash;项目必须首先决定通用的语言，这样所有的参与者会使用相同的地区设置。）</para>
      </sidebar>

    </sect2>

  </sect1>

<!-- ================================================================= -->
  <!-- ================================================================= -->
  <!-- ================================================================= -->
  <sect1 id="svn.advanced.externaldifftools">
    <title>使用外置区别工具</title>

    <para>选项<option>--diff-cmd</option>和<option>--diff3-cmd</option>的形式相似，也有类似名称的运行配置参数（见<xref
      linkend="svn.advanced.confarea.opts.config"/>），这会导致一个错误的观念，也就是在Subversion中使用外置的比较（或<quote>diff</quote>）和合并工具会非常的容易，虽然Subversion可以使用大多数类似的工具，但是设置这些工具绝非易事。
     </para>

    <para>Subversion和外置比较和合并工具的接口可以追溯到很久以前，当时Subversion的唯一文本比较能力是建立在GNU的工具链之上，特别是<command>diff</command>和<command>diff3</command>工具