在 gitattributes 中使用 `**/` 匹配目录
Match dirs using `**/` in gitattributes
我原以为 **/
可以匹配 git 存储库中的任何目录,但实际上它什么都不匹配。 man gitattributes
说:
patterns that match a directory do not recursively match paths inside that directory (so using the
trailing-slash path/ syntax is pointless in an attributes file; use path/** instead)
但在我的例子中 **/
甚至不匹配 目录本身 。我的意思是,如果我的存储库中有 a/b.txt
,那么 **/
甚至不匹配目录 a
。我期待它匹配目录 a
但不匹配文件 a/b.txt
.
如果我将 **/
更改为 **
,则目录及其内容都匹配。在同一示例中,dir a
和 file a/b.txt
都匹配。
那么 git 属性如何作用于目录?如果 git 认为目录的属性无关紧要,为什么 git 在 **
的情况下列出它们?
与 执行 特殊情况目录处理的 .gitignore
不同,似乎 .gitattributes
代码从不处理特殊情况,但有一个重要的例外一个目录。这部分是因为 Git 并不真正 store 目录。1 所以每个属性都应用于某些文件集。尽管如此,文件 do 驻留在目录中——在两个真实的操作系统中,以及它们在提交中的存储方式(见脚注 1)——所以它可能对 .gitattributes
对尾部斜线有特殊情况,而不是将属性应用于文件。
不过正如您所观察到的,这并没有发生。尽管 attr.c
(path_matches
中)有一些代码检查目录。但这并不重要,因为 Git 属性的有用部分(同样有一个例外) 无论如何只适用于文件.
我上面强调的文字是这个意思。考虑,例如:
*.txt text
*.jpg -text diff=jpg
version export-subst
可能会在某些 .gitattributes
文件中找到。这告诉 Git *.txt
文件是文本,因此任何 CRLF 转换都适用,而 *.jpg
文件是二进制的,它们不会(并且将使用“jpg”生成差异diff 驱动程序:参见 gitattributes)。 version
文件将在执行 git archive
时执行哈希 ID 替换。 所有这些操作都发生在文件上。如果我们有:
sub/ -text
这根本什么都不做,因为目录 sub
永远不会在 Git 的索引中(因此影响 index/worktree 转换的操作不适用)并且只会作为树存储在 Git 存储库中(再次参见脚注 1),您(用户)没有可用的操作或选项。如果你想让sub
内的文件被当作二进制文件,你可以这样写:
sub/* -text
sub/**/* -text
诚然,这是两行,如果 Git 允许 sub/
作为此处的模式,则其中一行会起作用。所以能够通过目录前缀命名文件可能有点用处,但缺乏这种能力并不是一个严重的问题。
异常和未来的考虑
此处的一个例外是 export-ignore
属性。 git archive
命令从某个存储库中的特定提交构建 tar 或 zip 存档,服从正在存档的快照中的 .gitattributes
文件,如果该文件说不导出一些文件 或目录 ,它不会导出该文件或目录中包含的任何文件。
因为 确实 属性进行目录匹配似乎是明智的,未来的某个 Git 版本可能会这样做,这样你就可以写,例如, assets/ -text
。不过,这并没有比 assets/* -text
多多少,目前你必须使用类似后者的东西。
1当Git提交时,Git为顶级目录创建一个树对象文件和子目录,每个包含文件的子目录再加一个树对象。它基于 Git 的 index 的内容来执行此操作,它不存储目录本身。索引仅存储 文件.
从技术上讲,对于每个文件,索引中的内容是一个信息元组:。您可以通过 运行 git ls-files --stage
查看此信息。这增加了缓存数据和标志——您也可以通过将 --debug
添加到 git ls-files
命令来看到其中的大部分内容——但是阶段编号通常为零的四元组是关键索引的一部分。这是 Git 存储库索引中 Git 本身的示例:
100644 c2f5fe385af1bbc161f6c010bdcf0048ab6671ed 0 .cirrus.yml
100644 c592dda681fecfaa6bf64fb3f539eafaf4123ed8 0 .clang-format
100644 f9d819623d832113014dd5d5366e8ee44ac9666a 0 .editorconfig
100644 b08a1416d86012134f823fe51443f498f4911909 0 .gitattributes
100644 e7b4e2f3c204c2c94c60222abbc702bd7d72de39 0 .github/CONTRIBUTING.md
100644 952c7c3a2aa11ea1087390be61eab6f7c0013599 0 .github/PULL_REQUEST_TEMPLATE.md
100644 84a5dcff7a05fb724d78826212c5fa22ba5df958 0 .github/workflows/main.yml
100644 ee509a2ad263989fcebe3c3543aa32efed1cacda 0 .gitignore
100644 cbeebdab7a5e2c6afec338c3534930f569c90f63 0 .gitmodules
100644 bde7aba756ea74c3af562874ab5c81a829e43c83 0 .mailmap
只要所有文件都处于零阶段,Git 就可以将索引变成树对象。树对象在那些条目之上包含一个子树,这些条目中似乎有一个目录名,例如 .github/*
文件。在索引中,目录不存在:文件只是命名为 .github/CONTRIBUTING.md
等等。但是,在一次提交中,它们变成了子树。请注意,一个文件名为 github/workflows/main.yml
,因此 .github
子树将包含一个名为 workflows
的子树,该子树将包含 main.yml
文件。
从某种意义上说,Git 存储 文件以类似目录树的方式;但它 使用 这些文件,当你在你的存储库中工作时,不考虑目录,因为索引将目录展平。这可能有点设计错误,因为这是 Git 无法正确存储空目录的主要原因。 (已经进行了各种尝试来解决这个问题,唯一正确工作的方法——使用空子模块——非常笨拙。)
我原以为 **/
可以匹配 git 存储库中的任何目录,但实际上它什么都不匹配。 man gitattributes
说:
patterns that match a directory do not recursively match paths inside that directory (so using the trailing-slash path/ syntax is pointless in an attributes file; use path/** instead)
但在我的例子中 **/
甚至不匹配 目录本身 。我的意思是,如果我的存储库中有 a/b.txt
,那么 **/
甚至不匹配目录 a
。我期待它匹配目录 a
但不匹配文件 a/b.txt
.
如果我将 **/
更改为 **
,则目录及其内容都匹配。在同一示例中,dir a
和 file a/b.txt
都匹配。
那么 git 属性如何作用于目录?如果 git 认为目录的属性无关紧要,为什么 git 在 **
的情况下列出它们?
与 执行 特殊情况目录处理的 .gitignore
不同,似乎 .gitattributes
代码从不处理特殊情况,但有一个重要的例外一个目录。这部分是因为 Git 并不真正 store 目录。1 所以每个属性都应用于某些文件集。尽管如此,文件 do 驻留在目录中——在两个真实的操作系统中,以及它们在提交中的存储方式(见脚注 1)——所以它可能对 .gitattributes
对尾部斜线有特殊情况,而不是将属性应用于文件。
不过正如您所观察到的,这并没有发生。尽管 attr.c
(path_matches
中)有一些代码检查目录。但这并不重要,因为 Git 属性的有用部分(同样有一个例外) 无论如何只适用于文件.
我上面强调的文字是这个意思。考虑,例如:
*.txt text
*.jpg -text diff=jpg
version export-subst
可能会在某些 .gitattributes
文件中找到。这告诉 Git *.txt
文件是文本,因此任何 CRLF 转换都适用,而 *.jpg
文件是二进制的,它们不会(并且将使用“jpg”生成差异diff 驱动程序:参见 gitattributes)。 version
文件将在执行 git archive
时执行哈希 ID 替换。 所有这些操作都发生在文件上。如果我们有:
sub/ -text
这根本什么都不做,因为目录 sub
永远不会在 Git 的索引中(因此影响 index/worktree 转换的操作不适用)并且只会作为树存储在 Git 存储库中(再次参见脚注 1),您(用户)没有可用的操作或选项。如果你想让sub
内的文件被当作二进制文件,你可以这样写:
sub/* -text
sub/**/* -text
诚然,这是两行,如果 Git 允许 sub/
作为此处的模式,则其中一行会起作用。所以能够通过目录前缀命名文件可能有点用处,但缺乏这种能力并不是一个严重的问题。
异常和未来的考虑
此处的一个例外是 export-ignore
属性。 git archive
命令从某个存储库中的特定提交构建 tar 或 zip 存档,服从正在存档的快照中的 .gitattributes
文件,如果该文件说不导出一些文件 或目录 ,它不会导出该文件或目录中包含的任何文件。
因为 确实 属性进行目录匹配似乎是明智的,未来的某个 Git 版本可能会这样做,这样你就可以写,例如, assets/ -text
。不过,这并没有比 assets/* -text
多多少,目前你必须使用类似后者的东西。
1当Git提交时,Git为顶级目录创建一个树对象文件和子目录,每个包含文件的子目录再加一个树对象。它基于 Git 的 index 的内容来执行此操作,它不存储目录本身。索引仅存储 文件.
从技术上讲,对于每个文件,索引中的内容是一个信息元组:git ls-files --stage
查看此信息。这增加了缓存数据和标志——您也可以通过将 --debug
添加到 git ls-files
命令来看到其中的大部分内容——但是阶段编号通常为零的四元组是关键索引的一部分。这是 Git 存储库索引中 Git 本身的示例:
100644 c2f5fe385af1bbc161f6c010bdcf0048ab6671ed 0 .cirrus.yml
100644 c592dda681fecfaa6bf64fb3f539eafaf4123ed8 0 .clang-format
100644 f9d819623d832113014dd5d5366e8ee44ac9666a 0 .editorconfig
100644 b08a1416d86012134f823fe51443f498f4911909 0 .gitattributes
100644 e7b4e2f3c204c2c94c60222abbc702bd7d72de39 0 .github/CONTRIBUTING.md
100644 952c7c3a2aa11ea1087390be61eab6f7c0013599 0 .github/PULL_REQUEST_TEMPLATE.md
100644 84a5dcff7a05fb724d78826212c5fa22ba5df958 0 .github/workflows/main.yml
100644 ee509a2ad263989fcebe3c3543aa32efed1cacda 0 .gitignore
100644 cbeebdab7a5e2c6afec338c3534930f569c90f63 0 .gitmodules
100644 bde7aba756ea74c3af562874ab5c81a829e43c83 0 .mailmap
只要所有文件都处于零阶段,Git 就可以将索引变成树对象。树对象在那些条目之上包含一个子树,这些条目中似乎有一个目录名,例如 .github/*
文件。在索引中,目录不存在:文件只是命名为 .github/CONTRIBUTING.md
等等。但是,在一次提交中,它们变成了子树。请注意,一个文件名为 github/workflows/main.yml
,因此 .github
子树将包含一个名为 workflows
的子树,该子树将包含 main.yml
文件。
从某种意义上说,Git 存储 文件以类似目录树的方式;但它 使用 这些文件,当你在你的存储库中工作时,不考虑目录,因为索引将目录展平。这可能有点设计错误,因为这是 Git 无法正确存储空目录的主要原因。 (已经进行了各种尝试来解决这个问题,唯一正确工作的方法——使用空子模块——非常笨拙。)