我有以下文件夹结构:
Project/
.git/
.gitignore #1
a/
a1/
a2.txt
a3.txt
.gitignore #2
b/
b1.txt
c.txt
我想拥有 git
不要忽略 a2.txt
,而不是忽略 B/
的全部。其他一切都应忽略。
根据建议/注释/答案提供, .gitignore#1
的内容是:
a/
c.txt
这实际上忽略了 c.txt
以及文件夹中的所有内容 a/
,后者受到不被更深的嵌套 .gitignore
覆盖的约束。
.gitignore#2
的内容是:
!a1/a2.txt
我希望更深的嵌套 gitignore
文件将导致不忽略文件 a2.txt
。
但是,运行 git状态 - 签名
结果:
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
以澄清两个。
I have the following folder structure:
Project/
.git/
.gitignore #1
a/
a1/
a2.txt
a3.txt
.gitignore #2
b/
b1.txt
c.txt
I would like to have git
not ignore a2.txt
, and not ignore entirety of b/
. Everything else should be ignored.
Based on suggestions/comments/answers provided here, the content of .gitignore #1
is:
a/
c.txt
This essentially ignores c.txt
and everything in folder a/
, the latter being subject to not being overridden by a deeper nested .gitignore
.
The content of .gitignore #2
is:
!a1/a2.txt
I was hoping this deeper nested gitignore
file would lead to not ignoring file a2.txt
.
However, running git status --ignored
results in:
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)
That is, the entirety of a/
seems to be ignored despite the exception I was hoping would be provided by .gitignore #2
.
How can nested .gitignore
's be correctly used to achieve the requirement above?
(Note: I have only named the .gitignore
files in the description above as #1
and #2
for clarificatory purposes to differentiate between the two. In my actual computer, these files are properly named just .gitignore
.)
发布评论
评论(1)
这里的一般规则是:
readdir
(以及最终nocleir
)功能。readdir
然后返回目录条目一次。每个条目都保留了以下定义的A 名称组件。条目 May 还保留其他信息,尤其是目录与文件区别,但这与Git一样多,可以真正依靠此处。如果操作系统填充d_type
带有dt_dir
,dt_file
等。回到调用lstat
(这很昂贵)。阅读了整个目录后,Git现在具有一组名称组件。 a name component 基本上是斜线之间的路径名的一部分:路径,<代码> 和
file.ext
。请注意,对于/path/to/file.ext
:领先的斜线仅表示“从顶部”而不是“从我们在树上的任何地方”而言,这也是如此。 git可以使用相同的想法(相当奇特)的使用 - 从斜线开始的路径是“ root相对”,其余的是“当前位置相对”,当使用.gitignore中的“锚定”条目
文件(请参见下文)。因此,如果path/to/file
存在于工作树的顶层中,则git在扫描顶级目录时仅会看到path
零件。(旁注:POSIX还包括
scandir
< /a>,但人们发现此接口很难正确使用。 git使用readdir
。)现在,Git具有名称组件,Git可以根据该特定级别的
.gitignore
(如果存在)检查它们。它还可以将每个组件与任何领先的路径名相结合,这些路径名首先在这里获得git。 对于初始扫描没有这样的领先组件,也不会发生组合,但是让我们在下面观察如果允许我们进入path/
(这是目录)。现在,组件可能需要A 类型检查:文件与目录。 .REAL-WORLD文件系统可能具有其他类型,包括符号链接,但就我们的目的而言,符号链接 目前将被视为文件。我们只想知道组件是否代表目录。
现在,我们到目前为止已阅读的任何
.gitignore
文件中的条目 - 包括我们现在正在阅读的 this 目录中的一个条目 - 以三种独立的方式标记:/path/to
或a/b
,而有些则不是,例如in
*.o
例如。 锚定条目是一个包含任何斜线后 删除单个尾随的斜线。有些仅适用于目录,而有些则用于所有名称。如果条目以直接斜线结束,则将其标记为仅目录。 (由于尾斜线是“仅目录”标志的意思,因此在决定是否设置“锚定”标志时必须忽略它。)
!
作为第一个字符开始的条目。 (/path
的锚定负条目必须读取!/path
;/!路径
在这里不起作用。)因此,让我们想象一下我们正在阅读最高级别,或者我们正在读取目录
path
在顶级。假设我们在此级别上遇到两个名称组件:path
, to 。现在,我们或多或少同时检查所有这些内容(按顺序,“最后一个条目”覆盖):检查目录条目本身本身忽略表达式。
路径
是否适合其中任何一个?如果是这样,则根据正/负标志忽略/Unigning。检查到到目前为止的完整路径 针对所有锚定忽略表达式。对于
路径
这是/path
,/path/path/path
,或/to/path
;对于到
,这是/to
或/path/to
或/to/to/to/to/to
之一。 (请记住,我们同时找到了/path
和/to
,并且大概是我们在内部都在内部。表达式,根据正/负标志对此名称被忽略/无标记。请注意,当我们 do 检查锚固路径时,我们正在查看工作树中的完整路径,而
.gitignore
本身可能来自内部的子路径.gitignore
树。因此,如果我们正在读取目录/path
例如,我们有/path/.gitignore
,并且它具有锚定的条目读取/xyzzy
,我们真的在检查此/Xyzzy
针对/path/xyzzy
(因为它来自/path/.gitignore
,而不是/ .gitignore
)。这有点复杂,但是一旦考虑一下它是有道理的:锚是相对于.gitignore
的位置。这使您可以重命名目录,而无需在任何子.gitignore
文件中编辑所有锚定路径。进一步注意,“是匹配”测试可能要求目录条目本身名称为目录。如果忽略条目被标记为仅目录,则是这种情况。因此,要检查这一点,我们需要知道输入是否是
path
或 to 例如 - 将OS文件系统中的目录命名为“名称”。在这一点上,我们已经完成了所有必须在此上进行的检查。它要么匹配一些
.gitignore
条目或条目,在这种情况下, last 匹配的.gitignore
是摄取的,或者未进行。并且,subdirectory.gitignore
s稍后在链条中匹配,因此最深.gitignore
可以匹配如果具有匹配项,则此条目将始终具有Last 匹配。如果此条目与任何
.gitignore
规则不匹配,则此特定名称是不忽略的。如果它 did 匹配.gitignore
规则,则最后一个人的正/负标志确定该特定名称是否被忽略。现在我们知道该名称是否被忽略,我们有两个选项,每个选项都有两个子选项:
它被忽略:
git add。
实例)或git状态
,我们不抱怨 未跟踪文件(假设它实际上是未跟踪的)。不忽略它:
git add
it(对于git add。添加。
git状态
)。这确定
git状态
是否抱怨它是未经跟踪的(git状态
命令),还是git git add
某种递归风味(git add - all ,
git add。
,git add add somedir
等)添加了它。请注意,您可以使用
git add-force
,例如,git add add-force忽略 - 忽略f-file
添加它,即使忽略
忽略
代码>将被普通.gitignore
规则忽略。我从未尝试过git add -force。
查看这里会发生什么,但这可能不好。The general rule here is this:
opendir
and the associatedreaddir
(and eventuallyclosedir
) functions.readdir
then returns directory entries, one at a time. Each entry holds a name component as defined below. Entries may also hold additional information—in particular the directory vs file distinction—but that's as much as Git can really count on here. If the OS fills in ad_type
field withDT_DIR
,DT_FILE
, etc., Git will try to use that, otherwise Git may have to fall back to callinglstat
(which is expensive).Having read the entire directory, Git now has a set of name components. A name component is basically the part of a path-name that goes between slashes: for instance, with
path/to/file.ext
we have three components,path
,to
, andfile.ext
. Note that the same is true for/path/to/file.ext
: the leading slash just means "from the top" rather than "from wherever we are in the tree". Git makes some (rather peculiar) use of this same idea—that paths starting with a slash are "root relative" and the rest are "current position relative"—when using "anchored" entries in.gitignore
files (see below). So ifpath/to/file
exists in the top level of a working tree, Git will see only thepath
part when it scans the top level directory.(Side note: POSIX also includes
scandir
, but people find this interface hard to use correctly. It's also "more efficient" in various senses on some systems, although not always or very predictably, to use the lower levelreaddir
routines, and Git usesreaddir
.)Now that Git has the name components, Git can check them against this particular level's
.gitignore
, if it exists. It can also combine each component with any leading path name that got Git here in the first place. For the initial scan there is no such leading component, and no combining happens, but let's observe below what happens if we are allowed to proceed intopath/
(which is a directory).The components may now need a type check: file vs directory. .Real-world file systems may have additional types, including symbolic link, but for our purposes here symbolic link is to be treated like a file for the moment. We just want to know whether component represents a directory.
Now, entries in any
.gitignore
file that we have read so far—including the one in this directory that we're reading now—are flagged in three independent ways:Some are anchored, as in
/path/to
ora/b
for instance, and some are not, as in*.o
for instance. An anchored entry is one containing any slashes after removing a single trailing slash if it exists.Some are for directories only and some are for all names. An entry is flagged as directory-only if it ends in a trailing slash. (Since the trailing slash is meant as the "directory only" flag, it has to be ignored while deciding whether to set the "anchored" flag.)
Some are positive ("do ignore") entries, and some are negative ("do not ignore") entries. A negative entry is one that starts with
!
as the first character. (An anchored negative entry for/path
would have to read!/path
;/!path
does not work here.)So let's imagine that we're reading the top level, or that we're reading directory
path
within the top level. Let's suppose we encounter two name components at this level:path
, andto
. We now check all of these things more or less at the same time (in order, so that "last entry" overrides):Check the directory entry itself against all non-anchored ignore expressions. Is
path
a match for any of those? If so, this name is ignored/unignored as per the positive/negative flag.Check the full path so far against all anchored ignore expressions. For
path
this is/path
,/path/path
, or/to/path
; forto
, this is one of/to
, or/path/to
, or/to/to
. (Remember that we found both/path
and/to
and presumably we're looking inside both.) If this path-so-far is a match against one of the anchored expressions, this name is ignored/unignored as per the positive/negative flag.Note that when we do check an anchored path, we're looking at the full path in the working tree, while the
.gitignore
itself might be from a sub-path within the.gitignore
tree. So if we're reading directory/path
for instance and we have/path/.gitignore
and it has an anchored entry reading/xyzzy
, we're really checking this/xyzzy
against/path/xyzzy
(because it's from/path/.gitignore
, not from/.gitignore
). This is a little complicated, but makes sense once you think about it: the anchor is relative to the.gitignore
's location. This lets you rename directories without having to edit all anchored paths in any sub-.gitignore
files.Note further that the "is a match" test may require that the directory entry itself name a directory. This is the case if the ignore entry is flagged as directories-only. So to check for that, we need to know if the entry—
path
orto
for instance—names a directory in the OS's file system.At this point, we have done all the checks we must do on this entry. It either matched some
.gitignore
entry or entries, in which case the last matching.gitignore
is the one taken, or it did not. And, subdirectory.gitignore
s are matched later in the chain, so that the deepest.gitignore
that could match this entry will always have the last match, if it has a match.If this entry did not match any
.gitignore
rule, this particular name is not ignored. If it did match a.gitignore
rule, the last one's positive/negative flag determines whether this particular name is ignored or not.Now that we know if the name is ignored, we have two options, each of which has two sub-options:
It is ignored:
git add .
for instance), or forgit status
, we don't complain about the untracked file (assuming it is in fact untracked).It is not ignored:
git add
it (forgit add .
for instance) or make sure to complain if it's untracked (git status
).This determines whether
git status
complains about it being untracked (forgit status
commands) or whethergit add
of some sort of recursive flavor (git add --all
,git add .
,git add somedir
, etc.) adds it.Note that you can override ignore entries with
git add --force
, e.g.,git add --force ignored-file
adds it even ifignored-file
would be ignored by the normal.gitignore
rules. I have never triedgit add --force .
to see what happens here, but it's probably not good. ???? It might completely ignore all.gitignore
rules, which seems bad, or it might completely obey them, which also seems bad. I will leave it to the reader to try it, see what it does, and decide how bad that is.Note also that once some pathname is in Git's index—and Git's index holds full path names, e.g.,
path/to/file
, as a literal string with literal slashes in it—that file is not ignored even if it's listed in a.gitignore
file. The ignoring rules are specific to the recursive directory travel process, but files listed in Git's index are tracked and are checked by en-massegit add .
operations. Once you get past the OS-interaction stuff and into Git proper, files no longer have "containing directories", they just have long path strings with embedded forward slashes if needed.Git's index is unable to store a bare directory name,1 and that's why you cannot commit an empty directory. The scanning process will scan directories for files and will (under appropriate conditions) add those files to the index, but it won't add the containing directory. The closest Git gets to this is that a submodule entry is stored as a so-called gitlink, a "file" with
mode 160000
, which if it were a Linux file-system entity would be a combination of directory-and-symbolic-link (which is not allowed in the file system). This is why the attempts to store an empty directory go awry (but you can store a submodule that has no files!).1Technically, it can, it just can't be stored as the kind of entry that Git uses to keep track of files for the next commit. Git's index has grown a whole bunch of weird add-ons for efficiency, and that includes keeping track of untracked stuff (the so-called untracked cache), which includes untracked directories. So it can't track a directory but it can untrack one! ????