忽略未被版本控制的项

优质
小牛编辑
119浏览
2023-12-01

在一个长时间使用的工作副本里, 除了被版本控制的文件和目录外, 常常 还有很多未被版本控制的文件与目录, 而且它们将来也不会被添加到仓库里, 这 些文件可能是文本编辑器的备份文件, 或编译器产生的目标文件, 对它们进行版 本控制是没有意义的, 用户随时都有可能把它们删除.

希望工作副本不受这些杂质影响是不可能的. 实际上这是 Subversion 的一个 特性, 那就是对操作系统来说工作副本就是一个普通的目录, 与未被版本化的 目录相比并没有本质上的区别. 不过工作副本里的未被版本化的文件和目录会给 用户产生一定的困扰. 比如说, 命令 svn addsvn import 默认会递归地执行, 命令并不知道目录中的哪些 文件是用户想要的, 哪些是不想要的. 命令 svn status 默认报告工作副本里的每一个项目的状态—包括未被版本控制的文件与目 录—如果未被版本控制的项目很多, 命令的输出就比较扰人.

于是, Subversion 提供了几种方式告诉 Subversion 哪些文件是可以忽略的. 其中一种要用到 Subversion 的运行时配置系统 (见 “运行时配置区域”一节), 会受到配置影响的通常是在特定 计算机上执行的 Subversion 操作, 或计算机上的某些特定用户. 另外两种方式 用到了 Subversion 的目录属性, 与版本化目录的联系更为紧密, 因此它会影响 到版本化目录的所有工作副本. 上面说的两种机制都会用到 文件模式 (file patterns) (用于匹配文件名的字符串, 包含了字面字符与通配符) 来决定应该忽略哪些 文件.

Subversion 运行时配置系统提供了一个选项— global-ignores—选项的值是空白符分隔的文件名模式集. 如果文件的名字 与集合中的某个模式匹配, 那这个文件对 Subversion 来说相当于是不存在的, 命令 svn add, svn importsvn status 就会忽略它. 如果工作副本里有永远不会 被版本控制的文件 (比如 Emacs 的备份文件 *~.*~), 这个特性就会非常有用.

Subversion 的文件名模式

文件名模式 (也叫作 globsshell 通配符模式 (shell wildcard patterns )) 是一个字符串, 这个字符串用于匹配一个文件名, 比较常见的 用法是从一大堆文件中, 选出具有类似性质的子集, 而不用列出每个文件名字. 模式中的字符分为两种: 普通字符—按照字面值进行匹配, 例如字母 a 就是匹配字母 a; 通配符— 在匹配时被解释成和字面值不同的含义.

文件名模式的语法有很多种, Subversion 用的是 Unix 系统中最常见的一 种语法, 这种语法被实现成系统函数 fnmatch. 下面简 单介绍该语法支持的通配符:

?

匹配任意 一个 字符

*

匹配 0 个或多个字符组成的字符串

[

开始定义一个字符类, ] 表示定义结束, 字 符类可以匹配任意一个类中的字符

Unix shell 支持相同的文件名模式语法, 下面是 shell 的一些使用例子:

$ ls   ### the book sources
appa-quickstart.xml ch06-server-configuration.xml
appb-svn-for-cvs-users.xml      ch07-customizing-svn.xml
appc-webdav.xml     ch08-embedding-svn.xml
book.xmlch09-reference.xml
ch00-preface.xml    ch10-world-peace-thru-svn.xml
ch01-fundamental-concepts.xml   copyright.xml
ch02-basic-usage.xmlforeword.xml
ch03-advanced-topics.xml        images/
ch04-branching-and-merging.xml  index.xml
ch05-repository-admin.xml       styles.css
$ ls ch*   ### the book chapters
ch00-preface.xml    ch06-server-configuration.xml
ch01-fundamental-concepts.xml   ch07-customizing-svn.xml
ch02-basic-usage.xmlch08-embedding-svn.xml
ch03-advanced-topics.xml        ch09-reference.xml
ch04-branching-and-merging.xml  ch10-world-peace-thru-svn.xml
ch05-repository-admin.xml
$ ls ch?0-*   ### the book chapters whose numbers end in zero
ch00-preface.xml  ch10-world-peace-thru-svn.xml
$ ls ch0[3578]-*   ### the book chapters that Mike is responsible for
ch03-advanced-topics.xml   ch07-customizing-svn.xml
ch05-repository-admin.xml  ch08-embedding-svn.xml
$

完整的文件名模式比我们这里介绍的要更加复杂, 但是对大多数 Subversion 用户来说, 这里介绍的基本用法已经足够了.

如果被版本控制的目录上设置了属性 svn:ignore, 属性值应该是一个文件名模式列表, 各项之间用换行符分开, Subversion 根据 文件名模式列表判断 相同 目录内的哪些文件是可以忽略 的. 属性 svn:ignore 不会覆盖运行时配置选项 global-ignores 的值, 而是作为一种补充. 与 global-ignores 不同的是, 属性 svn:ignore 里的模式只能作用在该属性所在的目录上, 不会递归作用到子目录上. 属性 svn:ignore 的一个常用目的是告诉 Subversion 去忽略每个 用户的工作副本中可能都会有的文件, 例如编译器的输出文件—对于本书而 言, 就是 HTML, PDF, PostScript 文件, 或其他 DocBook XML 转换过程中产生 的临时文件和输出文件.

Subversion 1.8 提供了一个比 svn:ignore 更强大的 属性—svn:global-ignores. 和 svn:ignore 相同的是, svn:global-ignores 只能设置到目录上, 属性值是文件名模式集合.[20] svn:global-ignores 定义的文件名模式会添加到运行时配置选项 global-ignores 与 属性 svn:ignore 定义的模式上. 与 svn:ignore 不同的是, svn:global-ignores 是可继承的 [21], 它会递归地作用到目录内的 所有 路径上, 而不仅仅是目录的直接子文件.

[注意]注意

Subversion 的忽略文件名模式只对未被版本控制的文件和目录起作用, 一旦 文件和目录被版本控制了, 忽略文件名模式就不会对它们产生影响. 也就是说, 不要以为某个被版本控制的文件名符合忽略模式, 在你提交时, Subversion 就 会忽略它的修改—Subversion 总是 会注意到所有 被版本控制的对象.

CVS 用户的忽略模式

Subversion 的 svn:ignore 属性的语法和功能非常 像 CVS .cvsignore 文件. 实际上, 如果用户想从 CVS 的工作副本迁移到 Subversion 的工作副本中, 为了迁移忽略模式, 可以把 .cvsignore 作为命令 svn propset 的输入:

$ svn propset svn:ignore -F .cvsignore .
property 'svn:ignore' set on '.'
$

但是, CVS 和 Subversion 在忽略模式的使用方式上有所区别. 两个系统 使用忽略模式的时机不同, 在忽略模式的作用目标上也有细微的差别, 另外, Subversion 无法识别 ! 的特殊意义, 对 CVS 来说, ! 表示不要忽略这种模式.

运行时配置选项 global-ignores 里的忽略模式更 倾向于个人化 [22], 并且和工作副本相比, 更贴近用户的个人需求. 所以, 本节的余 下部分主要关注 svn:ignore, svn:global-ignores 及如何使用它们.

假设某个工作副本的 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, 但是 工作副本里还有一些未被版本控制的项目: 刚从源代码编译出的 calculator 程序, 一个叫做 data.c 的 源代码文件, 还有几个用于调试的日志文件. 假设用户已经知道编译系统总是会 输出一个目标文件 calculator [23], 而且测试程序总是会留下一些 调试日志文件, 除了用户自己的工作副本, 该项目所有的工作副本都有可能出现 这些文件. 用户非常清楚地知道, 当他执行 svn status 时, 并不想看到这些他不感兴趣的文件, 而且他也相信其他人也对它们不感兴趣. 于是, 用户决定为目录 calc 设置属性 svn:ignore:

$ svn propget svn:ignore calc
calculator
debug_log*
$

属性设置完毕后, 目录 calc 包含了未被提交的本地 修改. 注意看 svn status 的输出发生了什么变化:

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

现在, 命令的输出变得干净多了! 编辑器产生的目标文件 calculator 和日志文件仍然留在工作副本里, Subversion 只是不 再提醒用户这些文件的存在. 输出变干净后, 用户就能更容易地关注到更重要的 事情上—例如用户可能忘记把源代码文件 data.c 添加到仓库里.

当然, 减少垃圾信息只是一个选择, 如果用户确实想看到所有的文件, 包括 正常情况下会被忽略的文件, 可以给 svn status 加上选项 --no-ignore:

$ svn status --no-ignore
 M      calc
 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
I       calc/wip.1.diff

被隐藏的未被版本控制的项目再度显示出来, 但是在项目的左边加上了字母 I (Ignored). 请等一下, 为什么 wip.1.diff 也有 I? calc 的属性 svn:ignore 里并没有匹配 wip.1.diff 的模式, 那么它为什么会被忽略?[24] 答案是继承的属性 svn:global-ignores. 执行带上选项 --show-inherited-props 的命令 svn propget , 就可以看到属性 svn:global-ignores 被设置 在了工作副本的根目录上, 果然在这个属性里找到了匹配 wip.1.diff 的模式:

$ svn pg svn:global-ignores calc -v --show-inherited-props
Inherited properties on 'calc',
from '.':
  svn:global-ignores
    *.diff
    *.patch

之前提过, svn addsvn import 也会用到忽略模式列表, 这两个操作都会要求 Subversion 开始管理文件与目录. 在递归的添加操作或导入操作中, Subversion 不会要求用户去选择目录 中的哪些文件应该被版本控制, 而是使用忽略模式—包括全局的, 每个目录 与继承的—来决定哪些文件应该被忽略. 同样, 用户也可以用选项 --no-ignore 告诉 Subversion 不会忽略任意一个文件.

[提示]提示

即使已经设置了属性 svn:ignoresvn:global-ignores, 使用 shell 的通配符可能还是会产生一些 问题. 在 Subversion 接收到参数之前, shell 会把通配符扩展成显式的文件 名列表, 所以执行 svn SUBCOMMAND * 相当于执行 svn SUBCOMMAND file1 file2 file3 …. 如果是 svn add, 效果就像是给命令带上选项 --no-ignore, 所以最好使用 svn add --force . 完成文件的批量添加. 显式地指定目标文件确保了当前目录不 会因为已经处于版本控制之下而被忽略, 选项 --force 使得 Subversion 在遍历目录, 添加未被版本控制的文件时, 仍然遵循属性 svn:ignore, svn:global-ignores 和运行时配置选项 global-ignores 的值. 如果你不想 递归地添加所有项目, 别忘了给命令 svn add 加上选项 --depth files.



[20] svn:global-ignores 的各个文件名模式之间可能用空白符 分隔 (就像运行时配置选项 global-ignores), 而不仅 仅是换行符 (属性 svn:ignore 的各个文件名模式之间 只能用换行符分隔).

[21] 当然, 只 有 1.8 或更新版本的 Subversion 客户端才能认识 svn:global-ignores 的意义与可继承性

[22] 抛开个人化不说, 如果用户没有显式地设置 global-ignores, Subversion 就会使用默认值, 见 “通用配置选项”一节

[23] 这不就是构建系统存在的意义吗?

[24] 假设用户的运行时配置选项 global-ignores 里也没有匹配的模式