概述
用户的工作区(也称工作树)内部的内容既可以被 Git 跟踪,也可以不被它跟踪。被跟踪文件,顾名思义,Git 将会跟踪该文件的一系列变更记录。对于 Git 来说,如果一个文件存在于暂存区(也称索引),如果没有特别声明的话,那么该文件将会被系统跟踪,并作为下一个修订记录的一部分。用户添加文件后,系统跟踪这些文件的目的是将它们当作项目历史的一部分。
索引或者暂存区不仅可以用来告知 Git 系统跟踪哪些文件,而且可以作为新建提交的一种暂存器。而且索引或者暂存区可以帮助用户解决合并冲突。
通常对于某些独立文件或者某类文件来说,用户永远都不希望它们作为项目历史记录的一部分而存在,并且也不希望系统跟踪它们。这些文件可能是编辑器的备份文件,也有可能是项目编译系统自动生成的临时文件。
用户不希望 Git 系统自动添加上述文件,例如,在使用 git add :/
(添加整个工作区)和 git add .
(添加当前目录下的所有文件)命令批量添加文件时,或者使用 git add --all
命令更新工作区状态索引时。
另外一方面,用户又希望 Git 能够有效地防止将不必要的文件添加到系统中。同时用户还希望在执行 git status
命令后不显示这些文件,因为它们的数量可能会非常庞大。有时它们也可能会和一些新增的未知文件混在一起。因此用户希望这类文件特意不被跟踪,即忽略它们。
未跟踪和重新跟踪的文件
如果用户希望忽略某个以前被跟踪的文件,例如从手工生成 HTML 文件迁移到使用类似 Markdown 这样的轻量级标记语言时,用户通常需要在不将它们从工作目录中删除的情况下,将它们添加到忽略文件列表中,对它们取消跟踪。为此,用户可以使用
git rm--cached <文件名>
命令。为了添加(开始跟踪)一个特意不跟踪(即忽略)的文件,用户需要使用命令git add -f
。
将文件刻意标记为不跟踪的
这种情况下,用户可以通过 Git 系统中 gitignore 文件添加一组 shell glob 模式来指定希望忽略的文件,文件中每个模式占用一行的位置。
- 可以通过配置变量
core.excludesFile
指定每个用户的个性化配置文件,该变量默认的值是$XDG_CONFIG_HOME/git/ignore
。如果环境变量$XDG_ CONFIG_HOME
未设置或者为空的话,那么其默认值为$HOME/.config/git/ignore
。 - 每个本地版本库的
$GIT_DIR/info/exclude
文件在本地版本库克隆的管理区中。 .gitignore
文件在项目工作区目录下;该文件通常是被系统跟踪记录并且可以和其他开发人员共享的。- 一些诸如
git clean
的命令还支持用户通过命令行声明忽略模式。
在判断是否忽略某个路径时,Git 系统会根据上述列表中的模式以一定的顺序进行模式匹配,然后根据就近优先原则决定输出结果。.gitignore
文件也是按照一定的顺序进行检查的,它会从项目的顶级目录开始,依次遍历项目中的所有文件。
为了增强 gitignore 文件的可读性,用户可以使用空白行将文件分组(空白行不匹配文件)。用户还可以对模式进行描述或者使用附带注释的模式组,以 #
开头的代表一行注释(为了实现对一个 #
开头的哈希字符串进行模式匹配,通常会在第一个哈希字符前面使用 \
对它转义,例如 \#*#
)。字符尾部的空格会被忽略,除非使用 \
对它进行转义。
gitignore 文件中的每一行都代表一种 UNIX 的 glob 模式,即 shell 通配符。通配符 *
可以匹配 0 个或者多个字符(任意字符串),通配符 ?
可以匹配任意单个字符。读者还会了解到字符类方括号 [...]
的使用,例如下面的模式匹配示例:
*. [oa]
*~
这里的第一行内容是告诉 Git 系统忽略所有后缀名是 .a
或者 .o
的文件(例如静态链接库),以及软件编译过程中产生的临时文件。第二行是告诉 Git 系统忽略所有以 ~
结尾的文件,这类文件常见于很多 UNIX 文本编辑器采用的临时备份文件。
如果模式中不包含 /
,即文件路径的分隔符,Git 会将它视为一个 shell glob 通配符,并且根据它查找相应的文件名和目录名,例如 .gitignore
文件的路径或者某个版本库顶级目录。以 /
结尾的模式是一个例外,它主要是用来匹配目录的,除非目录级下的反斜杠被移除了。以反斜杠开头的模式是用来匹配路径名称前面位置的,它的含义有以下几种:
- 模式中不包含反斜杠匹配版本库中的任意路径,那么我们可以说该模式是递归的。
例如*.o
模式匹配任意的文件对象,其中既包含 gitignore 文件,也包括file.o
和obj/file.o
这样的子目录。 - 以一个反斜杠结尾的模式只匹配目录,否则它就是递归的(除非它还包含其他斜杠)。
例如auto/
模式将会匹配顶层的auto
目录以及src/auto
目录,但是它不会匹配名为auto
的文件(或者一个标记链接)。 - 如果希望固定一个模式,并且确保它是非递归的,那么可以在其起始位置添加一个反斜杠进行转义。
例如/TODO
文件将会忽略当前层级的TODO
文件,但是不会忽略子目录中的文件,例如src/TODO
。 - 包含斜杠的模式都是固化并且非递归的,通配符不会匹配作为目录分隔符的斜杠。如果用户希望匹配任意目录,那么可以使用两个连续的星号
**
替换路径地址的某个部分(例如**/foo
、foo/**
和foo/**/bar
)。
例如doc/.html
匹配的是doc/index.html
文件,但是无法匹配地址doc/api/index.html
。为了匹配doc
目录下的任意 HTML 文件,用户可以使用doc/**/*.html
模式(或者将*.html
模式添加到doc/.gitignore
文件中)。
用户还可以在模式前面加一个感叹号 !
前缀使之失效,任何根据先前的规则被排除的文件,现在又再次被包含(不再被忽略)进来了。例如已经忽略所有生成的 HTML 文件,但是希望包含某个通过手工生成的文件,用户可以在 gitignore 文件中做如下设置:
# 忽略所有以 .html 结尾的文件
*.html
# welcome.html 文件除外
!welcome.html
注意,Git 基于性能方面的考虑不会排除某个目录,这意味着当父目录被排除后,用户不能将其目录下的某个文件再次包含到跟踪列表中。也就是说,为了将某个子目录作为例外被跟踪,必须做如下配置:
# 除了目录 t0001/bin 之外,将会排除所有文件
/
!/t0001
/t0001/
!/t0001/bin
为了匹配一个用 !
开始的模式,必须使用一个反斜杠对其进行转义,例如模式 \!important!.md
是为了匹配 !important!.md
。
确定忽略文件类型
现在我们已经了解了如何将文件状态特意标记为不被跟踪的(忽略),那么接下来的问题是哪些(哪一类)文件应该被标记。另外一个问题是:上述文件的位置是什么?以及我们应该如何在 3 个 .gitignore
文件中声明需要忽略的文件类型?
首先,用户永远不应该跟踪自动生成的文件(通常是由项目的编译系统生成的)。如果用户将它们添加到版本库中了,那么它们很有可能无法和源文件保持同步。此外,它们也不是必须添加的文件,因为系统可以很容易地重新生成它们。唯一的例外是生成这些文件的源文件极少发生变动,并且生成它们需要额外的工具辅助,但是开发人员有可能没有这些工具(如果源代码经常发生变更,用户可以使用一个孤儿分支存放这些生成的文件,只在发布预览版程序时更新该分支)。
这些是所有开发人员都希望忽略的文件,因此它们应该被放到一个被跟踪的 .gitignore
文件中。模式列表将会是经由版本控制的,并且可以通过克隆副本分发给其他开发人员。我们可以在 https://github.com/github/gitignore 上找到和若干编程语言有关的一组非常有用的 .gitignore
模版。
提示:本站也提供了一个 .gitignore 模板生成工具,可以自动生成各类常见项目、开发语言、框架、IDE 所使用的 .gitignore 文件。
其次,临时文件和特定产品相关的用户工具链,这些内容通常都不会与其他开发人员共享。如果模式对版本库和用户都是有效的,例如在版本库中的辅助文件,并且该文件也会影响特定的工作流用户(例如该项目中 IDE 程序),那么它就应该被放到每个克隆的 $GIT_DIR/info/exclude
文件夹中。
在用户采用的通用忽略模式中,不必特意声明版本库(或者项目),一般来说可以通过 core.excludesFile
配置变量中进行声明,同时对于每个用户(全局)还可以在 ~/.gitconfig
或者 ~/.config/git/config
配置做相关设置。一般来说,默认的配置文件路径是 ~/.config/git/ignore
。
专属于每个用户的忽略文件应该不会是
~/.gitignore
,因为如果用户希望让~/directory ($HOME)
主目录保持版本控制,那么该文件有可能就会是用户主目录对应版本库中的.gitignore
文件。
这里是编辑器或者 IDE 程序生成的备份或者临时文件对应的模式匹配文件所在之处。
已忽略文件通常也是无关紧要的
重要提醒:不要将重要资料添加到忽略列表中,这些资料通常是用户不希望在某个版本库中被跟踪的,但是内容相比忽略文件列表中的内容来说却是非常重要的!被 Git 忽略的这类文件既可以很容易地被重新生成(产品编译系统生成的中间文件),而且对于用户来说又是无关紧要的(临时文件或者备份文件)。
因此 Git 会认为这类已忽略的文件用处不大,当需要做一些清理工作时,即使在没有向用户提示的情况下也可能把它们删除,例如,如果已忽略文件和当前签出的修订内容有冲突时。
忽略文件列表
用户可以在执行 status
命令时使用 --ignored
选项查看被忽略的文件:
$ git status --ignored
On branch master
Ignored files:
(use "git add -f <file>..." to include in what will be committed)
.DS_Store
no changes added to commit (use "git add" and/or "git commit -a")
用户还可以使用清理被忽略文件的测试选项:git clean -Xnd 和底层(管道化)的命令 git ls-files:
$ git ls-files --others --ignored --exclude-standard
.DS_Store
后一个命令还可以用来显示匹配忽略模式的被跟踪文件。找到这类文件往往意味着某些文件需要被取消跟踪(也可能是源代码文件临时生成的中间文件),或者也可能是忽略模式覆盖的范围过于宽泛了。因为 Git 是通过暂存区(缓存)已有的文件来识别哪些文件需要被跟踪的,相关的命令如下:
$ git ls-files --cached --ignored --exclude-standard
底层命令(Plumbing)和高层命令(Porcelain)的区别
Git 的命令主要分为两种:一种是方便和用户交互的高层命令,另外一种是方便编写 shell 脚本的管道化底层命令。它们之间的区别在于:高层命令的输出结果可以变更并不断完善,例如在遇到与 HEAD 分离的情况下,执行
git branch
命令后的输出结果显示了其中的分离过程(无分支到与 HEAD 分离)。同时底层命令中也有选项(通常是--porcelain
)来决定是否选择无变更的输出结果。它们的输出结果和行为是可以根据主题配置的。另外一个比较重要的区别是高层命令会尝试猜测用户的意图,并且会使用默认参数和默认配置。底层命令则没有那么智能,用户必须通过
--exclude-standard
这样的选项和git ls-files
命令搭配使用,才能让它采用忽略文件的默认设置。
忽略跟踪文件内的变更
也许用户版本库中不少文件发生了变更,但是它们很少被提交。这类文件可以是若干本地配置文件,为了适应用户本地环境经过编辑配置,但是用户永远不希望将它们提交到远程上游分支。这也可以是某个包含新发布的预览版软件名称的文件,只有当它们添加了下一个预览版程序便签后才会被提交。用户可能希望让这类文件大部分时间都保持 “杂乱无章” 的状态,但是又不希望 Git 系统经常向用户提示这些文件发生了变更。为了防止干扰其他变更信息的提示,用户应该将这类信息忽略。
用户可以对 Git 进行配置,让它跳过对工作目录的检查(假定它始终是最新版本),并使用文件的暂存版本替代,可以对某个文件设定相应的 skip-worktree
标记来达到此目的。为此用户将会需要用到底层的 git update-index
命令,它相当于面向用户的高层命令 git add(用户可以使用 git ls-files
查看文件的状态和标记):
$ git update-index --skip-wroktree GIT-VERSION-NAME
$ git ls-files -v
S GIT-VERSION-NAME
H Makefile
不过这种对工作区的省略也会影响到 git stash
命令;为了暂存用户的变更并且保持工作目的整洁,用户需要禁用该标记(至少是临时措施)。为了让 Git 再次监测到工作目录中的修订版本,并且开始跟踪文件的变更记录,可以执行下列命令:
$ git update-index --no-assume-unchanged GIT-VERSION-NAME
还有一个类似的选项 --assume-unchanged
,它可以用来让 Git 系统完全忽略文件的变更,甚至可以假定文件没有发生任何变化。当文件被这个标签标记之后,它们将永远不会出现在 git status
或 git diff
命令的输出结果中,与之相关的变更将不会被暂存或提交。
有时这是非常有用的,特别是在检查一个大型项目的文件变更时。不要对被跟踪的文件使用 --assume-unchanged
选项已达到忽略文件的目的。用户务必确保文件没有发生变更,不会发生欺骗 Git 系统的情况,例如 git stash save
命令将会根据你的设置进行保存,这样就可能失去用户本地文件的变更记录。
参考资料: