参考:git中的.gitignore 的忽略规则、.gitignore文件语法和常见写法、git-scm.com/docs/gitignore
忽略规则的优先级
gitignore
文件中的每一行都指定一个模式。当决定是否忽略该路径的文件时,Git 通常会检查来自多个源的gitignore
模式,按照以下优先级顺序,从最高到最低(在一个优先级内,最后匹配的模式决定结果):
- 从命令行中读取可用的忽略规则,从上往下依次读取
- 当前目录定义的规则(即:如果在父目录中定义了一些模式,但在子目录的
.gitignore
中有冲突的模式,则子目录的规则会优先。) - 父级目录定义的规则,依次递推。
- $GIT_DIR/info/exclude 文件中定义的规则
- core.excludesfile中定义的全局规则
注:这些模式是相对于 .gitignore
文件所在位置进行匹配的。这意味着如果 .gitignore
文件在某个子目录中,那么它定义的规则只适用于该子目录及其子目录中的文件。称.gitignore
文件所处的目录为根目录。
PATTERN的格式
Pattern可以理解为我们在gitignore
文件中写下的每一行字符,可以理解为匹配的规则。
-
所有空行或者以 #(hash) 开头的行都会被 Git 忽略。其中以 # 开头的行用作注释。
# 的英文为 hash,对于以哈希开头的Pattern,需要在第一个哈希前面放置一个反斜杠(backslash)(“
\
”)。\
表示转义。 -
末尾的空格也需要加
\
转义,否则空格被忽略。 -
前缀“
!
”,用于取消之前的排除规则。已经被前面的Pattern所排除的任何匹配文件(且符合当前Pattern的),将再次被包含在内。如果文件的开头就是
!
那么需要在最开头添加\
,例如:\~important!.txt
注意: 如果某个文件的上级目录已经被排除(例如上级目录在
.gitignore
中被忽略了),则无法通过 ”!” 将该文件重新包括进来。因为 Git 出于性能原因,不会保留已被排除的目录内容,因此即便你试图通过规则重新包括某个文件,这个操作也不会生效。举例:
Terminal window 1qrkernel/2!qrkernel/filelist.mk
!qrkernel/filelist.mk
操作是无效的,因为qrkernel/
表示排除当前根目录以及子目录中所有的qrkernel
目录。因此,文件filelist.mk
的上级目录已经被排除,所以再次包含filelist.mk
无效。正确的写法:
Terminal window 1qrkernel/**2!qrkernel/filelist.mk
qrkernel/**
只排除qrkernel
目录下的文件和子目录及其内容,不会排除qrkernel
目录本身。 -
“
/
“(slash)符号用作文件夹分隔符,可以出现在pattern的开头,中间,结尾。 -
“
/
“出现在pattern的开头或者中间,或者两者同时出现,则表示pattern是.gitignore
所在的目录层级,否则,pattern表示的范围是.gitignore
当前目录及其子目录层级。 -
如果模式末尾有“
/
“,则该模式将仅匹配目录,否则该模式可以匹配文件和目录。 -
例如,模式
doc/frotz/
匹配doc/frotz
目录,但不匹配a/doc/frotz
目录;然而frotz/
匹配frotz
和a/frotz
目录(所有路径都以.gitignore
文件所处的文件夹为根目录,相对于该根目录进行匹配)。 -
星号(asterisk)“
*
”匹配除“/
”之外的任何内容。字符“?
”匹配除“/
”之外的任意1个字符。范围表示法,例如[a-zA-Z]
,可用于匹配范围中的字符之一。有关更详细的说明,请参阅 fnmatch(3) 和 FNM_PATHNAME 标志。 -
两个连续的“
*
”,在全路径匹配pattern中有特殊的含义- 以
**/
开头的pattern,表示在所有的文件夹中进行匹配。例如:“**/foo
“匹配当前根目录及所有子目录下的所有的以foo命名的文件以及文件夹,和patternfoo
等价。“**/foo/bar
“则匹配根目录及子目录下的foo目录下的所有bar
命名的文件及目录。 - 以“
/**
”结尾的pattern,表示匹配目录中的所有内容。例如,“abc/**
”匹配目录根目录下“abc
”目录内的所有文件。 -
/**/
匹配任意目录,例如,“a/**/b
”匹配“a/b
”、“a/x/b
”、“a/x/y/b
”等。
- 以
.gitignore
只能忽略那些原来没有被track的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore
是无效的。例如已经进行了提交,或者add的文件和目录,则需要git rm -r --cached .
命令,将当前目录下所有文件从 Git 的暂存区(Index)中移除,但是保留这些文件在工作目录中的状态。这意味着这些文件不再被 Git 跟踪,但仍然会保留在本地文件系统中,不会被删除。
然后再修改.gitignore
文件,此时git就会按照新修正的.gitignore
进行索引。
注意:建议只在需要移除的目录下使用该命令,如果在根目录下使用,会直接移除所有的文件索引。
1git rm -r --cached .2git add .3git commit -m 'update .gitignore'
匹配规则举例
文件 .gitignore
的格式规范如下:
-
可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
-
匹配是区分大小写的,如:/abc 和 /Abc 含义不同
-
*~ 忽略所有以~结尾的文件(这种文件通常被许多编辑器标记为临时文件)
-
空目录(包括隐藏目录)会被忽略,无法提交追踪
如果不希望空目录被忽略,需要在里头建.gitkeep文件
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。
星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c)。
问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。
使用两个星号( ** )表示匹配任意中间目录,比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等。
前提约定
-
约定1
“当前目录、子目录、子子目录…” 的表述包含的目录是:.gitignore文件所在的目录,以及该目录下的所有目录和它们的所有子目录及子子目录… 总之是这颗目录树的所有节点。例如: .gitignore文件在 /Users/stonewang/git-ignore-test/.gitignore,即.gitignore文件所在的目录为/Users/stonewang/git-ignore-test/。 该表述的含义是:以/Users/stonewang/git-ignore-test/作为起点的所有目录树节点
1# 该表述包含了dir1、dir2、dir1_sub、dir1_sub2、dir1_sub_sub、dir1_sub_sub2、dir1_sub2_sub2/Users/stonewang/git-ignore-test/3|-------.gitignore4|5|-------dir16| |----dir1_sub7| |-----dir1_sub_sub8| |-----dir1_sub_sub29| |----dir1_sub210| |-----dir1_sub2_sub11|12|-------dir2
-
其他补充
目录(即文件夹)的名字有各种表现形式,如显示的、隐藏的、带扩展名的和不带扩展名的。例如:dir、.dir、dir.ext、.dir.ext文件名的形式也各种各样,如显示的,隐藏的,带扩展名的,不带扩展名的。如file、.file、file.ext、.file.ext
在.gitignore中,以 / 结尾的只会匹配目录,不带 / 结尾的匹配文件和目录,注意没有一种写法仅匹配文件的
在Mac和Windows中都不允许文件之间重名,目录之间重名,目录和文件之间重名。不区分大小写
在Mac和Windows中,目录名都是允许带点的,如dir.ext 可以作为目录名(看起来就像文件的扩展名)
详细例子
-
为了表述准确,引入自创数学符号
- (.gitignore)N 表示.gitignore文件所在的目录+所有子目录包括直接或间接
- (.gitignore)O 表示.gitignore文件所在的目录,不包括其任何子目录
写法 | 作用 |
---|---|
dir/ | 忽略 (.gitignore)N 中的dir目录(不包含子目录) |
/dir/ | 忽略 (.gitignore)O 中的dir目录 |
file | 忽略(.gitignore)N 中的file 文件&目录(名为file的目录也会被忽略) |
/file | 忽略(.gitignore)O 中的file文件 |
*.log | 忽略(.gitignore)N 中的*.log 文件&目录(符合名字的目录也将被忽略) |
/dir/file | 忽略(.gitignore)O 中的dir目录下的file文件 |
/dir/Abc* 和 /dir/Abc .java 和/dir/ .java | 忽略(.gitignore)O 中的dir目录下符合Abc (或Abc.java或*.java)规则的文件&目录 |
/dir/Abc*/ | 忽略(.gitignore)O 中的dir目录下符合Abc*的目录(不忽略dir下的文件!) |
/dir/*/ | 忽略(.gitignore)O 中的dir目录下的符合*的子目录(注意/sub/file的文件不会忽略) |
/dir// .txt | 忽略(.gitignore)O 中的dir目录下的符合的子目录下的,符合.txt的文件&目录。注意是一个星,仅忽略一层,即/dir/sub/a.txt 和 /dir/sub/sub2/b.txt,仅仅忽略a.txt,不忽略b.txt,另外/dir/k.txt也不会被忽略 |
/dir/**/*.txt | 忽略 (.gitignore)O 中的dir目录下的直接和间接子目录下的,符合*.txt的文件&目录。两个星号表示0-n层级的目录 |
/sub/** 和 /sub/ 是等价的 | 亲测。前者表示忽略/sub/下的所有直接或间接的目录和文件(**表示文件和目录,因为没有/结尾),后者表示忽略/sub/下的东西 |
/sub/**/ 和 /sub/ 是不等价的 | 亲测。前者明确表示忽略目录除掉了文件,所以对于/sub/file是不会被忽略的。 |
sub/ 和 /sub/ 含义不同 | 前者忽略(.gitignore)N下的sub目录,后者忽略(.gitignore)O下的sub |
sub/abc/ 和 /sub/abc/ | 这两个的含义完全相同(有点奇怪,本以为前者是递归所有的目录) |
**/src/main/java/ 和 src/main/java/ | 不等价。前者匹配(.gitignore)N下的src/main/java/ 目录,要满足这个目录的层级结构的。后者等价于/src/main/java/,仅仅忽略(.gitignore)O下的该目录 |
**/src/main/file.txt 和 src/main/file.txt | 不等价。前者匹配(.gitignore)N下的src/main/file.txt,符合这个目录层级结构的将会被忽略,后者等价于/src/main/file.txt,仅仅忽略(.gitignore)O下所匹配的 |
**/dir/ 和 dir/ | 是等价的。上面的例子等价这个不等价,就是因为目录的层级数的问题导致的 |
**/file.txt 和 file.txt | 是等价的。 |
先后写!a.txt和*.txt | 后面的配置覆盖前面的,导致所有*.txt文件都被忽略(有点奇怪,实际测试确实如此) |
先后写*.txt 和 !a.txt | 正确。能够忽略除了a.txt外的文件。 |
对于.gitignore文件不在git仓库根目录的情况:参考特殊情况 | (参考特殊情况) |