简体中文 ▾ 主题 ▾ 最新版本 ▾ gitattributes 上次更新于 2.55.0

名称

gitattributes - 按路径定义属性

概要

$GIT_DIR/info/attributes, .gitattributes

描述

一个 gitattributes 文件是一个简单的文本文件,用于为路径名赋予 属性

gitattributes 文件中的每一行都具有以下格式

pattern attr1 attr2 ...

也就是说,一个模式后面跟着一个属性列表,用空格分隔。首尾的空格会被忽略。以 # 开头的行会被忽略。以双引号开头的模式会以 C 语言风格进行引用。当模式与相应的路径匹配时,该行中列出的属性就会被赋予该路径。

对于给定的路径,每个属性可以处于以下状态之一

已设置

路径具有特殊值为 "true" 的属性;这可以通过在属性列表中仅列出属性名称来指定。

未设置

路径具有特殊值为 "false" 的属性;这可以通过在属性列表中列出带有连字符(减号) - 前缀的属性名称来指定。

设为某个值

路径具有指定字符串值的属性;这可以通过在属性列表中列出属性名称,后跟等号 = 及其值来指定。

未指定

没有模式匹配该路径,且没有说明该路径是否具有该属性,则该路径的属性被称为未指定(Unspecified)。

当有多个模式匹配该路径时,后面的行会覆盖前面的行。这种覆盖是针对每个属性单独进行的。

模式匹配路径的规则与 .gitignore 文件中的规则相同(参见 gitignore[5]),但有少数例外

  • 禁止使用否定模式

  • 匹配目录的模式不会递归匹配该目录内的路径(因此在属性文件中使用带尾部斜杠的 path/ 语法是没有意义的;请改用 path/**

在决定为路径分配哪些属性时,Git 会参考 $GIT_DIR/info/attributes 文件(具有最高优先级)、与相应路径在同一目录下的 .gitattributes 文件,以及其直到工作区顶层的父目录(包含 .gitattributes 的目录距离相应路径越远,其优先级越低)。最后会考虑全局和系统级文件(它们的优先级最低)。

当工作区中缺少 .gitattributes 文件时,会使用暂存区中的路径作为备用。在检出过程中,会首先使用暂存区中的 .gitattributes,然后再将工作区中的文件作为备用。

如果您只想影响单个仓库(即,将属性分配给该仓库中特定于某个用户工作流的文件),那么属性应该放置在 $GIT_DIR/info/attributes 文件中。应该进行版本控制并分发到其他仓库的属性(即所有用户都感兴趣的属性)应该放入 .gitattributes 文件中。影响单个用户所有仓库的属性应该放置在由 core.attributesFile 配置选项指定的文件中(参见 git-config[1])。其默认值为 $XDG_CONFIG_HOME/git/attributes。如果 $XDG_CONFIG_HOME 未设置或为空,则改用 $HOME/.config/git/attributes。系统上所有用户的属性应该放置在 $(prefix)/etc/gitattributes 文件中。

有时您需要将某个路径的属性设置覆盖为 Unspecified(未指定)状态。这可以通过列出带有感叹号 ! 前缀的属性名称来实现。

预留的 BUILTIN_* 属性

builtin_* 是内置属性值的预留命名空间。任何该命名空间下用户定义的属性都将被忽略并触发警告。

builtin_objectmode

此属性用于根据文件的比特模式(40000、120000、160000、100755、100644)过滤文件。例如 :(attr:builtin_objectmode=160000)。您也可以使用 git check-attr builtin_objectmode -- <file> 来检查这些值。如果对象不在暂存区中,git check-attr --cached 将返回 unspecified(未指定)。

效果

通过向路径分配特定属性,可以影响 Git 的某些操作。目前,以下操作是支持属性的。

检出(Checking-out)与检入(Checking-in)

当运行 git switchgit checkoutgit merge 等命令时,这些属性会影响如何将仓库中存储的内容复制到工作区文件中。它们还会影响在执行 git addgit commit 时,Git 如何将您在工作区中准备的内容存储到仓库中。

text

此属性将路径标记为文本文件,从而启用行尾转换:当匹配的文件被添加到暂存区时,该文件的行尾会在暂存区中被规范化为 LF。相反,当文件从暂存区复制到工作目录时,其行尾可能会根据 eol 属性、Git 配置和平台转换为 CRLF(参见下面对 eol 的解释)。

已设置

在路径上设置 text 属性会如上所述在检入和检出时启用行尾转换。每次检入文件时,暂存区中的行尾都会规范化为 LF,即使该文件之前是以 CRLF 行尾添加到 Git 中的。

未设置

在路径上取消设置 text 属性会告知 Git 在检入或检出时不要尝试进行任何行尾转换。

设为字符串值 "auto"

text 设为 "auto" 时,Git 会自行决定该文件是文本还是二进制。如果是文本,且该文件在 Git 中尚不具有 CRLF 结尾,则在检入和检出时会如上所述转换行尾。否则,在检入或检出时不进行转换。

未指定

如果未指定 text 属性,Git 将使用 core.autocrlf 配置变量来决定是否应该转换该文件。

任何其他值都会使 Git 的行为如同未指定 text 属性一样。

eol

此属性标记路径在检出时在工作区中使用特定的行尾样式。只有在设置了 texttext=auto 时它才有效(见上文),但如果未指定 text,指定 eol 会自动设置 text

设为字符串值 "crlf"

此设置会在检出文件时将工作目录中该文件的行尾转换为 CRLF。

设为字符串值 "lf"

此设置会在检出文件时,在工作目录中使用与暂存区中相同的行尾。

未指定

如果未对文件指定 eol 属性,则其在工作目录中的行尾由 core.autocrlfcore.eol 配置变量决定(参见 git-config[1] 中这些选项的定义)。如果设置了 text 但未设置这两个变量中的任何一个,则在 Windows 上默认值为 eol=crlf,在所有其他平台上默认值为 eol=lf

crlf 属性的向后兼容性

为了向后兼容,对 crlf 属性的解释如下

crlf		text
-crlf		-text
crlf=input	eol=lf

行尾转换

虽然 Git 通常不会改动文件内容,但可以配置为在仓库中将行尾规范化为 LF,并在检出文件时选择性地将其转换为 CRLF。

如果您只是希望在工作目录中拥有 CRLF 行尾,而不管您使用的是哪个仓库,则可以设置配置变量 "core.autocrlf",而不使用任何属性。

[core]
	autocrlf = true

这不会强制对文本文件进行规范化,但能确保您引入到仓库的文本文件在添加时将其行尾规范化为 LF,并且仓库中已经规范化的文件保持规范化。

如果您想确保任何贡献者引入到仓库的文本文件都进行了行尾规范化,可以将 所有 文件的 text 属性设置为 "auto"。

*	text=auto

属性允许对行尾如何转换进行细粒度控制。下面是一个示例,它将使 Git 规范化 .txt、.vcproj 和 .sh 文件,确保 .vcproj 文件在工作目录中具有 CRLF,.sh 文件具有 LF,并防止 .jpg 文件被规范化,无论其内容如何。

*               text=auto
*.txt		text
*.vcproj	text eol=crlf
*.sh		text eol=lf
*.jpg		-text
注意
当在跨平台项目中使用推送(push)和拉取(pull)到中央仓库并启用了 text=auto 转换时,包含 CRLF 的文本文件应该被规范化。

从一个干净的工作目录开始

$ echo "* text=auto" >.gitattributes
$ git add --renormalize .
$ git status        # Show files that will be normalized
$ git commit -m "Introduce end-of-line normalization"

如果 git status 中出现了不应被规范化的文件,请在运行 git add -u 之前取消设置它们的 text 属性。

manual.pdf	-text

相反,对于 Git 未检测到的文本文件,可以手动启用规范化。

weirdchars.txt	text

如果 core.safecrlf 设置为 "true" 或 "warn",Git 会验证对于当前的 core.autocrlf 设置,该转换是否是可逆的。对于 "true",Git 拒绝不可逆的转换;对于 "warn",Git 仅打印警告但接受不可逆的转换。该安全机制旨在防止在工作区文件上执行此类转换,但有少数例外。即使……

  • git add 本身不会改变工作区中的文件,但下一次检出会改变,因此会触发安全保护;

  • 使用 git apply 用补丁更新文本文件确实会改变工作区中的文件,但该操作针对的是文本文件,而 CRLF 转换是为了修复行尾不一致问题,因此不会触发安全保护;

  • git diff 本身不会改变工作区中的文件,它通常被用来检查您打算下一步进行 git add 的更改。为了尽早发现潜在问题,会触发安全保护。

working-tree-encoding

Git 将以 ASCII 或其超集(例如 UTF-8、ISO-8859-1 等)编码的文件识别为文本文件。以某些其他编码(例如 UTF-16)编码的文件会被解释为二进制文件,因此,Git 内置的文本处理工具(例如 git diff)以及大多数 Git Web 前端默认不会可视化这些文件的内容。

在这些情况下,您可以通过 working-tree-encoding 属性告诉 Git 工作目录中文件的编码。如果将具有此属性的文件添加到 Git 中,Git 会将内容从指定的编码重新编码为 UTF-8。最后,Git 将 UTF-8 编码的内容存储在其内部数据结构(称为“暂存区”)中。检出时,内容会被重新编码回指定的编码。

请注意,使用 working-tree-encoding 属性可能会有一些陷阱

  • 其他 Git 实现(例如 JGit 或 libgit2)和旧版本的 Git(截至 2018 年 3 月)不支持 working-tree-encoding 属性。如果您决定在仓库中使用 working-tree-encoding 属性,强烈建议确保所有与该仓库协同工作的客户端都支持它。

    例如,Microsoft Visual Studio 资源文件(*.rc)或 PowerShell 脚本文件(*.ps1)有时是以 UTF-16 编码的。如果您将 *.ps1 文件声明为 UTF-16,并使用启用了 working-tree-encoding 的 Git 客户端添加 foo.ps1,那么 foo.ps1 在内部将存储为 UTF-8。不支持 working-tree-encoding 的客户端在检出时会将 foo.ps1 检出为 UTF-8 编码的文件。这通常会给该文件的使用者带来麻烦。

    如果一个不支持 working-tree-encoding 属性的 Git 客户端添加了一个新文件 bar.ps1,那么 bar.ps1 在内部将“原样”存储(在此示例中可能是 UTF-16)。而支持 working-tree-encoding 的客户端会将其内部内容解释为 UTF-8,并在检出时尝试将其转换为 UTF-16。该操作将失败并导致错误。

  • 将内容重新编码为非 UTF 编码可能会导致错误,因为这种转换可能无法在 UTF-8 之间安全地往返。如果您怀疑您的编码不是往返安全的,请将其添加到 core.checkRoundtripEncoding 中以让 Git 检查往返编码(参见 git-config[1])。已知 SHIFT-JIS(日本字符集)与 UTF-8 之间存在往返问题,默认会进行检查。

  • 重新编码内容需要消耗资源,这可能会减慢某些 Git 操作(例如 git checkoutgit add)。

仅当您无法将文件存储为 UTF-8 编码,且希望 Git 能够将内容作为文本处理时,才使用 working-tree-encoding 属性。

作为一个示例,如果您的 *.ps1 文件是带有字节顺序标记(BOM)的 UTF-16 编码,并且您希望 Git 根据您的平台执行自动行尾转换,请使用以下属性。

*.ps1		text working-tree-encoding=UTF-16

如果您的 *.ps1 文件是不带 BOM 的 UTF-16 小端序(little endian)编码,并且您希望 Git 在工作目录中使用 Windows 行尾,请使用以下属性(如果您想要带 BOM 的 UTF-16 小端序,请使用 UTF-16LE-BOM 代替 UTF-16LE)。请注意,如果使用了 working-tree-encoding 属性,强烈建议显式定义带有 eol 的行尾以避免歧义。

*.ps1		text working-tree-encoding=UTF-16LE eol=crlf

您可以使用以下命令在您的平台上获取所有可用编码的列表

iconv --list

如果您不知道某个文件的编码,可以使用 file 命令来推测编码

file foo.ps1

ident

当为路径设置了属性 ident 时,Git 在检出时会将 blob 对象中的 $Id$ 替换为 $Id:,后跟 40 位十六进制的 blob 对象名,最后是一个美元符号 $。工作区文件中任何以 $Id: 开头并以 $ 结尾的字节序列在检入时都会被替换为 $Id$

filter

可以向 filter 属性设置一个字符串值,该值命名了配置中指定的过滤器驱动程序。

过滤器驱动程序由 clean 命令和 smudge 命令组成,这两者中的任何一个都可以不指定。在检出时,如果指定了 smudge 命令,该命令会从其标准输入接收 blob 对象,并使用其标准输出更新工作区文件。类似地,在检入时,clean 命令用于转换工作区文件的内容。默认情况下,这些命令仅处理单个 blob 然后终止。如果使用长时间运行的 process 过滤器来代替 clean 和/或 smudge 过滤器,那么 Git 可以在单个 Git 命令的整个生命周期内(例如 git add --all),通过单次调用过滤器命令来处理所有 blob。如果配置了长时间运行的 process 过滤器,它总是优先于配置的单 blob 过滤器。有关与 process 过滤器进行通信所用协议的说明,请参见下一节。

内容过滤的一种用途是将内容调整为对平台、文件系统和用户更方便使用的形式。对于这种操作模式,这里的关键短语是“更方便”,而不是“将不可用的内容变成可用的”。换句话说,其目的是:如果有人取消设置了过滤器驱动程序定义,或者没有合适的过滤器程序,项目应该仍然是可用的。

内容过滤的另一种用途是存储无法在仓库中直接使用的内容(例如,指向存储在 Git 外部的真实内容的 UUID,或加密内容),并在检出时将其转换为可用形式(例如,下载外部内容或解密加密内容)。

这两类过滤器的行为不同。默认情况下,过滤器会被视为前者,即将内容调整为更方便的形式。配置中缺失过滤器驱动程序定义,或者过滤器驱动程序退出时返回非零状态,这不属于错误,而是使该过滤器成为无操作的直通(no-op passthru)过滤器。

您可以通过将 filter.<driver>.required 配置变量设置为 true,来声明一个过滤器能够将本身不可用的内容转换为可用内容。

注意:每当更改 clean 过滤器时,应该重新规范化仓库:$ git add --renormalize .

例如,在 .gitattributes 中,您将为路径分配 filter 属性。

*.c	filter=indent

然后,您将在 .git/config 中定义 "filter.indent.clean" 和 "filter.indent.smudge" 配置,以指定在提交源文件(运行 "clean")和检出源文件(由于命令是 "cat" 故不作更改)时修改 C 程序内容的一对命令。

[filter "indent"]
	clean = indent
	smudge = cat

为了获得最佳效果,如果运行两次,clean 不应进一步更改其输出(“clean→clean”应该等同于 “clean”),并且多个 smudge 命令不应更改 clean 的输出(“smudge→smudge→clean”应该等同于 “clean”)。请参阅下文关于合并的部分。

在这方面,“indent” 过滤器表现良好:它不会修改已经正确缩进的输入。在这种情况下,缺少 smudge 过滤器意味着 clean 过滤器 必须 在不修改自身输出的情况下接受其输出。

如果过滤器 必须 成功才能使存储的内容可用,您可以在配置中将该过滤器声明为 required

[filter "crypt"]
	clean = openssl enc ...
	smudge = openssl enc -d ...
	required

过滤器命令行中的序列 "%f" 会被替换为该过滤器正在处理的文件的名称。过滤器可能会在关键字替换中使用它。例如

[filter "p4"]
	clean = git-p4-filter --clean %f
	smudge = git-p4-filter --smudge %f

请注意,"%f" 是正在处理的路径的名称。根据正在过滤的版本,磁盘上相应的文件可能不存在,或者可能具有不同的内容。因此,smudge 和 clean 命令不应尝试访问磁盘上的文件,而应仅作为对通过标准输入提供给它们的内容的过滤器。

长时间运行的过滤器进程

如果过滤器命令(一个字符串值)是通过 filter.<driver>.process 定义的,那么 Git 可以在单个 Git 命令的整个生命周期内通过单次调用过滤器来处理所有 blob。这是通过使用长时间运行的进程协议(在 Documentation/technical/long-running-process-protocol.adoc 中进行了描述)实现的。

当 Git 遇到第一个需要进行 clean 或 smudge 的文件时,它会启动过滤器并执行握手。在握手过程中,Git 发送的欢迎消息是 "git-filter-client",仅支持版本 2,支持的能力有 "clean"、"smudge" 和 "delay"。

随后,Git 发送一个以 flush 数据包结束的 "key=value" 对列表。该列表将至少包含过滤器命令(基于支持的能力)以及要过滤的文件相对于仓库根目录的路径名。紧接着 flush 数据包,Git 将发送分割在零个或多个 pkt-line 数据包中的内容,并以一个 flush 数据包来结束内容。请注意,过滤器在收到内容和最后的 flush 数据包之前,绝不能发送任何响应。另请注意,"key=value" 对中的 "value" 可以包含 "=" 字符,而键永远不会包含该字符。

packet:          git> command=smudge
packet:          git> pathname=path/testfile.dat
packet:          git> 0000
packet:          git> CONTENT
packet:          git> 0000

过滤器应当响应一个以 flush 数据包结束的 "key=value" 对列表。如果过滤器没有遇到问题,该列表必须包含一个 "success" status。紧接着这些数据包,过滤器应当发送分割在零个或多个 pkt-line 数据包中的内容,并在最后发送一个 flush 数据包。最后,应当发送第二个以 flush 数据包结束的 "key=value" 对列表。过滤器可以在第二个列表中更改状态,或通过空列表保持状态不变。请注意,无论如何,空列表都必须以 flush 数据包结束。

packet:          git< status=success
packet:          git< 0000
packet:          git< SMUDGED_CONTENT
packet:          git< 0000
packet:          git< 0000  # empty list, keep "status=success" unchanged!

如果结果内容为空,则过滤器应当响应 "success" 状态和一个 flush 数据包以表示内容为空。

packet:          git< status=success
packet:          git< 0000
packet:          git< 0000  # empty content!
packet:          git< 0000  # empty list, keep "status=success" unchanged!

如果过滤器不能或不想处理该内容,它应当响应一个 "error" 状态。

packet:          git< status=error
packet:          git< 0000

如果过滤器在处理过程中遇到错误,它可以在发送(部分或全部)内容后发送 "error" 状态。

packet:          git< status=success
packet:          git< 0000
packet:          git< HALF_WRITTEN_ERRONEOUS_CONTENT
packet:          git< 0000
packet:          git< status=error
packet:          git< 0000

如果过滤器在 Git 进程的生命周期内,不能或不想处理该内容以及任何未来的内容,则它应当在协议的任何时间点响应一个 "abort" 状态。

packet:          git< status=abort
packet:          git< 0000

如果设置了 "error"/"abort" 状态,Git 既不会停止也不会重启过滤器进程。然而,Git 会根据 filter.<driver>.required 标志来设置其退出码,这模拟了 filter.<driver>.clean / filter.<driver>.smudge 机制的行为。

如果过滤器在通信期间意外终止或不遵守协议,Git 将停止该过滤器进程,并在需要处理下一个文件时重启它。根据 filter.<driver>.required 标志,Git 可能会将其解释为错误。

延迟

如果过滤器支持 "delay" 能力,Git 可以在过滤器命令和路径名之后发送 "can-delay" 标志。该标志表示过滤器可以通过响应无内容、状态为 "delayed" 且带有一个 flush 数据包的方式,来延迟过滤当前的 blob(例如,以补偿网络延迟)。

packet:          git> command=smudge
packet:          git> pathname=path/testfile.dat
packet:          git> can-delay=1
packet:          git> 0000
packet:          git> CONTENT
packet:          git> 0000
packet:          git< status=delayed
packet:          git< 0000

如果过滤器支持 "delay" 能力,则它必须支持 "list_available_blobs" 命令。如果 Git 发送此命令,则过滤器应当返回一个路径名列表,表示先前被延迟且现在可用的 blob。该列表必须以一个 flush 数据包结束,后跟一个同样以 flush 数据包结束的 "success" 状态。如果延迟路径的 blob 还不可用,则过滤器应当阻塞响应,直到至少有一个 blob 可用。过滤器可以通过发送一个空列表来告诉 Git 它没有更多延迟的 blob 了。一旦过滤器响应了空列表, Git 就会停止询问。此时 Git 尚未收到的所有 blob 都将被视为丢失并导致错误。

packet:          git> command=list_available_blobs
packet:          git> 0000
packet:          git< pathname=path/testfile.dat
packet:          git< pathname=path/otherfile.dat
packet:          git< 0000
packet:          git< status=success
packet:          git< 0000

在 Git 收到路径名后,它会再次请求对应的 blob。这些请求包含一个路径名和一个空内容段。过滤器应当以通常的方式(如上文所述)响应经 smudge 处理的内容。

packet:          git> command=smudge
packet:          git> pathname=path/testfile.dat
packet:          git> 0000
packet:          git> 0000  # empty content!
packet:          git< status=success
packet:          git< 0000
packet:          git< SMUDGED_CONTENT
packet:          git< 0000
packet:          git< 0000  # empty list, keep "status=success" unchanged!

示例

可以在 Git 核心仓库中的 contrib/long-running-filter/example.pl 找到长时间运行过滤器的演示实现。如果您开发自己的长时间运行过滤器进程,则 GIT_TRACE_PACKET 环境变量对于调试会非常有用(参见 git[1])。

请注意,您不能将现有的 filter.<driver>.cleanfilter.<driver>.smudge 命令与 filter.<driver>.process 一起使用,因为前两者使用的进程间通信协议与后者不同。

提交/检出属性之间的交互

在检入(check-in)代码路径中,工作区文件首先使用 filter 驱动程序(如果指定且定义了相应的驱动程序)进行转换,然后用 ident(如果指定)处理结果,最后再用 text(同样地,如果指定且适用)处理。

在检出(check-out)代码路径中,blob 内容首先使用 text 转换,然后使用 ident,最后送入 filter

合并具有不同提交/检出属性的分支

如果您向文件添加了导致该文件的规范仓库格式发生变化的属性(例如添加 clean/smudge 过滤器或 text/eol/ident 属性),则在属性未生效的情况下进行任何合并通常会导致合并冲突。

为了防止这些不必要的合并冲突,可以通过设置 merge.renormalize 配置变量,指示 Git 对每个需要进行三路内容合并的文件在所有三个暂存阶段执行虚拟的检出和检入。这可以防止在将已转换的文件与未转换的文件合并时,由于检入转换而引起的虚假合并冲突。

只要“smudge→clean”产生与“clean”相同的输出(即使在已经过 smudge 处理的文件上),该策略就会自动解决所有与过滤器相关的冲突。不以这种方式运作的过滤器可能会导致额外的合并冲突,必须手动解决。

生成 diff 文本

diff

属性 diff 会影响 Git 如何为特定文件生成差异(diff)。它可以告诉 Git 是为该路径生成文本补丁,还是将该路径视为二进制文件。它还会影响在块头(hunk header) @@ -k,l +n,m @@ 行上显示什么内容,告诉 Git 使用外部命令来生成 diff,或者要求 Git 在生成 diff 之前将二进制文件转换为文本格式。

已设置

设置了 diff 属性的路径会被视为文本,即使它们包含通常绝不会出现在文本文件中的字节值(例如 NUL)。

未设置

未设置 diff 属性的路径将生成 Binary files differ(二进制文件不同)提示(或者如果启用了二进制补丁,则生成二进制补丁)。

未指定

未指定 diff 属性的路径会首先接受其内容的检查,如果看起来像文本且大小小于 core.bigFileThreshold,则会被视为文本。否则将生成 Binary files differ

字符串

使用指定的 diff 驱动程序显示差异。每个驱动程序可以指定一个或多个选项,如以下部分所述。“foo” diff 驱动程序的选项由 Git 配置文件中 “diff.foo” 部分的配置变量定义。

定义外部 diff 驱动程序

diff 驱动程序的定义是在 gitconfig 中完成的,而不是在 gitattributes 文件中,所以严格来说,这篇手册页并不是讨论它的合适地方。然而……

要定义外部 diff 驱动程序 jcdiff,可以向您的 $GIT_DIR/config 文件(或 $HOME/.gitconfig 文件)添加如下部分

[diff "jcdiff"]
	command = j-c-diff

当 Git 需要为您显示已将 diff 属性设置为 jcdiff 的路径的差异时,它会使用 7 个参数调用您在上述配置中指定的命令(即 j-c-diff),这与调用 GIT_EXTERNAL_DIFF 程序的方式相同。有关详细信息,请参见 git[1]

如果程序能够忽略某些变化(类似于 git diff --ignore-space-change),则也可以将 trustExitCode 选项设置为 true。然后,如果它发现显著更改,预期返回退出码 1,否则返回 0。

设置内部 diff 算法

diff 算法可以通过 diff.algorithm 配置键进行设置,但有时按路径设置 diff 算法可能会很有用。例如,人们可能希望对 .json 文件使用 minimal 算法,对 .c 文件使用 histogram 算法,等等,而不必每次都在命令行中传递该算法。

首先,在 .gitattributes 中,为路径分配 diff 属性。

*.json diff=<name>

然后,定义 "diff.<name>.algorithm" 配置以指定 diff 算法,可从 myerspatienceminimalhistogram 中进行选择。

[diff "<name>"]
  algorithm = histogram

此 diff 算法适用于面向用户的 diff 输出(如 git-diff(1)、git-show(1)),并且也用于 --stat 输出。合并机制不会使用通过此方法设置的 diff 算法。

注意
如果为带有 diff=<name> 属性的路径定义了 diff.<name>.command,它将作为外部 diff 驱动程序执行(见上文),而添加 diff.<name>.algorithm 没有效果,因为算法不会传递给外部 diff 驱动程序。

定义自定义块头(hunk-header)

文本 diff 输出中的每组更改(称为“块/hunk”)都带有以下格式的前缀行

@@ -k,l +n,m @@ TEXT

这被称为 块头(hunk header)。 默认情况下,“TEXT” 部分是起自字母、下划线或美元符号的一行;这与 GNU diff -p 输出所使用的匹配。然而,此默认选择并不适用于某些内容,您可以使用自定义模式进行选择。

首先,在 .gitattributes 中,您将为路径分配 diff 属性。

*.tex	diff=tex

然后,您将定义 "diff.tex.xfuncname" 配置以指定一个正则表达式,该表达式匹配您希望作为块头 “TEXT” 出现的行。像这样向您的 $GIT_DIR/config 文件(或 $HOME/.gitconfig 文件)添加一个部分

[diff "tex"]
	xfuncname = "^(\\\\(sub)*section\\{.*)$"

注意。单层反斜杠会被配置文件解析器吃掉,因此您需要将反斜杠翻倍;上面的模式会匹配以反斜杠开头,且包含零个或多个紧跟在 section 之后、再紧跟在左大括号之后的 sub 字符,直到行尾的一行。

有一些内置模式可以使这更容易,而 tex 就是其中之一,因此您不必在配置文件中编写上述内容(您仍然需要通过 .gitattributes 属性机制来启用它)。可以使用以下内置模式:

  • ada 适用于 Ada 语言源文件。

  • bash 适用于 Bourne-Again SHell 语言的源文件。涵盖 POSIX shell 函数定义的超集。

  • bibtex 适用于包含 BibTeX 编码引用的文件。

  • cpp 适用于 C 和 C++ 语言的源文件。

  • csharp 适用于 C# 语言的源文件。

  • css 适用于层叠样式表(CSS)。

  • dts 适用于设备树(DTS)文件。

  • elixir 适用于 Elixir 语言的源文件。

  • fortran 适用于 Fortran 语言的源文件。

  • fountain 适用于 Fountain 文档。

  • golang 适用于 Go 语言的源文件。

  • html 适用于 HTML/XHTML 文档。

  • java 适用于 Java 语言的源文件。

  • kotlin 适用于 Kotlin 语言的源文件。

  • markdown 适用于 Markdown 文档。

  • matlab 适用于 MATLAB 和 Octave 语言的源文件。

  • objc 适用于 Objective-C 语言的源文件。

  • pascal 适用于 Pascal/Delphi 语言的源文件。

  • perl 适用于 Perl 语言的源文件。

  • php 适用于 PHP 语言的源文件。

  • python 适用于 Python 语言的源文件。

  • ruby 适用于 Ruby 语言的源文件。

  • rust 适用于 Rust 语言的源文件。

  • scheme 适用于大多数 Lisp 方言的源文件,包括 Scheme、Emacs Lisp、Common Lisp 和 Clojure。

  • tex 适用于 LaTeX 文档的源文件。

自定义单词差异(word diff)

您可以通过在 "diff.*.wordRegex" 配置变量中指定适当的正则表达式,来自定义 git diff --word-diff 用于在行中拆分单词的规则。例如,在 TeX 中,反斜杠后跟一系列字母构成一个命令,但多个这样的命令可以在没有空格间隔的情况下一起运行。为了将它们分开,可以向您的 $GIT_DIR/config 文件(或 $HOME/.gitconfig 文件)添加如下正则表达式:

[diff "tex"]
	wordRegex = "\\\\[a-zA-Z]+|[{}]|\\\\.|[^\\{}[:space:]]+"

为上一节中列出的所有语言都提供了一个内置模式。

对二进制文件执行文本 diff

有时,希望查看某些二进制文件的文本转换版本的差异。例如,文字处理器文档可以转换为 ASCII 文本表示,并显示文本的差异。尽管这种转换会丢失某些信息,但生成的差异对于人工查看非常有用(但不能直接应用为补丁)。

textconv 配置选项用于定义执行此类转换的程序。该程序应该接受单个参数(要转换的文件名),并在 stdout 上输出转换后的文本。

例如,要显示文件 exif 信息的差异而不是二进制信息(假设您已安装了 exif 工具),请向您的 $GIT_DIR/config 文件(或 $HOME/.gitconfig 文件)添加以下部分:

[diff "jpg"]
	textconv = exif
注意
文本转换通常是单向转换;在此示例中,我们丢失了实际的图像内容,仅关注文本数据。这意味着由 textconv 生成的差异 适合应用(apply)。出于这个原因,只有 git diffgit log 命令族(即 log、whatchanged、show)会执行文本转换。 git format-patch 绝不会生成此类输出。如果您想向某人发送二进制文件的文本转换差异(例如,因为它能快速传达您所做的更改),您应该单独生成它,并将其作为注释 连同 您可能发送的常规二进制差异一起发送。

因为文本转换可能会很慢,尤其是在使用 git log -p 进行大量转换时,Git 提供了一种缓存输出并在未来的差异中使用的机制。要启用缓存,请在 diff 驱动程序的配置中设置 “cachetextconv” 变量。例如:

[diff "jpg"]
	textconv = exif
	cachetextconv = true

这将无限期地缓存对每个 blob 运行 "exif" 的结果。如果您更改了 diff 驱动程序的 textconv 配置变量,Git 会自动使缓存条目失效并重新运行 textconv 过滤器。如果您想手动使缓存失效(例如,因为您的 "exif" 版本已更新且现在能产生更好的输出),可以使用 git update-ref -d refs/notes/textconv/jpg(其中 "jpg" 是 diff 驱动程序的名称,如上例所示)手动删除缓存。

选择 textconv 还是外部 diff

如果您想显示仓库中二进制或特殊格式 blob 之间的差异,您可以选择使用外部 diff 命令,或者使用 textconv 将它们转换为可进行 diff 的文本格式。选择哪种方法取决于您的具体情况。

使用外部 diff 命令的优点是灵活性。您不受限于寻找面向行的更改,输出也不需要类似于统一 diff(unified diff)。您可以自由地以最适合您的数据格式的方式查找和报告更改。

相比之下,textconv 的限制要多得多。您只需提供将数据转换为面向行的文本格式的转换方法,Git 就会使用其常规 diff 工具来生成输出。选择这种方法有几个优点:

  1. 易用性。编写二进制到文本的转换通常比执行您自己的 diff 简单得多。在许多情况下,现有的程序可以用作 textconv 过滤器(例如 exif、odt2txt)。

  2. Git diff 功能。通过仅自己执行转换步骤,您仍然可以利用 Git 的许多 diff 功能,包括着色、word-diff 以及用于合并的联合(combined)diff。

  3. 缓存。Textconv 缓存可以加速重复的 diff,例如您可能通过运行 git log -p 触发的那些 diff。

将文件标记为二进制

Git 通常通过检查内容的开头来正确猜测 blob 是包含文本还是二进制数据。然而,有时您可能想覆盖它的决定,这可能是因为 blob 在文件后面包含了二进制数据,或者是因为内容虽然在技术上是由文本字符组成的,但对人类读者来说是不透明的。例如,许多 postscript 文件仅包含 ASCII 字符,但会产生杂乱无章且毫无意义的差异。

将文件标记为二进制的最简单方法是在 .gitattributes 文件中取消设置 diff 属性

*.ps -diff

这将导致 Git 生成 Binary files differ(二进制文件不同)提示(或者如果启用了二进制补丁,则生成二进制补丁)而不是常规的 diff。

然而,人们可能也想指定其他 diff 驱动程序属性。例如,您可能想使用 textconv 将 postscript 文件转换为 ASCII 表示以便人工查看,但在其他情况下将它们视为二进制文件。您不能同时指定 -diffdiff=ps 属性。解决方案是使用 diff.*.binary 配置选项:

[diff "ps"]
  textconv = ps2ascii
  binary = true

执行三路合并

merge

git merge 以及其他命令(如 git revertgit cherry-pick)期间,当需要进行文件级合并时,属性 merge 会影响如何合并一个文件的三个版本。

已设置

内置的三路合并驱动程序用于以类似于 RCS 套件的 merge 命令的方式合并内容。这适用于普通文本文件。

未设置

将当前分支的版本作为暂定的合并结果,并声明合并存在冲突。这适用于没有明确定义合并语义的二进制文件。

未指定

默认情况下,这使用与设置了 merge 属性时相同的内置三路合并驱动程序。然而,merge.default 配置变量可以指定不同的合并驱动程序,以用于未指定 merge 属性的路径。

字符串

使用指定的自定义合并驱动程序执行三路合并。可以通过要求 "text" 驱动程序来显式指定内置的三路合并驱动程序;可以通过 "binary" 来要求内置的“采用当前分支”驱动程序。

内置合并驱动程序

定义了几个内置的低级合并驱动程序,可以通过 merge 属性来要求使用它们。

text

文本文件常用的三路文件级合并。冲突区域用冲突标记 <<<<<<<=======>>>>>>> 标记。来自您分支的版本显示在 ======= 标记之前,而来自被合并分支的版本显示在 ======= 标记之后。

binary

在工作区中保留您分支的版本,但使路径保持冲突状态,以便用户自行解决。

union

对文本文件运行三路文件级合并,但从两个版本中获取行,而不是保留冲突标记。这往往会使添加的行以随机顺序留在结果文件中,用户应当验证结果。如果您不理解其影响,请勿使用此方法。

定义自定义合并驱动程序

合并驱动程序的定义是在 .git/config 文件中完成的,而不是在 gitattributes 文件中,所以严格来说,这篇手册页并不是讨论它的合适地方。然而……

要定义自定义合并驱动程序 filfre,可以向您的 $GIT_DIR/config 文件(或 $HOME/.gitconfig 文件)添加如下部分:

[merge "filfre"]
	name = feel-free merge driver
	driver = filfre %O %A %B %L %P
	recursive = binary

带有 merge.*.name 变量为驱动程序提供一个易于阅读的名称。

merge.*.driver 变量的值用于构建一个命令,该命令运行以处理共同祖先的版本(%O)、当前版本(%A)和其他分支的版本(%B)。在构建命令行时,这三个占位符会被替换为保存这些版本内容的临时文件的名称。此外,%L 将被替换为冲突标记的大小(见下文)。

合并驱动程序应当通过覆盖名为 %A 的文件来在其中保留合并结果,并且如果成功干净地合并了它们,则以零状态退出,如果存在冲突,则以非零状态退出。当驱动程序崩溃时(例如被 SEGV 信号终止),应当以大于 128 的非零状态退出,在这种情况,合并会导致失败(这与产生冲突不同)。

merge.*.recursive 变量指定当有多个共同祖先且需要为共同祖先之间的内部合并调用该合并驱动程序时,应使用什么其他合并驱动程序。未指定时,驱动程序本身将同时用于内部合并和最终合并。

合并驱动程序可以通过占位符 %P 获取将存储合并结果的路径名。用于共同祖先、本地 head 和其他 head 的冲突标签可以分别使用 %S%X%Y 来传递。

conflict-marker-size

此属性控制在发生冲突的合并期间留在工作区文件中的冲突标记的长度。只有正整数才有意义。

例如,.gitattributes 中的这一行可以用来告诉合并机制,当合并文件 Documentation/git-merge.adoc 导致冲突时,保留长得多的(而不是通常的 7 个字符长)冲突标记。

Documentation/git-merge.adoc	conflict-marker-size=32

检查空白错误

whitespace

core.whitespace 配置变量允许您为项目中的所有路径定义 diffapply 应当将哪些内容视为空白错误(参见 git-config[1])。此属性使您能够对每个路径进行更细粒度的控制。

已设置

注意 Git 已知的所有类型的潜在空白错误。制表符宽度取自 core.whitespace 配置变量的值。

未设置

不将任何内容视为错误。

未指定

使用 core.whitespace 配置变量的值来决定将什么视为错误。

字符串

指定要留意的常见空白问题的逗号分隔列表,格式与 core.whitespace 配置变量相同。

创建归档

export-ignore

具有 export-ignore 属性的文件和目录不会被添加到归档文件中。

export-subst

如果为文件设置了 export-subst 属性,则在将该文件添加到归档文件时,Git 将展开几个占位符。展开取决于提交 ID 是否可用,即如果给 git-archive[1] 提供的是一个树而不是一个提交或标签,则不会进行任何替换。占位符与 git-log[1]--pretty=format: 选项的占位符相同,只是它们在文件中需要像这样包装:$Format:PLACEHOLDERS$。例如,字符串 $Format:%H$ 将被替换为提交哈希。然而,每个归档仅展开一个 %(describe) 占位符,以避免拒绝服务(DoS)攻击。

打包对象

delta

对于将 delta 属性设置为 false 的路径的 blob,将不会尝试进行增量压缩。

在 GUI 工具中查看文件

encoding

此属性的值指定了 GUI 工具(例如 gitk[1]git-gui[1])显示相关文件内容时应当使用的字符编码。请注意,出于性能考虑,除非您在选项中手动启用单文件编码,否则 gitk[1] 不会使用此属性。

如果未设置此属性或其值无效,则改用 gui.encoding 配置变量的值(参见 git-config[1])。

使用宏属性

您不希望对您追踪的任何二进制文件应用任何行尾转换,也不希望生成文本差异。您需要指定例如:

*.jpg -text -diff

但是当您有很多属性时,这可能会变得繁琐。使用宏属性,您可以定义一个属性,在设置它时,同时设置或取消设置其他几个属性。系统包含一个内置的宏属性:binary

*.jpg binary

如上所述,设置 “binary” 属性也会取消设置 “text” 和 “diff” 属性。请注意,宏属性只能被“已设置”(Set),尽管设置其中一个可能会起到设置或取消设置其他属性,甚至将其他属性恢复为“未指定”(Unspecified)状态的效果。

定义宏属性

自定义宏属性只能在顶层 gitattributes 文件中定义($GIT_DIR/info/attributes、工作树顶层的 .gitattributes 文件,或者全局或系统级的 gitattributes 文件),而不能在工作区子目录的 .gitattributes 文件中定义。内置的宏属性 “binary” 等同于:

[attr]binary -diff -merge -text

注意事项

Git 在访问工作区中的 .gitattributes 文件时不会遵循符号链接。这保持了从暂存区(index)或树(tree)访问该文件与从文件系统访问该文件时行为的一致性。

示例

如果您有这三个 gitattributes 文件

(in $GIT_DIR/info/attributes)

a*	foo !bar -baz

(in .gitattributes)
abc	foo bar baz

(in t/.gitattributes)
ab*	merge=filfre
abc	-foo -bar
*.c	frotz

赋予路径 t/abc 的属性计算如下:

  1. 通过检查 t/.gitattributes(它与相关路径在同一个目录下),Git 发现第一行匹配。设置了 merge 属性。它还发现第二行匹配,并且取消设置了 foobar 属性。

  2. 然后它检查 .gitattributes(它在父目录中),并发现第一行匹配,但是 t/.gitattributes 文件已经决定了应当如何赋予该路径 mergefoobar 属性,因此它保持 foobar 为取消设置状态。设置了属性 baz

  3. 最后它检查 $GIT_DIR/info/attributes。该文件用于覆盖树内设置。第一行匹配,并且设置了 foobar 恢复为未指定状态,取消设置了 baz

结果,分配给 t/abc 的属性变为

foo	set to true
bar	unspecified
baz	set to false
merge	set to string value "filfre"
frotz	unspecified

另请参阅

GIT

Git[1] 套件的一部分