属性

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

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

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

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

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

现在考虑使用Subversion文件的属性来做相同的设置,想象我们有一个单独的图像文件image123.jpg,然后这个文件的属性集包括captiondatestamp甚至thumbnail。现在你的工作拷贝目录看起来更容易管理—实际上,它看起来只有图像文件,但是你的自动化脚本知道得更多,它们知道可以用svn(更好的选择是使用Subversion的语言绑定—见“使用C和C++以外的语言”一节)来挖掘更多的站点显示需要的额外信息,而不必去阅读一个索引文件或者是玩一个路径处理的游戏。

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

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

$ svn propset copyright '(c) 2003 Red-Bean Software' calc/button.c
property 'copyright' set on 'calc/button.c'
$

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

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

作为propset命令的补充,svn提供了一个propedit命令,这个命令使用定制的编辑器程序(见“config”一节)来添加和修改属性。当你运行这个命令,svn调用你的编辑器程序打开一个临时文件,文件中保存当前的属性值(或者是空文件,如果你正在添加新的属性)。然后你只需要修改为你想要的值,保存临时文件,然后离开编辑器程序。如果Subversion发现你已经修改了属性值,就会接受新值,如果你未作任何修改而离开,不会产生属性修改操作。

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

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

$ 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'
…
$

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

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

还有一个proplist变种命令会列出所有属性的名称和值,只需要设置--verbose-v)选项。

$ 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.
…

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

$ 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 : 
$

你需要用propdel来删除属性,语法与其它与属性相关命令相似:

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

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

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

$ 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

$

注意status子命令显示的M在第二列而不是在第一列,这是因为我们修改了calc/button.c的属性,而不是它的文本内容,如果我们都修改了,我们也会看到M出现在第一列(见svn status”一节)。

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

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

Subversion没有关于属性的特殊政策—你可以通过它们实现自己的目的。Subversion只是要求你不要使用svn:开头的命名空间作为属性名,这是Subversion自己使用的命名空间。实际上,Subversion定义了某些特殊的属性,这些属性对它们所附加的文件和目录有特殊的影响。在本小节,我们会解开这个谜团,并且描述这些属性怎样让你的生活更加容易。

这个svn:ignore属性保存了一个Subversion特定操作忽略的文件模式列表,或许这个是最常用的属性,它可以与global-ignores运行配置选项配合使用(见“config”一节)来过滤svn statussvn addsvn import命令中操作的未版本化文件。

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

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

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

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

为了这个目的,svn:ignore属性是解决方案,它的值是一个多行的文件模式集,一行一个模式,这个属性已经设置到这个你希望应用模式的目录。 [29] 举个例子,你的svn status有如下的输出:

$ 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

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

calculator
debug_log*

当你添加完这些属性,你会在calc目录有一个本地修改,但是注意你的svn status输出有什么其他的不同:

$ svn status
 M     calc
 M     calc/button.c
?      calc/data.c

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

如果想查看被忽略的文件,可以设置Subversion的--no-ignore选项:

$ 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

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

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

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

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

Subversion定义了用来替换的关键字列表,这个列表保存了如下五个关键字,有一些也包括了可用的别名:

只在你的文件增加关键字anchor不会做什么特别的事情,Subversion不会尝试对你的文件内容执行文本替换,除非明确的被告知这样做,毕竟,你可以撰写一个文档 [31] 关于如何使用关键字,你希望Subversion不会替代你漂亮的关于不需要替换的关键字anchor实例!

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

举个例子,假定你有一个版本化的文件weather.txt,内容如下:

Here is the latest report from the front lines.
$LastChangedDate$
$Rev$
Cumulus clouds are appearing more frequently as summer approaches.

当没有svn:keywords属性设置到这个文件,Subversion不会有任何特别操作,现在让我们允许LastChangedDate关键字的替换。

$ svn propset svn:keywords "Date Author" weather.txt
property 'svn:keywords' set on 'weather.txt'
$

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

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

注意我们设置svn:keywords属性为"Date Author",关键字anchor使用别名$LastChangedDate$并且正确的扩展。

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.

如果有其他人提交了weather.txt的修改,你的此文件的拷贝还会显示同样的替换关键字值—直到你更新你的工作拷贝,此时你的weather.txt重的关键字将会被替换来反映最新的提交信息。

不像我们说过的版本化文件的svn:mime-type属性,Subversion假定这个文件保存了可读的数据,一般来讲,Subversion因为这个属性来判断一个文件是否可以用上下文区别报告,否则,对Subversion来说只是字节。

这意味着缺省情况下,Subversion不会关注任何行结束标记(end-of-line,EOL),不幸的是不同的操作系统在文本文件使用不同的行结束标志,举个例子,Windows平台下的A编辑工具使用一对SCII控制字符—回车(CR)和一个移行(LF)。Unix软件,只使用一个LF来表示一个行的结束。

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

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

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

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

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

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

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

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



[27] 修正提交日志信息的拼写错误,文法错误和“简单的错误”是--revprop选项最常见用例。

[28] Windows文件系统使用文件扩展名(如.EXE.BAT.COM)来标示可执行文件。

[29] 这个模式对那个目录是严格的—不会迭代的应用到子目录。

[30] 这不是编译系统的基本功能吗?

[31] … 或者可能是一本书的一个小节 …