如何使用.gitignore忽略目录中除一个文件以外的所有内容? [英] How to use .gitignore to ignore everything in a directory except one file?
问题描述
我在So上找到了此问题的几个据称的解决方案,但由于某些未知原因,这些解决方案都不适用于我。
我需要忽略给定文件夹中除一个特定文件之外的所有内容。很简单,对吧?不要那么快。
我几乎尝试了所有这些问题的建议答案:
- Make .gitignore ignore everything except a few files
- Using .gitignore to ignore everything but specific directories
- Make .gitignore ignore everything except one file
- .gitignore - Ignore everything in a directory except one file
- How to ignore everything in a directory except one file
- gitignore everything except specific files
...但我的进展并不比我开始时更大。
以下是要包含的文件的路径:
D:ProjectsWebsiteWebsiteinSettings.json
回购地址:
D:ProjectsWebsite
我的.gitignore
文件是由Visual Studio生成的,因此它包含以下条目:
[Bb]in/
根据上面问题的许多答案,我应该能够做这样的事情:
!/Website/[Bb]in/Settings.json
...但这不管用。该文件仍被忽略。
这些排列都不起作用:
!*/Settings.json
!**/Settings.json
![Bb]in/Settings.json
![Bb]in/**/Settings.json
![Ww]ebsite/[Bb]in/Settings.json
!Website/bin/Settings.json
!/Website/bin/Settings.json
我还尝试在bin
中放置一个单独的.gitignore
文件:
# Don't block Settings.json
!Settings.json
!.gitignore
运气不好。
如何阻止[Bb]in
中除Settings.json
文件以外的所有内容?
预期结果:
WebsiteinSettings.json
未被忽略实际结果:
WebsiteinSettings.json
继续被忽略
推荐答案
添加到LeGEC's answer,这很好,我注意到您评论:
这很管用。这让我觉得有点脆弱(也许这只是我的想象,希望我会被证明是大错特错),但如果这是唯一的方法,我可以接受它。
这不是唯一的方法,我也有同样的感觉,觉得它很脆弱,或者以其他方式微妙地错了。它确实有效,在正常的日常使用中它不会中断,但在我看来,仅仅因为在您开始进行新的提交时,在您提取的提交中跟踪它们而跟踪它们,这似乎是错误的。
这里的诀窍是,Git路径名Website/bin/Settings.json
生成一个文件,该文件在解压缩后位于文件夹中:文件Settings.json
位于文件夹bin
中(而文件夹位于Website
文件夹中,但这只是添加到堆中;一个位于文件夹中的层就足够了)。
请注意,对于Git来说,Website/bin/Settings.json
只是一个文件名:该文件名就像这样存储在Git的索引(也称为临时区域)中,并带有正斜杠。1问题在以后Git扫描工作树时发生。Git所做的排除处理-使用.git/info/exclude
和各种.gitignore
文件-通过工作树文件工作。它必须:它都是关于未跟踪的文件,而未跟踪的文件的定义就是存在于您的工作树中但不在Git的索引中的文件。
当Git将当前(HEAD
)提交的内容(当前提交中存储的一组文件及其所有数据)与索引/暂存区域中的文件进行比较时,Git根本不需要也不会查看工作树。Git需要的一切都在存储库中:当前提交是通过读取HEAD
确定的,HEAD
解析为提交散列ID,提交散列ID解析为内部树对象,该对象为Git获取所有文件名和模式及其散列ID。建议的下一个提交位于索引/暂存区域,包含文件名和模式及其散列ID。散列ID让Git知道文件是否100%匹配,对于大多数目的,这就是我们所关心的:git status
只是打印M
表示修改,或者modified
这个词,而没有找出实际更改了。
阅读工作树:嗯,这是更难的。操作系统在这里阻碍了我们的发展。当然,可能有一个C库scandir
或readdir
函数,或者其他一些方法来枚举文件夹的内容。但Git可能仍然需要对每个名称调用lstat
。2在任何情况下,如果您分析了git status
花费超过20纳秒的计时结果,您会发现它花费了大量时间来读取目录。如果我们能找到一些捷径,不是很好吗?
输入.gitignore
和其他排除文件:如果我们阅读顶层工作树并找到名为tmp
和zorg
的目录,但这些目录被忽略-通过*
或*/
或tmp
或tmp/
或其他任何方式-为什么,我们甚至不必打开和阅读它们!./tmp
是包含一个文件,还是包含10亿个文件并不重要:我们将跳过整个内容!考虑到仅仅打开和读取目录以找到其文件名就可能需要几毫秒的时间--并且在每个名称上使用lstat
可以添加更多--这是一个巨大的节省。
因此,Git做到了这一点。如果Git正在准备工作树遍历,且允许跳过查看某个文件夹/目录,则不会跳过查看该文件夹。因此,如果您的.gitignore
文件为:
*
然后任何目录名都将匹配,Git将跳过打开目录,更不用说读取目录了。您的Website
文件夹会发生这种情况。
如果.gitignore
显示为:
*
!Website
但是,当Git读取顶级目录并找到名称Website
时,不能忽略它。因此,Git打开Website
文件夹并找到bin
等内容。但是:bin
匹配*
,而不匹配Website
,因此可以忽略。这意味着Git可以直接跳过它,永远不会查看它的内部。您需要添加Website/bin
:
*
!Website
!Website/bin
现在Git必须打开Website/bin
并阅读它。其中的每个文件和目录都可以忽略,因此要使而不是忽略其中的Settings.json
,我们需要列出该文件:
*
!Website
!Website/bin
!Website/bin/Settings.json
这个相当小的.gitignore
文件将起作用。然而,它确实有一个缺陷。如果bin
中存在名为Website
的文件或目录,则不会忽略该文件或目录。如果没有被忽略,Git会抱怨它没有被跟踪,或者添加git add .
,或者其他不受欢迎的行为。要解决这个问题,我们应该确保只匹配Website
,而不是bin/Website
。这将我们带到Git排除规则的第二个棘手部分。
1索引项的格式有点凌乱,并且会被压缩,具体取决于索引格式版本(有几个版本),但git ls-files --stage
将转储主要内容,在那里,您将看到使用嵌入的正斜杠命名的文件。当然,Git能够处理和理解Windows在这里使用的反斜杠,因此将文件存储在Website
目录的bin
文件夹中。
Git索引中的字符串区分大小写,并且以UTF-8或等效格式存储,无论文件名如何存储在文件系统中,也不管文件系统的文件名是否区分大小写。
2一些readdir
变体包括一个类型字段,例如DT_DIR,如果您可以依赖它,有时可以跳过这一步;这可以节省大量的时间。我不知道Git是否会尝试这样做:工作树代码已经修改了多次,现在具有fsmonitor代码的所有复杂性,这是一种不同的加速方式,所以我最近没有看过。
另一个棘手的部分:固定名称与非固定名称
为了正确理解这一部分,我喜欢借用正则表达式中的一个概念:将锚定到左边或右边的想法。在像me*s
这样正则表达式中,我们将匹配ms pacman
和message
,但不匹配memory
,因为我们要查找m
,然后是任意数目的e
,然后是s
,而memory
没有s
。但我们也会匹配acmestorage
,因为m
后面跟着一个e
,然后s
,嵌入在acme
和storage
(一起运行)中。我们可以通过锚定左侧的^m*s
不匹配acmestorage
来避免出现这种情况,因为m
必须是第一个字母。
(通常,RES还允许我们使用$
锚定在右侧。每种RE语法都有它自己的特点,.gitignore
文件使用GLOB语法而不是RE语法,所以我们不要太深入。只要记住锚定的想法:把火柴粘在左边或右边,或者两个都粘上。在Git的例子中,锚定的路径是完全匹配的,两边都卡住了。这是因为右侧始终锚定。您必须使用path/*
或path/**
来允许任意右侧部分。)
在我们的例子中,使用.gitignore
,我们希望确保Website
只在顶层匹配,我们将.gitignore
放在其中。为此,我们可以以前斜杠开头:
*
!/Website
!Website/bin
!Website/bin/Settings.json
现在bin/Website
将不会与第二行匹配:第二行定位在扫描的顶层(根)目录,而bin/Website
不在该层:它是下一层。
您可能认为我们应该对所有三个文件名执行此操作:
*
!/Website
!/Website/bin
!/Website/bin/Settings.json
这是可行的,但不是必需的,原因是如果.gitignore
条目中嵌入了斜杠,则条目将自动锚定。Website/bin
中有一个不在两端的斜杠,因此它会自动锚定。Website/bin/Settings.json
有两个这样的斜杠,也是锚定的。
更复杂的部分
我暗示这里只有两个棘手的部分。我撒谎了。😀排除文件还有一种使用斜杠的方法,不幸的是这很棘手,那就是最后的斜杠使条目与只匹配目录名。即:
bin/
与bin
目录匹配,但与名为bin
的文件不匹配。
此规则独立于其余规则:
- 前导
!
表示否定整个事情,因此!/Website/
表示不要忽略。 - 前导
/
(在任何前导!
之后)或任何不在末尾的嵌入斜杠表示锚定,因此!/Website/
被锚定。 - 尾随
/
表示仅当它是目录时,因此!/Website/
只与目录匹配。尾部斜杠不适用于锚定目的(并且永远不应使用双尾部斜杠),因此如果您想要锚定,请确保包括前导斜杠或嵌入斜杠。
使用所有规则,我们得出:
*
!/Website
!Website/bin
!Website/bin/Settings.json
这是完整和正确的(假设我这里有正确的大小写:请记住,Git将区分大小写,无论您的文件系统是什么)。但我们还可以使用另一个技巧,使文件稍微短一些。假设我们这样写:
*
!*/
!Website/bin/Settings.json
Git将:
- 打开并读取顶层工作树目录;
- 对于每个文件,忽略它(
*
); - 对于每个目录,不是忽略它(
!*/
); - 找到
Website
目录,打开并阅读; - 对于
Website/
中的每个文件,忽略(*
); - 查找目录
bin
和不忽略它(!*/
); - 打开并阅读
Website/bin
目录; - 找到每个文件并将其忽略(
*
)除外Website/bin/Settings.json
。
这个三行版本的缺点是,在上述处理过程中,Git将打开并读取每个目录,包括每个目录的每个子目录,因此如果存在包含10亿个文件的顶级tmp
目录(直接或递归后),Git将花费时间检查每个文件。也就是说,!*/
完全破坏了在某些情况下可以节省大量时间的优化。
如果Git的排除代码足够聪明,能够在您编写以下代码时意识到这一点,那就好了:
*
!Website/bin/Settings.json
它应该自动将!/Website/
和!/Website/bin/
注册到其排除列表中(如果它们还不存在)。这看起来很简单。(具体如何进行否定和锚定取决于这里的内部数据结构,这是我十多年来没有研究过的……)
这篇关于如何使用.gitignore忽略目录中除一个文件以外的所有内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!