Lyndra's Blog

Gitignore

2024-02-22
编程开发 Git
14分钟
2616字
温馨提示:本文最后更新于 2025-03-11 ,部分信息可能因时间推移而不再适用,欢迎反馈。

参考:git中的.gitignore 的忽略规则.gitignore文件语法和常见写法git-scm.com/docs/gitignore

忽略规则的优先级

  ​gitignore​文件中的每一行都指定一个模式。当决定是否忽略该路径的文件时,Git 通常会检查来自多个源的gitignore​模式,按照以下优先级顺序,从最高到最低(在一个优先级内,最后匹配的模式决定结果):

  1. 从命令行中读取可用的忽略规则,从上往下依次读取
  2. 当前目录定义的规则(即:如果在父目录中定义了一些模式,但在子目录的 .gitignore​ 中有冲突的模式,则子目录的规则会优先。)
  3. 父级目录定义的规则,依次递推。
  4. $GIT_DIR/info/exclude 文件中定义的规则
  5. core.excludesfile中定义的全局规则

  注:这些模式是相对.gitignore​ 文件所在位置进行匹配的。这意味着如果 .gitignore​ 文件在某个子目录中,那么它定义的规则只适用于该子目录及其子目录中的文件。称.gitignore​文件所处的目录为根目录。

PATTERN的格式

  Pattern可以理解为我们在gitignore​文件中写下的每一行字符,可以理解为匹配的规则。

  • 所有空行或者以 #(hash) 开头的行都会被 Git 忽略。其中以 # 开头的行用作注释。

    # 的英文为 hash,对于以哈希开头的Pattern,需要在第一个哈希前面放置一个反斜杠(backslash)(“ \​ ”)。\​表示转义。

  • 末尾的空格也需要加\​转义,否则空格被忽略。

  • 前缀“ !​ ”,用于取消之前的排除规则。已经被前面的Pattern所排除的任何匹配文件(且符合当前Pattern的),将再次被包含在内。

    如果文件的开头就是!​那么需要在最开头添加\​,例如:\~important!.txt

    注意: 如果某个文件的上级目录已经被排除(例如上级目录在 .gitignore​ 中被忽略了),则无法通过 ”!” 将该文件重新包括进来。因为 Git 出于性能原因,不会保留已被排除的目录内容,因此即便你试图通过规则重新包括某个文件,这个操作也不会生效。

    举例:

    Terminal window
    1
    qrkernel/
    2
    !qrkernel/filelist.mk

    !qrkernel/filelist.mk​操作是无效的,因为qrkernel/​表示排除当前根目录以及子目录中所有的qrkernel​目录。因此,文件filelist.mk​的上级目录已经被排除,所以再次包含filelist.mk​无效。

    正确的写法:

    Terminal window
    1
    qrkernel/**
    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​进行索引。

  注意:建议只在需要移除的目录下使用该命令,如果在根目录下使用,会直接移除所有的文件索引。

1
git rm -r --cached .
2
git add .
3
git 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. 约定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_sub
    2
    /Users/stonewang/git-ignore-test/
    3
    |-------.gitignore
    4
    |
    5
    |-------dir1
    6
    | |----dir1_sub
    7
    | |-----dir1_sub_sub
    8
    | |-----dir1_sub_sub2
    9
    | |----dir1_sub2
    10
    | |-----dir1_sub2_sub
    11
    |
    12
    |-------dir2

  2. 其他补充
    目录(即文件夹)的名字有各种表现形式,如显示的、隐藏的、带扩展名的和不带扩展名的。例如: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仓库根目录的情况:参考特殊情况(参考特殊情况)
本文标题:Gitignore
文章作者:Lyndra
发布时间:2024-02-22
总访问量
总访客数人次
Copyright 2025
站点地图