在存储库的嵌套文件夹中设置多个 .gitignore 文件的正确方法
Proper way to setup multiple .gitignore files in nested folders of a repository
我有以下文件夹结构:
Project/
.git/
.gitignore #1
a/
a1/
a2.txt
a3.txt
.gitignore #2
b/
b1.txt
c.txt
我希望 git
不会忽略 a2.txt
,并且不会忽略整个 b/
。其他一切都应该被忽略。
根据suggestions/comments/answers提供,.gitignore #1
的内容为:
a/
c.txt
这基本上会忽略 c.txt
和文件夹 a/
中的所有内容,后者不会被更深的嵌套 .gitignore
.
覆盖
.gitignore #2
的内容是:
!a1/a2.txt
我希望这个嵌套更深的 gitignore
文件不会忽略文件 a2.txt
.
然而,运行 git status --ignored
导致:
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
b/
Ignored files:
(use "git add -f <file>..." to include in what will be committed)
a/
c.txt
nothing added to commit but untracked files present (use "git add" to track)
也就是说,整个 a/
似乎都被忽略了,尽管我希望 .gitignore #2
会提供例外情况。
如何正确使用嵌套 .gitignore
来实现上述要求?
(注意:我只将上面描述中的 .gitignore
文件命名为 #1
和 #2
以明确区分两者。在我的实际计算机中,这些文件正确命名为 .gitignore
.)
这里的一般规则是这样的:
- Git 将使用 OS 的工具来读取目录。
- 要扫描目录,Git 调用
opendir
和关联的 readdir
(最终 closedir
)函数。
readdir
然后 returns 目录条目 ,一次一个。每个条目都包含一个 name component,定义如下。条目 可能 还包含其他信息——特别是目录与文件的区别——但这就是 Git 在这里真正可以指望的了。如果 OS 用 DT_DIR
、DT_FILE
等填充 d_type
字段,Git 将尝试使用它,否则 Git 可能不得不退回到调用 lstat
(这很昂贵)。
阅读整个目录后,Git 现在有了一组名称组件。 name component 基本上是 path-name 中斜杠之间的部分:例如,对于 path/to/file.ext
我们有三个组件,path
,to
,以及 file.ext
。请注意,/path/to/file.ext
也是如此:前导斜杠仅表示“从顶部”而不是“从我们在树中的任何位置”。 Git 做了一些(相当奇特的)使用相同的想法——以斜杠开头的路径是“根相对”,其余的是“当前位置相对”——当在 .gitignore
中使用“锚定”条目时文件(见下文)。因此,如果 path/to/file
存在于工作树的顶层,则 Git 在扫描顶层目录时将只看到 path
部分。
(旁注:POSIX 也包括 scandir
,但人们发现这个接口很难正确使用。在某些系统上它在各种意义上也“更高效”,尽管并非总是如此或者可以预见,使用较低级别的 readdir
例程,而 Git 使用 readdir
。)
现在 Git 有了名称组件,Git 可以根据此特定级别的 .gitignore
检查它们(如果存在)。它还可以将每个组件与首先在此处获得 Git 的任何前导路径名组合起来。 对于初始扫描,没有这样的前导组件,也没有合并发生,但让我们在下面观察如果允许我们继续进入 path/
(这是一个目录)会发生什么.
组件现在可能需要 类型 检查:文件与目录。 .Real-world 文件系统可能有其他类型,包括 symbolic link,但为了我们这里的目的 symbolic link 暂时被当作一个文件来对待。我们只想知道组件是否代表一个目录。
现在,我们目前阅读的任何 .gitignore
文件中的条目——包括我们正在阅读的 this 目录中的条目——被标记为三个独立方式:
有些是 锚定的 ,例如 /path/to
或 a/b
,有些不是,例如 [=34] =] 例如。 anchored 条目是包含任何斜杠的条目 在 删除单个尾部斜杠(如果存在)之后。
一些仅用于 目录,一些用于 所有名称。如果条目以尾部斜杠结尾,则条目被标记为 directory-only。 (由于尾部斜杠是“仅目录”标志,因此在决定是否设置“锚定”标志时必须忽略它。)
有些是肯定的(“忽略”)条目,有些是否定的(“不要忽略”)条目。否定条目是以 !
作为第一个字符开头的条目。 (/path
的锚定否定条目必须阅读 !/path
;/!path
在这里不起作用。)
所以假设我们正在读取顶层,或者我们正在读取顶层内的目录 path
。假设我们在此级别遇到两个名称组件:path
和 to
。我们现在或多或少同时检查所有这些东西(按顺序,以便“最后输入”覆盖):
根据所有 non-anchored 忽略表达式检查 目录条目 本身。 path
是其中任何一个的匹配项吗?如果是这样,根据 positive/negative 标志,此名称为 ignored/unignored。
检查到目前为止的完整路径 与所有anchored 忽略表达式。对于 path
,这是 /path
、/path/path
或 /to/path
;对于 to
,这是 /to
、/path/to
或 /to/to
之一。 (请记住,我们同时找到了 /path
和 /to
,并且我们可能正在查看两者的内部。)如果此 path-so-far 与其中一个锚定表达式匹配,则此名称为 ignored/unignored 根据 positive/negative 标志。
请注意,当我们检查锚定路径时,我们正在查看工作树中的完整路径,而.gitignore
本身可能来自sub-path 在 .gitignore
树中。因此,如果我们正在读取目录 /path
并且我们有/path/.gitignore
并且它有一个锚定条目 /xyzzy
,我们实际上是在检查这个 /xyzzy
与 /path/xyzzy
(因为它来自 /path/.gitignore
,而不是来自 /.gitignore
).这有点复杂,但是一旦你考虑它就有意义了:锚点是相对于 .gitignore
的位置的。这使您可以重命名目录,而无需编辑任何子 .gitignore
文件中的所有锚定路径。
进一步注意,“匹配”测试可能需要目录条目本身命名一个目录。如果 ignore 条目被标记为 directories-only,就会出现这种情况。因此,要检查这一点,我们需要知道条目——例如 path
或 to
——是否命名了 OS 文件系统中的一个目录。
至此,我们已经完成了对 this 条目必须进行的所有检查。它要么匹配了一些 .gitignore
条目,在这种情况下,匹配 .gitignore
的 last 就是那个条目,或者没有。并且,子目录 .gitignore
在链的后面匹配,因此 deepest .gitignore
可以 匹配此条目将始终具有 last 匹配项,如果它有匹配项的话。
如果此条目不匹配任何 .gitignore
规则,则此特定名称不会 被忽略。如果它 did 匹配 .gitignore
规则,则最后一个的 positive/negative 标志决定是否忽略此特定名称。
现在我们知道名字是否被忽略了,我们有两个选项,每个选项都有两个sub-options:
被忽略:
- 如果是目录,我们根本不扫描它。
- 如果是 文件,我们不会 auto-add 文件(例如
git add .
),或者 git status
,我们不会抱怨 未跟踪 文件(假设它实际上未被跟踪)。
不忽略:
- 如果它是一个目录,我们递归扫描它并应用所有这些规则。
- 如果它是一个 文件,我们
git add
它(例如 git add .
)或者如果它没有被追踪一定要投诉(git status
).
这决定了 git status
是否抱怨它未被跟踪(对于 git status
命令)或者是否 git add
某种递归风格(git add --all
,git add .
, git add somedir
, 等等) 添加它。
请注意,您可以使用 git add --force
覆盖忽略条目,例如,即使 ignored-file
会被正常的 .gitignore
规则忽略,git add --force ignored-file
也会添加它。我从来没有尝试 git add --force .
看看这里发生了什么,但它可能不太好。它可能完全忽略所有 .gitignore
规则,这看起来很糟糕,或者它可能完全遵守它们,这看起来也很糟糕。我会把它留给 reader 来尝试,看看它做了什么,然后决定它有多糟糕。
另请注意,一旦某个路径名出现在 Git 的索引中,并且 Git 的索引包含 完整路径名 ,例如,path/to/file
,作为其中包含文字斜杠的文字字符串——该文件 不会被忽略 即使它列在 .gitignore
文件中。忽略规则特定于递归目录移动过程,但是 Git 的索引 中列出的文件被 跟踪并且 被 检查en-masse git add .
操作。一旦你越过了 OS-interaction 的东西并进入 Git 正确的地方,文件就不再有“包含目录”,它们只有长路径字符串,如果需要的话,它们会嵌入正斜杠。
Git 的索引无法存储裸目录名称,1,这就是您无法提交空目录的原因。扫描过程将 扫描 目录以获取 文件 并将(在适当的条件下)将这些文件添加到索引中,但不会添加包含目录。最接近的 Git 是 submodule 条目存储为 so-called gitlink ,一个带有 mode 160000
的“文件”,如果它是一个 Linux file-system 实体,它将是 directory-and-symbolic-link 的组合(这在文件系统中是不允许的)。这就是 store an empty directory 尝试出错的原因(但您可以存储没有文件的子模块!)。
1从技术上讲,它可以,只是不能存储为Git使用的那种条目为下一次提交跟踪 files。 Git 的索引增长了一大堆奇怪的 add-ons 以提高效率,其中包括跟踪未跟踪的内容(so-called 未跟踪的缓存 ),其中包括未跟踪的目录。所以它不能 track 一个目录但是它可以 untrack 一个!
我有以下文件夹结构:
Project/
.git/
.gitignore #1
a/
a1/
a2.txt
a3.txt
.gitignore #2
b/
b1.txt
c.txt
我希望 git
不会忽略 a2.txt
,并且不会忽略整个 b/
。其他一切都应该被忽略。
根据suggestions/comments/answers提供.gitignore #1
的内容为:
a/
c.txt
这基本上会忽略 c.txt
和文件夹 a/
中的所有内容,后者不会被更深的嵌套 .gitignore
.
.gitignore #2
的内容是:
!a1/a2.txt
我希望这个嵌套更深的 gitignore
文件不会忽略文件 a2.txt
.
然而,运行 git status --ignored
导致:
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
b/
Ignored files:
(use "git add -f <file>..." to include in what will be committed)
a/
c.txt
nothing added to commit but untracked files present (use "git add" to track)
也就是说,整个 a/
似乎都被忽略了,尽管我希望 .gitignore #2
会提供例外情况。
如何正确使用嵌套 .gitignore
来实现上述要求?
(注意:我只将上面描述中的 .gitignore
文件命名为 #1
和 #2
以明确区分两者。在我的实际计算机中,这些文件正确命名为 .gitignore
.)
这里的一般规则是这样的:
- Git 将使用 OS 的工具来读取目录。
- 要扫描目录,Git 调用
opendir
和关联的readdir
(最终closedir
)函数。 readdir
然后 returns 目录条目 ,一次一个。每个条目都包含一个 name component,定义如下。条目 可能 还包含其他信息——特别是目录与文件的区别——但这就是 Git 在这里真正可以指望的了。如果 OS 用DT_DIR
、DT_FILE
等填充d_type
字段,Git 将尝试使用它,否则 Git 可能不得不退回到调用lstat
(这很昂贵)。
阅读整个目录后,Git 现在有了一组名称组件。 name component 基本上是 path-name 中斜杠之间的部分:例如,对于 path/to/file.ext
我们有三个组件,path
,to
,以及 file.ext
。请注意,/path/to/file.ext
也是如此:前导斜杠仅表示“从顶部”而不是“从我们在树中的任何位置”。 Git 做了一些(相当奇特的)使用相同的想法——以斜杠开头的路径是“根相对”,其余的是“当前位置相对”——当在 .gitignore
中使用“锚定”条目时文件(见下文)。因此,如果 path/to/file
存在于工作树的顶层,则 Git 在扫描顶层目录时将只看到 path
部分。
(旁注:POSIX 也包括 scandir
,但人们发现这个接口很难正确使用。在某些系统上它在各种意义上也“更高效”,尽管并非总是如此或者可以预见,使用较低级别的 readdir
例程,而 Git 使用 readdir
。)
现在 Git 有了名称组件,Git 可以根据此特定级别的 .gitignore
检查它们(如果存在)。它还可以将每个组件与首先在此处获得 Git 的任何前导路径名组合起来。 对于初始扫描,没有这样的前导组件,也没有合并发生,但让我们在下面观察如果允许我们继续进入 path/
(这是一个目录)会发生什么.
组件现在可能需要 类型 检查:文件与目录。 .Real-world 文件系统可能有其他类型,包括 symbolic link,但为了我们这里的目的 symbolic link 暂时被当作一个文件来对待。我们只想知道组件是否代表一个目录。
现在,我们目前阅读的任何 .gitignore
文件中的条目——包括我们正在阅读的 this 目录中的条目——被标记为三个独立方式:
有些是 锚定的 ,例如
/path/to
或a/b
,有些不是,例如 [=34] =] 例如。 anchored 条目是包含任何斜杠的条目 在 删除单个尾部斜杠(如果存在)之后。一些仅用于 目录,一些用于 所有名称。如果条目以尾部斜杠结尾,则条目被标记为 directory-only。 (由于尾部斜杠是“仅目录”标志,因此在决定是否设置“锚定”标志时必须忽略它。)
有些是肯定的(“忽略”)条目,有些是否定的(“不要忽略”)条目。否定条目是以
!
作为第一个字符开头的条目。 (/path
的锚定否定条目必须阅读!/path
;/!path
在这里不起作用。)
所以假设我们正在读取顶层,或者我们正在读取顶层内的目录 path
。假设我们在此级别遇到两个名称组件:path
和 to
。我们现在或多或少同时检查所有这些东西(按顺序,以便“最后输入”覆盖):
根据所有 non-anchored 忽略表达式检查 目录条目 本身。
path
是其中任何一个的匹配项吗?如果是这样,根据 positive/negative 标志,此名称为 ignored/unignored。检查到目前为止的完整路径 与所有anchored 忽略表达式。对于
path
,这是/path
、/path/path
或/to/path
;对于to
,这是/to
、/path/to
或/to/to
之一。 (请记住,我们同时找到了/path
和/to
,并且我们可能正在查看两者的内部。)如果此 path-so-far 与其中一个锚定表达式匹配,则此名称为 ignored/unignored 根据 positive/negative 标志。
请注意,当我们检查锚定路径时,我们正在查看工作树中的完整路径,而.gitignore
本身可能来自sub-path 在 .gitignore
树中。因此,如果我们正在读取目录 /path
并且我们有/path/.gitignore
并且它有一个锚定条目 /xyzzy
,我们实际上是在检查这个 /xyzzy
与 /path/xyzzy
(因为它来自 /path/.gitignore
,而不是来自 /.gitignore
).这有点复杂,但是一旦你考虑它就有意义了:锚点是相对于 .gitignore
的位置的。这使您可以重命名目录,而无需编辑任何子 .gitignore
文件中的所有锚定路径。
进一步注意,“匹配”测试可能需要目录条目本身命名一个目录。如果 ignore 条目被标记为 directories-only,就会出现这种情况。因此,要检查这一点,我们需要知道条目——例如 path
或 to
——是否命名了 OS 文件系统中的一个目录。
至此,我们已经完成了对 this 条目必须进行的所有检查。它要么匹配了一些 .gitignore
条目,在这种情况下,匹配 .gitignore
的 last 就是那个条目,或者没有。并且,子目录 .gitignore
在链的后面匹配,因此 deepest .gitignore
可以 匹配此条目将始终具有 last 匹配项,如果它有匹配项的话。
如果此条目不匹配任何 .gitignore
规则,则此特定名称不会 被忽略。如果它 did 匹配 .gitignore
规则,则最后一个的 positive/negative 标志决定是否忽略此特定名称。
现在我们知道名字是否被忽略了,我们有两个选项,每个选项都有两个sub-options:
被忽略:
- 如果是目录,我们根本不扫描它。
- 如果是 文件,我们不会 auto-add 文件(例如
git add .
),或者git status
,我们不会抱怨 未跟踪 文件(假设它实际上未被跟踪)。
不忽略:
- 如果它是一个目录,我们递归扫描它并应用所有这些规则。
- 如果它是一个 文件,我们
git add
它(例如git add .
)或者如果它没有被追踪一定要投诉(git status
).
这决定了 git status
是否抱怨它未被跟踪(对于 git status
命令)或者是否 git add
某种递归风格(git add --all
,git add .
, git add somedir
, 等等) 添加它。
请注意,您可以使用 git add --force
覆盖忽略条目,例如,即使 ignored-file
会被正常的 .gitignore
规则忽略,git add --force ignored-file
也会添加它。我从来没有尝试 git add --force .
看看这里发生了什么,但它可能不太好。它可能完全忽略所有 .gitignore
规则,这看起来很糟糕,或者它可能完全遵守它们,这看起来也很糟糕。我会把它留给 reader 来尝试,看看它做了什么,然后决定它有多糟糕。
另请注意,一旦某个路径名出现在 Git 的索引中,并且 Git 的索引包含 完整路径名 ,例如,path/to/file
,作为其中包含文字斜杠的文字字符串——该文件 不会被忽略 即使它列在 .gitignore
文件中。忽略规则特定于递归目录移动过程,但是 Git 的索引 中列出的文件被 跟踪并且 被 检查en-masse git add .
操作。一旦你越过了 OS-interaction 的东西并进入 Git 正确的地方,文件就不再有“包含目录”,它们只有长路径字符串,如果需要的话,它们会嵌入正斜杠。
Git 的索引无法存储裸目录名称,1,这就是您无法提交空目录的原因。扫描过程将 扫描 目录以获取 文件 并将(在适当的条件下)将这些文件添加到索引中,但不会添加包含目录。最接近的 Git 是 submodule 条目存储为 so-called gitlink ,一个带有 mode 160000
的“文件”,如果它是一个 Linux file-system 实体,它将是 directory-and-symbolic-link 的组合(这在文件系统中是不允许的)。这就是 store an empty directory 尝试出错的原因(但您可以存储没有文件的子模块!)。
1从技术上讲,它可以,只是不能存储为Git使用的那种条目为下一次提交跟踪 files。 Git 的索引增长了一大堆奇怪的 add-ons 以提高效率,其中包括跟踪未跟踪的内容(so-called 未跟踪的缓存 ),其中包括未跟踪的目录。所以它不能 track 一个目录但是它可以 untrack 一个!