技术的区别已经很明显了。如果您正在使用一些处理 **,1 的 fnmatch 函数并作为模式和字符串对传入:
fnmatch(pattern="foo/**", string="foo/bar/baz")
它将匹配。但是,使用模式 foo/*,它不会匹配。
但是,由于.gitignores 的处理方式,这里对于纯正 模式没有任何意义。那是由于您用斜体字标注的那句话。 Git 在深度优先搜索工作树之前或期间读取排除文件(.gitignore、.git/info/exclude 和您的全局排除文件)。这种深度优先搜索使用这种一般形式的代码。我在这里使用 Python 作为语法,但并没有真正尝试使其全部工作(也没有尝试过任何效率方面的尝试,与 Git 相比,从内部来说,效率低下)。
# call the given function fn on each file in the directory
# (note that we have already committed to reading the directory).
def search(dir, excludes, fn):
try:
with open(os.path.join(dir, ".gitignore")) as stream:
excludes = excludes.more(dir, stream)
except FileNotFoundError:
pass # ignore the lack of a .gitignore
all_files = os.listdir(dir)
for name in all_files:
full_path = os.path.join(dir, name)
is_dir = os.path.isdir(full_path)
if excludes.is_excluded(name, path, is_dir):
continue # don't add this file or search this directory
if is_dir:
search(full_path, excludes, fn)
else:
fn(full_path)
(我们将通过cd-ing 到工作树的顶部并使用search(".", repo.top_excluder, add_file) 或类似的东西来启动整个事情。这里的top_excluder 字段包含我们的全局和每个repo 模式。请注意, excludes.more() 必须使用在递归 search 调用返回时自动清除子目录排除项的数据结构,并且需要处理排除程序文件优先级,因为更深的 .gitignore 覆盖了外层 .gitignore。)
这种处理排除目录的方式是它从不费心去查看它的内部。这就是事实的根源,即仅给定积极的排除(没有!foo/** 之类的东西),这里不需要**:如果我们确定将排除某个目录,那么它已经与它。
但我们不仅有积极的模式:我们也有消极的模式。例如,考虑这个非常简单的.gitignore 文件:
# ignore things named skip unless they're directories
*skip
!*skip/
否定,!*skip/,覆盖*skip,但仅当文件名为fooskip 或barskip 或任何实际是一个目录。所以我们确实查看了fooskip/ 内部,当我们在那里时,我们跳过另一个名为quuxskip 的文件,但不是名为plughskip 的子目录。
这意味着打败 Git 优化的一个简单方法是:
!*/
这样的一行,放置在.gitignore 文件的适当位置(靠近或在末尾),会导致搜索所有目录,即使它们会被忽略规则。也就是说,我们的excludes.is_excluded() 调用将接收本地文件名——不管它是什么——和一个用于is-a-directory 测试的True 标志,以便*/ 匹配它;前缀! 表示该目录不被忽略,因此我们将递归搜索它。
这一行完全放弃了 Git 在此处尝试进行的优化,因此如果您有应该忽略的目录,则相对昂贵。但是,如果您不想使用更冗长的方法,这是使.gitignore 表现良好的一种非常快速而肮脏的方法。也就是说,而不是:
foo/*
!foo/one/
foo/one/*
!foo/one/is/
foo/one/is/*
!foo/one/is/important/
foo/one/is/important/*
!foo/one/is/important/this-file
你可以简单地写:
foo/**
!foo/one/is/important/this-file
!foo/**/
这将迫使 Git 费力地搜索整个 foo 目录及其所有子目录,以便 foo/one/is/important/this-file 文件可以匹配第二条规则。这里我们需要双 *,因为它们以 foo/ 为前缀;如果我们将这个.gitignore 文件放入foo/.gitignore,我们可以使用更简单的* 形式:
*
!one/is/important/this-file
!*/
无论如何,这是一般原则,也是** 有用的原因。
(请注意,您也可以在进行第一次保存它的提交之前将一个重要文件强制添加到 Git 的索引中,或者在创建将忽略它的 .gitignore 规则之前添加它。我不喜欢这个特别的技巧我自己,因为这意味着你在 Git 的索引中携带了一个文件,如果它不小心从 Git 的索引中删除,将不会被重新添加。)
1请注意,POSIX 和 Python fnmatch 首先不处理这些问题。在 Python 中,您需要glob.glob。当然,Git 一开始并没有将这些公开为函数调用。