属性 ====== Git通过属性文件为版本库中的文件或目录添加属性。设置了属性的文件或目录,\ 例如之前介绍换行符转换时设置了文本属性(\ ``text``\ )的文件,在执行Git\ 相关操作时会做特殊处理。 属性定义 --------- 属性文件是一个普通的文本文件,每一行对一个路径(可使用通配符)设置相应的\ 属性。语法格式如下: :: ... 其中路径由可以使用通配符的\ ````\ 定义,属性可以设置一个或多个,\ 不同的属性之间用空格分开。路径中通配符的用法和文件忽略\ (\ file:`.gitignore`\ )的语法格式相同,参见本书第2篇“第10.8节文件忽略”\ 相关内容。下面以\ ``text``\ 属性为例,介绍属性的不同写法: * text 直接通过属性名进行设置,相当于设置\ ``text``\ 属性的值为\ ``true``\ 。 对于设置了\ ``text``\ 属性的文件,不再需要Git对文件类型进行猜测,\ 而直接判定为文本文件并进行相应的换行符转换。 * -text 在属性名前用减号标识,相当于设置\ ``text``\ 属性值为\ ``false``\ 。 对于设置了取反\ ``text``\ 属性的文件,直接判定为二进制文件,在文件\ 检入和检出时不进行换行符转换。 * !text 在属性名前面添加感叹号,相当于该属性没有设置,即不等于\ ``true``\ ,\ 也不等于\ ``false``\ 。 对于未定义\ ``text``\ 属性的文件,根据Git是否配置了\ ``core.autocrlf``\ 配置变量,决定是否进行换行符转换。因此对于\ ``text``\ 属性没有定义和进\ 行取反\ ``text``\ 属性设置,两者存在差异。 * text=auto 属性除了上述\ ``true``\ 、\ ``false``\ 、未设置三个状态外,还可以对属\ 性用相关的枚举值(预定义的字符串)进行设置。不同的属性值可能有不同的\ 枚举值,对于\ ``text``\ 属性可以设置为\ ``auto``\ 。 对于\ ``text``\ 属性设置为\ ``auto``\ 的文件,文件类型实际上尚未确定,\ 需要Git读取文件内容进行智能判别,判别为文本文件则进行换行符转换。显然\ 当设置\ ``text``\ 属性为\ ``auto``\ 时,并不等同于\ ``true``\ 。 属性文件及优先级 ----------------- 属性文件可以以\ :file:`.gitattributes`\ 文件名保存在工作区目录中,提交到\ 版本库后就可以和其他用户共享项目文件的属性设置。属性文件也可以保存在工作\ 区之外,例如保存在文件\ :file:`.git/info/attributes`\ 中,仅对本版本库生\ 效,若保存在\ :file:`/etc/gitattributes`\ 文件中则全局生效。在查询某个工\ 作区某一文件的属性时,在不同位置的属性文件具有不同的优先级,Git依据下列\ 顺序依次访问属性文件。 * 文件\ :file:`.git/info/attributes`\ 具有最高的优先级。 * 接下来检查工作区同一目录下的\ :file:`.gitattributes`\ ,并依次向上递归\ 查找\ :file:`.gitattributes`\ 文件,直到工作区的根目录。 * 然后查询由Git的配置变量\ ``core.attributesfile``\ 指定的全局属性文件。 * 最后是系统属性文件,即文件\ :file:`$(prefix)/etc/gitattributes`\ 。不\ 同的Git安装方式这个文件的位置可能不同,但是该文件始终和Git的系统配置文件\ (可以通过\ :command:`git config --system -e`\ 命令打开进而知道位置)\ 位于同一目录中。 注意只有在1.7.4或更新版本的Git才提供后两种(全局和系统级的)属性文件。可\ 以通过下面的例子来理解属性文件的优先级和属性设置方法。 首先来看看某个版本库即系统中所包含的属性文件: * 其一是位于版本库中的文件\ :file:`.git/info/attributes`\ ,内容如下: :: a* foo !bar -baz * 其二是位于工作区子目录\ :file:`t`\ 下的属性文件,即\ :file:`t/.gitattributes`\ ,内容如下: :: ab* merge=filfre abc -foo -bar *.c frotz * 再一个是位于工作区根目录下的属性文件\ :file:`.gitattributes`\ ,内容如下: :: abc foo bar baz * 系统文件\ :file:`/etc/gitconfig`\ 中包含如下配置,则每个用户主目录下的\ :file:`.gitattributes`\ 文件做为全局属性文件。 :: [core] attributesfile = ~/.gitattributes * 位于用户主目录下的属性文件,即文件\ :file:`~/.gitattributes`\ 的内容如下: :: * text=auto 当查询工作区文件\ :file:`t/abc`\ 的属性时,根据属性文件的优先级,按照下\ 列顺序进行检索: 1. 先检查属性文件\ :file:`.git/info/attributes`\ 。显然该文件中唯一的一\ 行就和文件\ :file:`t/abc`\ 匹配,因此文件\ :file:`t/abc`\ 的属性如下: :: foo : true bar : 未设置 baz : false 2. 再检查和文件\ :file:`t/abc`\ 同目录的属性文件\ :file:`t/.gitattributes`\ 。\ 该属性文件的前两行和路径\ :file:`t/abc`\ 相匹配,但是因为第二行设置\ ``foo``\ 和\ ``bar``\ 属性已经由属性文件\ :file:`.git/info/attributes`\ 提供,因此第二行的设置不起作用。经过这一步,文件\ :file:`t/abc`\ 获得的属性为: :: foo : true bar : 未设置 baz : false merge : filfre 3. 然后沿工作区当前目录向上遍历属性文件,找到工作区根目录下的属性文件\ :file:`.gitattributes`\ ,进行检查。因为该属性文件设置的属性已经由\ 前面的属性文件提供,所以文件\ :file:`t/abc`\ 的属性和上面第2步的结果\ 一样。 4. 因为设置了\ ``core.attributesfile``\ 为\ :file:`~/.gitattributes`\ 文件,因此接下来查找用户主目录下文件即\ :file:`.gitattributes`\ 。\ 该文件唯一的一行匹配所有文件,因此\ :file:`t/abc`\ 又被附加了新的属性值\ ``text=auto``\ 。最终文件\ :file:`t/abc`\ 的属性如下。 :: foo : true bar : 未设置 baz : false merge : filfre text : auto 常用属性介绍 ------------- text ^^^^ 属性\ ``text``\ 用于显式的指定文件的类型:二进制(\ ``-text``\ )、文本\ 文件(\ ``text``\ )或是开启文件类型的智能判别(\ ``text=auto``\ )。对\ 于文本文件,Git会对其进行换行符转换。本书第40章“40.3换行符问题”中已经详\ 细介绍了属性\ ``text``\ 的用法,并且在本章“40.1.1 属性定义”的示例中对属\ 性\ ``text``\ 的取值做了总结,在此不再赘述。 在“40.3换行符问题”一节,我们还知道可以通过在Git配置文件中设置\ ``core.autocrlf``\ 配置变量,来开启Git对文件类型的智能判别,并对文本文件\ 开启换行符转换。那么Git的配置变量\ ``core.autocrlf``\ 和属性\ ``text``\ 有什么异同呢? 当设置了Git了配置变量\ ``core.autocrlf``\ 为\ ``true``\ 或者\ ``input``\ 后,相当于设置了属性\ ``text=auto``\ 。但是Git配置文件中的配置变量只能\ 在本地进行设置并且只对本地版本库有效,不能通过共享版本库传递到其他用户的\ 本地版本库中,因而\ ``core.autocrlf``\ 开启换行符转换不能跟其他用户共享,\ 或者说不能将换行符转换策略设置为整个项目(版本库)的强制规范。属性文件\ 则不同,可以被检入到版本库中并通过共享版本库传递给其他用户,因此可以通过\ 在检入的\ :file:`.gitattributes`\ 文件中设置\ ``text``\ 属性,或者干脆设\ 置\ ``text=auto``\ 属性,强制同一项目的所有用户在提交文本文件时都要规范\ 换行符。 建议所有存在跨平台开发可能的项目都在项目根目录中检入一个\ :file:`.gitattributes`\ 文件,根据文件扩展名设置文件的\ ``text``\ 属性,\ 或者设置即将介绍的\ ``eol``\ 属性。 eol ^^^ 属性\ ``eol``\ 用于设定文本文件的换行符格式。对于设置了\ ``eol``\ 属性的\ 文件,如果没有设定\ ``text``\ 属性时,默认会设置\ ``text``\ 属性为\ ``true``\ 。属性\ ``eol``\ 的取值如下: * eol=crlf 当文件检入版本库时,blob对象使用LF作为换行符。当检出到工作区时,使用\ CRLF作为换行符。 * eol=lf 当文件检入版本库时,blob对象使用LF作为换行符,检出的时候工作区文件也使\ 用LF作为换行符。 除了通过属性设定换行符格式外,还可以在Git的配置文件通过\ ``core.eol``\ 配置变量来设定。两者的区别在于配置文件中的\ ``core.eol``\ 配置变量设置的\ 换行符是一个默认值,没有通过\ ``eol``\ 属性指定换行符格式的文本文件会采\ 用\ ``core.eol``\ 的设置。变量\ ``core.eol``\ 的值可以设定为\ ``lf``\ 、\ \ ``crlf``\ 和\ ``native``\ 。默认\ ``core.eol``\ 的取值为\ ``native``\ ,\ 即采用操作系统标准的换行符格式。 下面的示例通过属性文件设置文件的换行符格式。 :: *.vcproj eol=crlf *.sh eol=lf 扩展名为\ ``.vcproj``\ 的文件使用CRLF作为换行符,而扩展名为\ ``.sh``\ 的文件使用LF作为换行符。在版本库中检入类似的属性文件,会使得Git客户端\ 无论在什么操作系统中都能够在工作区检出一致的换行符格式,这样无论是在\ Windows上还是在Linux上使用\ :command:`git archive`\ 命令将工作区文件打包,\ 导出的文件都会保持正确的换行符格式。 ident ^^^^^ 属性\ ``ident``\ 开启文本文件中的关键字扩展,即关键字\ ``$Id$``\ 的自动\ 扩展。当检出到工作区时,\ ``$Id$``\ 自动扩展为\ ``$Id:``\ ,后面紧接着40\ 位SHA1哈希值(相应blob对象的哈希值),然后以一个\ ``$``\ 字符结尾。当文\ 件检入时,要对内容中出现的以\ ``$Id:``\ 开始,以\ ``$``\ 结束的内容替换\ 为\ ``$Id$``\ 再保存到blob对象中。 这个功能可以说是对CVS相应功能的模仿。自动扩展的内容使用的是blob的哈希值\ 而非提交本身的哈希值,因此并无太大实际意义,不建议使用。如果希望在文本文\ 件中扩展出提交者姓名、提交ID等更有实际意义的内容,可以参照后面介绍的属性\ ``export-subst``\ 。 filter ^^^^^^ 属性\ ``filter``\ 为文件设置一个自定义转换过滤器,以便文件在检入版本库及\ 检出到工作区时进行相应的转换。定义转换过滤器通过Git配置文件来完成,因此\ 这个属性应该只在本地进行设置,而不要通过检入到版本库中的\ :file:`.gitattributes`\ 文件传递。 例如下面的属性文件设置了所有的C语言源文件在检入和检出的时候使用名为\ indent的代码格式化过滤器。 :: *.c filter=indent 然后还要通过Git配置文件设定indent过滤器,示例如下: :: [filter "indent"] clean = indent smudge = cat 定义过滤器只要设置两条命令,一条是名为clean的配置设定的的命令,用于在文\ 件检入时执行,另外一条是名为smudge的配置设定的命令,用于将文件检出到工作\ 区时使用的命令。对于本例,在代码检入时执行\ :command:`indent`\ 命令对代\ 码格式化后,再保存到版本库中。当检出到工作区执行\ :command:`cat`\ 命令,\ 实际上相当于直接将blob对象复制到工作区。 diff ^^^^ 和前面介绍的属性不同,属性\ ``diff``\ 不会对文件检入检出造成影响,而只是\ 在查看文件历史变更时起作用。属性\ ``diff``\ 可以取值如下: * diff 进行版本间比较时,以文本方式进行比较,即使文件看起来像是二进制文件\ (包含NULL字符),或者被设置为二进制文件(\ ``-text``\ )。 * -diff 不以文本方式进行差异比较,而以二进制方式进行比较。因为默认查看版本间差\ 异时只显示文本文件的差异不显示二进制文件差异,因此包含\ ``-diff``\ 属性\ 设置的文件在差异比较时不显示内容上的差异。对于有些文本文件(如postscript\ 文件)进行差异比较没有意义,可以对其设置\ ``-diff``\ 属性,避免在显示提\ 交间差异时造成干扰。 * !diff 不设置\ ``diff``\ 属性,相当于在执行差异比较时要对文件内容进行智能判别,\ 如果文件看起来像是文本文件,则显示文本格式的差异比较。 * diff= 设定一个外部的驱动用于文件的差异比较。例如对于Word文档的差异比较就可以\ 通过这种方式进行配置。 Word文档属于二进制文件,默认不显示差异比较。在Linux上有一个名为\ ``antiword``\ 的应用软件可以将Word文档转换为文本文件显示,借助该软件就\ 可以实现在Linux(包括Mac OS X)上显示Word文件的版本间差异。 下面的Git配置就定义了一个名为antiword的适用于Word差异比较的驱动: :: [diff "antiword"] textconv=antiword 其中\ ``textconv``\ 属性用于设定一个文件转换命令行,这里设置为\ ``antiword``\ ,用于将 Word 文档转换为纯文本。 然后还需要设置属性,修改版本库下的\ ``.git/info/attributes``\ 文件就可以,\ 新增属性设置如下: :: *.doc diff=antiword 关于更多的差异比较外部驱动的设置,执行\ ``git help --web attributes``\ 参见相关的帮助。 merge ^^^^^ 属性\ ``merge``\ 用于为文件设置指定的合并策略,受影响的Git命令有:\ :command:`git merge`\ 、\ :command:`git revert`\ 和\ :command:`git cherry-pick`\ 等。属性\ ``merge``\ 可以取值如下: * merge 使用内置的三向合并策略。 * -merge 将当前分支的文件版本设置为暂时的合并结果,并且声明合并发生了冲突,这实\ 际上是二进制文件默认的合并方式。可以对文本文件设置该属性,使得在合并时\ 的行为类似二进制文件。 * !merge 和定义了\ ``merge``\ 属性效果类似,使用内置的三向合并策略。然而当通过\ Git配置文件的\ ``merge.default``\ 配置变量设置了合并策略后,如果没有为\ 文件设置\ ``merge``\ 属性,则使用\ ``merge.default``\ 设定的策略。 * merge= 使用指定的合并驱动执行三向文件合并。驱动可以是内置的三个驱动,也可以是\ 用户通过Git配置文件自定义的驱动。 下面重点说一说通过枚举值来指定在合并时使用的内置驱动和自定义驱动。先来看\ 看Git提供的三个内置驱动: * merge=text 默认文本文件在进行三向合并时使用的驱动。会在合并后的文本文件中用特殊的\ 标识\ ``<<<<<<<``\ 、\ ``=======``\ 和\ ``>>>>>>>``\ 来标记冲突的内容。 * merge=binary 默认二进制文件在进行三向合并时使用的驱动。会在工作区中保持当前分支中的\ 版本不变,但是会通过在三个暂存区中进行冲突标识使得文件处于冲突状态。 * merge=union 在文本文件三向合并过程中,不使用冲突标志符标识冲突,而是将冲突双方的内\ 容简单的罗列在文件中。用户应该对合并后的文件进行检查。请慎用此合并驱动。 用户还可以自定义驱动。例如Topgit就使用自定义合并驱动的方式来控制两个\ Topgit管理文件\ :file:`.topmsg`\ 和\ :file:`.topdeps`\ 的合并行为。 Topgit会在版本库的配置文件\ :file:`.git/info/config`\ 中添加下面的设置定\ 义一个名为ours的合并驱动。注意不要将此ours驱动和本书第3篇第16章\ “16.6合并策略”一节中介绍的ours合并策略弄混淆。 :: [merge "ours"] name = \"always keep ours\" merge driver driver = touch %A 定义的合并驱动的名称由\ ``merge.*.name``\ 给出,合并时执行的命令则由配置\ ``merge.*.driver``\ 给出。本例中使用了命令\ :command:`touch %A`\ ,含义\ 为对当前分支中的文件进行简单的触碰(更新文件时间戳),亦即合并冲突时采用\ 本地版本,丢弃其他版本。 Topgit还会在版本库\ :file:`.git/info/attributes`\ 属性文件中包含下面的属\ 性设置: :: .topmsg merge=ours .topdeps merge=ours 含义为对这两个Topgit管理文件,采用在Git配置文件中设定的ours合并驱动。\ Topgit之所以要这么实现是因为不同特性分支的管理文件之间并无关联,也不需要\ 合并,在遇到冲突时只使用自己的版本即可。这对于Topgit要经常地执行变基和\ 分支合并来说,设置这个策略可以简化管理,但是这个合并设置在特定情况下也\ 存在不合理之处。例如两个用户工作在同一分支上同时更改了\ :file:`.topmsg`\ 文件以修改特性分支的描述,在合并时会覆盖对方的修改,这显然是不好的行为。\ 但是权衡利弊,还是如此实现最好。 whitespace ^^^^^^^^^^ Git可以对文本文件中空白字符的使用是否规范做出检查,在文件差异比较时,将\ 使用不当的空白字符用红色进行标记(开启\ ``color.diff.whitespace``\ )。\ 也可以在执行\ :command:`git apply`\ 时通过参数\ ``--whitespace=error``\ 防止错误的空白字符应用到提交中。 Git默认开启对下面三类错误空白字符的检查。 * blank-at-eol 在行尾出现的空白字符(换行符之前)被视为误用。 * space-before-tab 在行首缩进中出现在TAB字符前面的空白字符视为误用。 * blank-at-eof 在文件末尾的空白行视为误用。 Git还支持对更多空白字符的误用做出检测,包括: * indent-with-non-tab 用8个或者更多的空格进行缩进视为误用。 * tab-in-indent 在行首的缩进中使用TAB字符视为误用。显然这个设置和上面的\ ``indent-with-non-tab``\ 互斥。 * trailing-space 相当于同时启用\ ``blank-at-eol``\ 和\ ``blank-at-eof``\ 。 * cr-at-eol 将行尾的CR(回车)字符视为换行符的一部分。也就是说,在行尾前出现的CR\ 字符不会引起\ ``trailing-space``\ 报错。 * tabwidth= 设置一个TAB字符相当于几个空格,缺省为8个。 可以通过Git配置文件中的\ ``core.whitespace``\ 配置变量,设置开启更多的\ 空白字符检查,将要开启的空白字符检查项用逗号分开即可。 如果希望对特定路径进行空白字符检查,则可以通过属性\ ``whitespace``\ 进行。属性\ ``whitespace``\ 可以有如下设置: * whitespace 开启所有的空白字符误用检查。 * -whitespace 不对空白字符进行误用检查。 * !whitespace 使用\ ``core.whitespace``\ 配置变量的设置进行空白字符误用检查。 * whitespace=... 和\ ``core.whitespace``\ 的语法一样,用逗号分隔各个空白字符检查项。 export-ignore ^^^^^^^^^^^^^^ 设置了该属性的文件和目录在执行\ :command:`git archive`\ 时不予导出。 export-subst ^^^^^^^^^^^^ 如果为文件设置了属性\ ``export-subst``\ ,则在使用\ :command:`git archive`\ 导出项目文件时,会对相应文件内容中的占位符展开,然后再添加到归档中。注意\ 如果在使用\ :command:`git archive`\ 导出时使用树ID,而没有使用提交或者\ 里程碑,则不会发生占位符展开。 占位符的格式为\ ``$Format:PLACEHOLDERS$``\ ,其中\ ``PLACEHOLDERS``\ 使用\ :command:`git log --pretty=format:`\ 相同的参数(具体参见\ :command:`git help log`\ 显示的帮助页)。例如:\ ``$Format:%H$``\ 将展开为提交的哈希值,\ ``$Format:%an$``\ 将展开为提交者姓名。 delta ^^^^^^ 如果设置属性\ ``delta``\ 为\ ``false``\ ,则不对该路径指向的blob文件执行\ Delta压缩。 encoding ^^^^^^^^^ 设置文件所使用的字符集,以便使用GUI工具(如\ ``gitk``\ 和\ ``git-gui``\ )能够正确显示文件内容。基于性能上的考虑,\ ``gitk``\ 默认不检查该属性,\ 除非通过\ ``gitk``\ 的偏好设置启用“Support per-file encodings”。 如果没有为文件设置\ ``encoding``\ 属性,则使用\ ``git.encoding``\ 配置变量。 binary ^^^^^^^ 属性\ ``binary``\ 严格来说是一个宏,相当于\ ``-text -diff``\ 。即禁止换\ 行符转换及禁止文本方式显示文件差异。 用户也可以自定义宏。自定义宏只能在工作区根目录中的\ :file:`.gitattributes`\ 文件中添加,以内置的\ ``binary``\ 宏为例,相当于在属性文件中进行了如下的设置: :: [attr]binary -diff -text