Git通过属性文件为版本库中的文件或目录添加属性。设置了属性的文件或目录,例如之前介绍换行符转换时设置了文本属性(text)的文件,在执行Git相关操作时会做特殊处理。
属性文件是一个普通的文本文件,每一行对一个路径(可使用通配符)设置相应的属性。语法格式如下:
<pattern> <attr1> <attr2> ...
其中路径由可以使用通配符的<pattern>定义,属性可以设置一个或多个,不同的属性之间用空格分开。路径中通配符的用法和文件忽略(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。
属性文件可以以.gitattributes文件名保存在工作区目录中,提交到版本库后就可以和其他用户共享项目文件的属性设置。属性文件也可以保存在工作区之外,例如保存在文件.git/info/attributes中,仅对本版本库生效,若保存在/etc/gitattributes文件中则全局生效。在查询某个工作区某一文件的属性时,在不同位置的属性文件具有不同的优先级,Git依据下列顺序依次访问属性文件。
注意只有在1.7.4或更新版本的Git才提供后两种(全局和系统级的)属性文件。可以通过下面的例子来理解属性文件的优先级和属性设置方法。
首先来看看某个版本库即系统中所包含的属性文件:
其一是位于版本库中的文件.git/info/attributes,内容如下:
a* foo !bar -baz
其二是位于工作区子目录t下的属性文件,即t/.gitattributes,内容如下:
ab* merge=filfre
abc -foo -bar
*.c frotz
再一个是位于工作区根目录下的属性文件.gitattributes,内容如下:
abc foo bar baz
系统文件/etc/gitconfig中包含如下配置,则每个用户主目录下的.gitattributes文件做为全局属性文件。
[core]
attributesfile = ~/.gitattributes
位于用户主目录下的属性文件,即文件~/.gitattributes的内容如下:
* text=auto
当查询工作区文件t/abc的属性时,根据属性文件的优先级,按照下列顺序进行检索:
foo : true bar : 未设置 baz : false
foo : true bar : 未设置 baz : false merge : filfre
foo : true bar : 未设置 baz : false merge : filfre text : auto
属性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开启换行符转换不能跟其他用户共享,或者说不能将换行符转换策略设置为整个项目(版本库)的强制规范。属性文件则不同,可以被检入到版本库中并通过共享版本库传递给其他用户,因此可以通过在检入的.gitattributes文件中设置text属性,或者干脆设置text=auto属性,强制同一项目的所有用户在提交文本文件时都要规范换行符。
建议所有存在跨平台开发可能的项目都在项目根目录中检入一个.gitattributes文件,根据文件扩展名设置文件的text属性,或者设置即将介绍的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上使用git archive命令将工作区文件打包,导出的文件都会保持正确的换行符格式。
属性ident开启文本文件中的关键字扩展,即关键字$Id$的自动扩展。当检出到工作区时,$Id$自动扩展为$Id:,后面紧接着40位SHA1哈希值(相应blob对象的哈希值),然后以一个$字符结尾。当文件检入时,要对内容中出现的以$Id:开始,以$结束的内容替换为$Id$再保存到blob对象中。
这个功能可以说是对CVS相应功能的模仿。自动扩展的内容使用的是blob的哈希值而非提交本身的哈希值,因此并无太大实际意义,不建议使用。如果希望在文本文件中扩展出提交者姓名、提交ID等更有实际意义的内容,可以参照后面介绍的属性export-subst。
属性filter为文件设置一个自定义转换过滤器,以便文件在检入版本库及检出到工作区时进行相应的转换。定义转换过滤器通过Git配置文件来完成,因此这个属性应该只在本地进行设置,而不要通过检入到版本库中的.gitattributes文件传递。
例如下面的属性文件设置了所有的C语言源文件在检入和检出的时候使用名为indent的代码格式化过滤器。
*.c filter=indent
然后还要通过Git配置文件设定indent过滤器,示例如下:
[filter "indent"]
clean = indent
smudge = cat
定义过滤器只要设置两条命令,一条是名为clean的配置设定的的命令,用于在文件检入时执行,另外一条是名为smudge的配置设定的命令,用于将文件检出到工作区时使用的命令。对于本例,在代码检入时执行indent命令对代码格式化后,再保存到版本库中。当检出到工作区执行cat命令,实际上相当于直接将blob对象复制到工作区。
和前面介绍的属性不同,属性diff不会对文件检入检出造成影响,而只是在查看文件历史变更时起作用。属性diff可以取值如下:
diff
进行版本间比较时,以文本方式进行比较,即使文件看起来像是二进制文件(包含NULL字符),或者被设置为二进制文件(-text)。
-diff
不以文本方式进行差异比较,而以二进制方式进行比较。因为默认查看版本间差异时只显示文本文件的差异不显示二进制文件差异,因此包含-diff属性设置的文件在差异比较时不显示内容上的差异。对于有些文本文件(如postscript文件)进行差异比较没有意义,可以对其设置-diff属性,避免在显示提交间差异时造成干扰。
!diff
不设置diff属性,相当于在执行差异比较时要对文件内容进行智能判别,如果文件看起来像是文本文件,则显示文本格式的差异比较。
diff=<driver>
设定一个外部的驱动用于文件的差异比较。例如对于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用于为文件设置指定的合并策略,受影响的Git命令有:git merge、git revert和git cherry-pick等。属性merge可以取值如下:
merge
使用内置的三向合并策略。
-merge
将当前分支的文件版本设置为暂时的合并结果,并且声明合并发生了冲突,这实际上是二进制文件默认的合并方式。可以对文本文件设置该属性,使得在合并时的行为类似二进制文件。
!merge
和定义了merge属性效果类似,使用内置的三向合并策略。然而当通过Git配置文件的merge.default配置变量设置了合并策略后,如果没有为文件设置merge属性,则使用merge.default设定的策略。
merge=<driver>
使用指定的合并驱动执行三向文件合并。驱动可以是内置的三个驱动,也可以是用户通过Git配置文件自定义的驱动。
下面重点说一说通过枚举值来指定在合并时使用的内置驱动和自定义驱动。先来看看Git提供的三个内置驱动:
merge=text
默认文本文件在进行三向合并时使用的驱动。会在合并后的文本文件中用特殊的标识<<<<<<<、=======和>>>>>>>来标记冲突的内容。
merge=binary
默认二进制文件在进行三向合并时使用的驱动。会在工作区中保持当前分支中的版本不变,但是会通过在三个暂存区中进行冲突标识使得文件处于冲突状态。
merge=union
在文本文件三向合并过程中,不使用冲突标志符标识冲突,而是将冲突双方的内容简单的罗列在文件中。用户应该对合并后的文件进行检查。请慎用此合并驱动。
用户还可以自定义驱动。例如Topgit就使用自定义合并驱动的方式来控制两个Topgit管理文件.topmsg和.topdeps的合并行为。
Topgit会在版本库的配置文件.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给出。本例中使用了命令touch %A,含义为对当前分支中的文件进行简单的触碰(更新文件时间戳),亦即合并冲突时采用本地版本,丢弃其他版本。
Topgit还会在版本库.git/info/attributes属性文件中包含下面的属性设置:
.topmsg merge=ours
.topdeps merge=ours
含义为对这两个Topgit管理文件,采用在Git配置文件中设定的ours合并驱动。Topgit之所以要这么实现是因为不同特性分支的管理文件之间并无关联,也不需要合并,在遇到冲突时只使用自己的版本即可。这对于Topgit要经常地执行变基和分支合并来说,设置这个策略可以简化管理,但是这个合并设置在特定情况下也存在不合理之处。例如两个用户工作在同一分支上同时更改了.topmsg文件以修改特性分支的描述,在合并时会覆盖对方的修改,这显然是不好的行为。但是权衡利弊,还是如此实现最好。
Git可以对文本文件中空白字符的使用是否规范做出检查,在文件差异比较时,将使用不当的空白字符用红色进行标记(开启color.diff.whitespace)。也可以在执行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=<n>
设置一个TAB字符相当于几个空格,缺省为8个。
可以通过Git配置文件中的core.whitespace配置变量,设置开启更多的空白字符检查,将要开启的空白字符检查项用逗号分开即可。
如果希望对特定路径进行空白字符检查,则可以通过属性whitespace进行。属性whitespace可以有如下设置:
whitespace
开启所有的空白字符误用检查。
-whitespace
不对空白字符进行误用检查。
!whitespace
使用core.whitespace配置变量的设置进行空白字符误用检查。
whitespace=...
和core.whitespace的语法一样,用逗号分隔各个空白字符检查项。
设置了该属性的文件和目录在执行git archive时不予导出。
如果为文件设置了属性export-subst,则在使用git archive导出项目文件时,会对相应文件内容中的占位符展开,然后再添加到归档中。注意如果在使用git archive导出时使用树ID,而没有使用提交或者里程碑,则不会发生占位符展开。
占位符的格式为$Format:PLACEHOLDERS$,其中PLACEHOLDERS使用git log –pretty=format:相同的参数(具体参见git help log显示的帮助页)。例如:$Format:%H$将展开为提交的哈希值,$Format:%an$将展开为提交者姓名。
如果设置属性delta为false,则不对该路径指向的blob文件执行Delta压缩。
设置文件所使用的字符集,以便使用GUI工具(如gitk和git-gui)能够正确显示文件内容。基于性能上的考虑,gitk默认不检查该属性,除非通过gitk的偏好设置启用“Support per-file encodings”。
如果没有为文件设置encoding属性,则使用git.encoding配置变量。
属性binary严格来说是一个宏,相当于-text -diff。即禁止换行符转换及禁止文本方式显示文件差异。
用户也可以自定义宏。自定义宏只能在工作区根目录中的.gitattributes文件中添加,以内置的binary宏为例,相当于在属性文件中进行了如下的设置:
[attr]binary -diff -text