.gitignore 文件中的单个 /* 和双 /** 尾随星号有什么区别?

blu*_*r4y 6 git gitignore fnmatch

.gitignore考虑文件中的以下两种模式

foo/*
foo/**
Run Code Online (Sandbox Code Playgroud)

模式格式规范指出:

星号*可以匹配除斜杠之外的任何内容。[...]

尾随/**匹配里面的所有内容。例如,abc/**匹配 目录 内的所有文件abc(相对于 .gitignore 文件的位置),具有无限深度。

当在斜杠之后直接在模式末尾使用时,这对我来说听起来是同样的事情。我确实测试了一些案例 - 有或没有下面的子目录foo以及各种否定模式 - 并且没有观察到任何差异。

有没有一种情况会让人做出/**选择/*


起初,我期望看到一个具有如下模式的用例,但没有,因为这两种模式都会忽略内部的所有内容,并且规范还表示“[...]不可能重新包含一个文件(如果该文件的父目录被排除)[...]”

foo/*
!foo/a/b/c/file.txt

foo/**
!foo/a/b/c/file.txt
Run Code Online (Sandbox Code Playgroud)

tor*_*rek 2

技术差异非常明显。如果您使用某些 fnmatch 函数来处理**, 1并作为模式和字符串对传入:

\n
fnmatch(pattern="foo/**", string="foo/bar/baz")\n
Run Code Online (Sandbox Code Playgroud)\n

匹配。但是,使用模式foo/*,它不会匹配。

\n

然而,由于处理方式的原因,纯粹的积极.gitignore模式在这里没有任何意义。这是由于您用斜体字标注的句子造成的。Git在通过工作树进行深度优先搜索之前或期间读取排除文件(、和全局排除文件)。这种深度优先搜索使用这种通用形式的代码。我在这里使用了 Python 作为语法,但并没有真正尝试让它全部工作(也没有尝试提高效率,与 Git 相比,从内部来说,Git 效率低下)。.gitignore.git/info/exclude

\n
fnmatch(pattern="foo/**", string="foo/bar/baz")\n
Run Code Online (Sandbox Code Playgroud)\n

(我们将通过cd-ing 到工作树的顶部并使用search(".", repo.top_excluder, add_file)或类似的东西来启动整个事情。这里的 top_excluder 字段携带我们的全局和每个存储库模式。请注意,excludes.more()必须使用自动的数据结构当递归search调用返回时清除子目录排除,并且需要处理排除程序文件优先级,因为更深的层.gitignore会覆盖外层.gitignore。)

\n

它对待排除目录的方式是它根本不费心去查看它的内部。这就是事实的根源,仅考虑到积极排除(没有!foo/**任何事情),这里不需要**:如果我们确定某个目录将被排除,那么它已经被排除了以及其中的一切。

\n

但我们不仅有积极的模式:我们也有消极的模式。例如,考虑这个非常简单的.gitignore文件:

\n
# ignore things named skip unless they\'re directories\n*skip\n!*skip/\n
Run Code Online (Sandbox Code Playgroud)\n

否定 ,!*skip/会覆盖,但仅当名为或的*skip文件实际上是一个目录时。因此,我们确实查看内部,当我们进入其中时,我们会跳过另一个名为 的文件,但不会跳过名为 的子目录。fooskipbarskipfooskip/quuxskipplughskip

\n

这意味着击败 Git 优化的一个简单方法是:

\n
!*/\n
Run Code Online (Sandbox Code Playgroud)\n

这样的行放置在.gitignore文件的适当位置(靠近或末尾),会导致搜索所有目录,即使它们会被忽略规则忽略。也就是说,我们的excludes.is_excluded()调用将接收本地文件名\xe2\x80\x94(无论它是什么)\xe2\x80\x94 和Trueis-a-directory 测试的标志,以便*/与其匹配;前缀!意味着该目录不会被忽略,因此我们将递归搜索它。

\n

这一行完全放弃了 Git 试图在这里进行的优化,因此如果您有应该忽略的目录,那么它的成本相对较高。.gitignore但如果您不想使用更冗长的方法,那么这是一种非常快速且肮脏的方法来使行为良好。也就是说,而不是:

\n
foo/*\n!foo/one/\nfoo/one/*\n!foo/one/is/\nfoo/one/is/*\n!foo/one/is/important/\nfoo/one/is/important/*\n!foo/one/is/important/this-file\n
Run Code Online (Sandbox Code Playgroud)\n

你可以简单地写:

\n
foo/**\n!foo/one/is/important/this-file\n!foo/**/\n
Run Code Online (Sandbox Code Playgroud)\n

这将迫使 Git 费力地搜索整个foo目录及其所有子目录,以便该foo/one/is/important/this-file 文件可以与第二条规则匹配。这里我们需要 double,*因为它们的前缀是foo/; 如果我们将此.gitignore文件放入,foo/.gitignore我们可以使用更简单的单一*形式:

\n
*\n!one/is/important/this-file\n!*/\n
Run Code Online (Sandbox Code Playgroud)\n

无论如何,这是一般原则,也是一个**有用的理由。

\n

(请注意,您也可以在第一次提交之前将一个重要文件强制添加到 Git 的索引中,或者在创建.gitignore将忽略它的规则之前添加它。我自己不喜欢这个特殊的技巧,因为这意味着您在 Git 的索引中保存了一个文件,如果该文件被意外地从 Git 的索引中删除,则不会重新添加该文件。)

\n
\n

1请注意,POSIX 和 Python 一fnmatch开始并不处理这些问题。在 Python 中,您可能需要glob.glob. 当然,Git 首先不会将这些公开为函数调用。

\n