你不知道的.gitignore

0. 几个概念

  • 被gitignore规则命中,即被gitignore规则匹配到的文件或目录,不会被Git追踪,即不会被Git给track

1. 简介

  • .gitignore,是用来显式指定哪些文件或文件夹应该被Git忽略的一个文件
  • $HOME/.gitignore_global, $HOME/.config/git/ignore, $GIT_DIR/info/exclude, .gitignore这些地方指定的ignore规则都会在Git仓库中生效

2. 描述

一个.gitignore文件显式地指定了哪些文件不应被Git追踪,即被Git忽略掉。在被gitignore之前已经被Git追踪的文件不受gitignore规则的影响。关于gitignore规则的详情请继续往下看。

.gitignore文件中的每一行都指定了一种匹配模式。通常来说,Git会从多个可能的规则源获取gitignore规则来决定Git是否要忽略某一个具体的路径path,如下按照优先级列出了各种规则源,越靠前的规则优先级越高(在一个规则源内部,如果有多个gitignore匹配,以最后匹配的为准)

  • 从命令行输入的规则
  • 该路径下的.gitignore文件,或者父级目录的.gitignore中定义的规则。其中,越靠近具体路径的.gitignore文件的优先级越高,同目录下的.gitignore文件优先级最高。项目仓库中通常都有.gitignore文件,里面会包含忽略项目build自动生成的文件的规则
  • $GIT_DIR/info/exclude中定义的规则
  • Git配置变量core.excludeFile指定的规则

具体讲gitignore规则定义在哪个文件中取决于该规则的作用(域):

  • 如果一个gitignore规则应该被Git追踪,别人clone仓库后规则也生效,那么它就应该被定义在.gitignore文件中
  • 如果某个规则只想在某一个特定的仓库中生效,就把它定义在$GIT_DIR/info/exclude中吧
  • 如果你的gitignore规则要在任何情况下都生效,这种规则最好放在core.excludesFile这一变量中,这个变量定义在用户目录下-~/.gitconfig,该变量的默认值是$XDG_CONFIG_HOME/git/ignore,如果$XDG_CONFIG_HOME是空的,Git会使用$HOME/.config/git/ignore

Git的底层管道工具,比如git ls-filesgit read-tree,只从命令行参数||命令行参数指定的文件中读取gitignore规则。上层的Git工具,比如git statusgit add,会从上述规则源中读取gitignore规则

3. gitignore规则

  • 空行不匹配任何文件,所以可以用空行来增强gitignore规则的可读性
  • 注释行以#开头。可以在#前加一个反斜杠转义之,使之能够匹配包含#字符的文件夹或文件
  • 如果每一行最后尾随的空格没有用反斜杠转义,那么这些空格是无效的,不会作为规则的一部分
  • 使用!前缀来否定之前的规则。如果一个文件被前面的gitignore规则给匹配到了,那么该文件不会被Git追踪,但是如果后面的规则使用!匹配到了该文件,那么该文件又会被Git追踪。当然,如果一个文件的父目录都被Git忽略了,那么无论如何,这个文件都不会被Git追踪。出于性能考虑,Git不会遍历被忽略的目录,因此,定义在被忽略目录下的gitignore规则都是无效的。有时候,我们真的是想忽略以感叹号!开头的一个文件或者目录,这时,可以在感叹号!前面加一个反斜杠转义之,比如:\!important.txt会匹配文件!important.txt
  • 如果一个规则以斜杠结尾,在实际匹配的时候,最后的斜杠会被移除掉,但是这个规则只会匹配目录,而不会匹配文件。换句话说,foo/会匹配到目录foofoo下的子目录,但不会匹配到文件foo或者软链接foo
  • 如果规则中不包含斜杠/,Git就会就会把该规则当成通配符规则来进行处理,从该规则所在.gitignore文件所在路径开始匹配。当然,如果这个规则不是放在.gitignore文件中的,就会从work tree的顶部开始匹配
  • 如果规则不符合以上的情况,那么Git就会把这个规则当成shell通配符规则来进行解析,是以带FNM_PATHNAME标记的fnmatch(3)规则进行解析。但是,规则中的通配符不会匹配路径名中的斜杠/。举个栗子,Documentation/*.html匹配Documentation/git.html,但不会匹配Documentation/ppc/ppc.html或者tools/perf/Documentation/perf.html
  • 以斜杠开头的通配符规则从路径开头开始匹配。比如,/*.c匹配cat-file.c,但不匹配mozilla-sha1/sha1.c

两个连续的星号**在匹配全路径名的时候可能有特殊含义:

  • 规则以两个星号**开头,后接一个斜杠,这样的规则会在所有路径或子路径中尝试进行匹配。比如,**/foo会匹配到文件foo或者目录foo,无论它在哪个目录;foo这条规则同样会尝试匹配所有路径中的文件foo或者目录foo**/foo/bar规则会匹配任意文件或目录foo下直接跟的文件bar或目录bar
  • 如果规则中间有连续的两个星号**,那这条规则会匹配下面的所有东西。比如abc/**会匹配目录abc下的所有文件或目录,当然,这里的目录abc是相对于.gitignore文件位置而言的,无限递归
  • 如果规则是斜杠/后跟两个星号,然后再跟一个斜杠的形式,这里的两个星号就会匹配0+个目录,这里的0+是指可以没有,也可以是多个。再举个例子,比如a/**/b会匹配a/ba/x/ba/x/y/b这些
  • 其他形式的连续星号都认为是非法的

4. 笔记

gitignore文件的目的是确保某些不应该被Git追踪的文件确实没有被track。如果要停止track一个已经被Git追踪的文件,请使用git rm --cached Xxx命令

5. 示例

$ git status
[...]
# 暂未被Git追踪的文件:
[...]
#       Documentation/foo.html
#       Documentation/gitignore.html
#       file.o
#       lib.a
#       src/internal.o
[...]
$ cat .git/info/exclude
# 忽略,即不再追踪仓库中所有的objects和压缩文件.
*.[oa]
$ cat Documentation/.gitignore
# 忽略自动生成的html文件,
*.html
# 排除手动维护的foo.html,即不忽略foo.html,即Git会追踪foo.html
!foo.html
$ git status
[...]
# 暂未被Git追踪的文件:
[...]
#       Documentation/foo.html
[...]

一个例子不够,再来一个:

$ cat .gitignore
vmlinux*
$ ls arch/foo/kernel/vm*
arch/foo/kernel/vmlinux.lds.S
$ echo '!/vmlinux*' > arch/foo/kernel/.gitignore

在这个例子中,第二个.gitignore文件arch/foo/kernel/.gitignore的优先级更高,它阻止了第一个.gitignore文件试图忽略arch/foo/kernel/vmlinux.lds.S的行为,从而,Git不会忽略arch/foo/kernel/vmlinux.lds.S,会尝试追踪它

最后再来一个例子收尾。举个忽略目录下所有文件或目录,除了某个特定的目录foo/bar的例子吧(注意下面的/*,就算没有前面的斜杠,通配符也会匹配包括foo/bar在内的所有文件或目录,所以斜杠/是可有可无的):

$ cat .gitignore
# exclude everything except directory foo/bar
/*
!/foo
/foo/*
!/foo/bar

6. 补充示例

译文到此完毕,我再补充几个小例子吧

6.1 示例一

6.2 示例二

6.3 示例三

7. 其他

  • 如果文章中有任何不妥之处或者错误的地方,欢迎在评论区留言,谢谢~