本章介绍如何避免在 Git 仓库中添加不需要的文件(或文件更改)。有几种方法可以实现(全局或本地的 .gitignore 文件、.git/exclude 文件,以及 git update-index --assume-unchanged 和 git update-index --skip-worktree),但要注意,Git 是管理内容的,这意味着:忽略实际上是忽略了文件夹的内容(即文件)。空文件夹默认会被忽略,因为它无论如何也无法被添加。
5.1 使用 .gitignore 文件忽略文件和目录
你可以通过在仓库中创建一个或多个 .gitignore 文件,让 Git 忽略某些文件和目录,即不将它们纳入 Git 的跟踪范围。
在软件项目中,.gitignore 文件通常包含在构建过程中或运行时生成的文件和/或目录的列表。.gitignore 文件中的条目可能包括:
- 临时资源的名称或路径,例如缓存、日志文件、编译代码等。
- 不应与其他开发者共享的本地配置文件。
- 包含敏感信息的文件,如登录密码、密钥和凭据。
当在顶层目录中创建 .gitignore 文件时,规则将递归应用于整个仓库中的所有文件和子目录。当在子目录中创建时,规则将仅适用于该特定目录及其子目录。
如果文件或目录被忽略,它将不会:
- 被 Git 跟踪
- 在
git status或git diff等命令中显示 - 通过
git add -A等命令暂存
在极少数情况下,如果需要忽略已跟踪的文件,应格外小心。参见:忽略已提交到 Git 仓库的文件。
示例
以下是一些基于通配符文件模式的 .gitignore 文件中的通用规则示例:
# 以 `#` 开头的行是注释。
# 忽略名为 “file.ext” 的文件
file.ext
# 注释不能与规则在同一行!
# 下一行忽略名为 “file.ext” 的文件,而不是注释
file.ext # not a comment
# 忽略具有完整路径的文件。
# 这也匹配根目录及其子目录中的文件。
# 例如,otherfile.ext 将在树的任何位置被忽略。
dir/otherdir/file.ext
otherfile.ext
# 忽略目录
# 该目录本身及其内容都将被忽略。
bin/
gen/
# 这里也可以使用通配符模式来忽略具有特定字符的路径。
# 例如,以下规则将匹配 build/ 和 Build/
[Bb]uild/
# 如果没有尾随斜杠,规则将匹配文件和/或目录,
# 所以下面的规则将忽略名为 “gen” 的文件和目录,
# 以及该目录中的任何内容
bin
gen
# 忽略文件扩展名
# 在此目录及其所有子目录中,具有以下扩展名的所有文件都将被忽略。
*.apk
*.class
# 可以将这两种形式结合起来,忽略特定目录中的特定扩展名的文件。
# 下面的规则与上面定义的通用规则重复。
java/*.apk
gen/*.class
# 如果只想在顶层目录中忽略文件,而不是其子目录,
# 则在规则前添加一个 `/`
*.apk
*.class
# 要忽略任何深度的名为 DirectoryA 的目录,
# 在 DirectoryA 前使用 **,不要忘记最后一个斜杠,
# 否则它将忽略所有名为 DirectoryA 的文件,而不是目录
**/DirectoryA/
# 这将忽略
# DirectoryA/
# DirectoryB/DirectoryA/
# DirectoryC/DirectoryB/DirectoryA/
# 它不会忽略任何级别的名为 DirectoryA 的文件
# 要忽略名为 DirectoryB 的目录,该目录位于名为 DirectoryA 的目录内,
# 且两者之间可以有任意数量的目录,使用 ** 将这两个目录分隔开
DirectoryA/**/DirectoryB/
# 这将忽略
# DirectoryA/DirectoryB/
# DirectoryA/DirectoryQ/DirectoryB/
# DirectoryA/DirectoryQ/DirectoryW/DirectoryB/
# 要忽略一组文件,可以使用通配符,如上所示。
# 单独的 `*` 将忽略文件夹中的所有内容,包括 `.gitignore` 文件。
# 使用通配符时,可以通过以下方式排除特定文件:
# 因此,它们被排除在忽略列表之外:
!.gitignore
# 使用反斜杠作为转义字符来忽略文件名中带有井号 (#) 的文件
#(自 1.6.2.1 版本起支持)
#*#
大多数 .gitignore 文件在不同语言项目中是通用的,因此你可以从以下按语言列出的样本 .gitignore 文件中克隆或复制/修改到你的项目中。或者,对于新项目,你也可以考虑使用在线工具自动生成一个初始文件。
其他形式的 .gitignore
.gitignore 文件旨在作为仓库的一部分提交。如果你希望在不提交忽略规则的情况下忽略某些文件,这里有一些选项:
- 编辑
.git/info/exclude文件(使用与.gitignore相同的语法)。这些规则在仓库范围内是全局的; - 设置一个全局
.gitignore文件,将其忽略规则应用于所有本地仓库:
此外,你还可以在不更改全局 Git 配置的情况下,忽略已跟踪文件的本地更改:
git update-index --skip-worktree [<file>...]:用于次要的本地修改;git update-index --assume-unchanged [<file>...]:用于生产就绪且上游不会更改的文件。
有关后两个标志之间的差异以及更多选项,请参阅 git update-index 文档。
清理被忽略的文件
你可以使用 git clean -X 清理被忽略的文件:
git clean -Xn # 显示被忽略的文件列表
git clean -Xf # 删除之前显示的文件
注意:-X(大写)仅清理被忽略的文件。使用 -x(小写)还会删除未跟踪的文件。
有关更多详细信息,请参阅 git clean 文档。
有关更多信息,请参阅 Git 手册。
5.2 检查文件是否被忽略
git check-ignore 命令报告 Git 忽略的文件。
你可以在命令行中传递文件名,git check-ignore 将列出被忽略的文件名。例如:
$ cat .gitignore
*.o
$ git check-ignore example.o Readme.md
example.o
在此示例中,只有 .gitignore 中定义的 *.o 文件,因此 Readme.md 不会出现在 git check-ignore 的输出中。
如果你想知道哪个 .gitignore 文件负责忽略某个文件,可以在 git check-ignore 命令中添加 -v:
$ git check-ignore -v example.o Readme.md
.gitignore:1:*.o example.o
从 Git 1.7.6 版本开始,你还可以使用 git status --ignored 来查看被忽略的文件。有关更多详细信息,请参阅官方文档或查找 .gitignore 忽略的文件。
5.3 .gitignore 文件中的例外
如果你使用模式忽略文件但有例外,可以在例外前添加一个感叹号(!)。例如:
*.txt
!important.txt
上述示例指示 Git 忽略所有扩展名为 .txt 的文件,但不包括名为 important.txt 的文件。
如果文件位于被忽略的文件夹中,你不能这样轻松地重新包含它:
folder/
!folder/*.txt
在此示例中,folder 中的所有 .txt 文件仍将被忽略。
正确的方法是先单独重新包含文件夹本身,然后通过 * 忽略文件夹中的所有文件,最后重新包含文件夹中的 *.txt 文件,如下所示:
!folder/
folder/*
!folder/*.txt
注意:对于以感叹号开头的文件名,可以添加两个感叹号或使用反斜杠进行转义:
!!includethis
\!excludethis
5.4 全局 .gitignore 文件
要让 Git 在所有仓库中忽略某些文件,可以在终端或命令提示符中使用以下命令创建一个全局 .gitignore 文件:
$ git config --global core.excludesfile <Path_To_Global_gitignore_file>
Git 会在此基础上使用每个仓库自己的 .gitignore 文件。规则如下:
如果本地 .gitignore 文件明确包含某个文件,而全局 .gitignore 忽略它,则本地 .gitignore 优先(该文件将被包含)。
如果仓库在多台机器上克隆,那么全局 .gitignore 必须在所有机器上加载,或者至少包含它,因为被忽略的文件会被推送到仓库中,而带有全局 .gitignore 的 PC 不会更新它。这就是为什么如果项目由团队协作,仓库特定的 .gitignore 比全局 .gitignore 更好的原因。
此文件是存放平台、机器或用户特定忽略项的好地方,例如,如果你不想将这些文件保留在仓库中,可以在这里忽略 OSX 的 .DS_Store、Windows 的 Thumbs.db 或 Vim 的 *.ext~ 和 *.ext.swp。例如,一个在 OS X 上工作的团队成员可以添加所有 .DS_STORE 和 _MACOSX(实际上这是无用的),而另一个在 Windows 上的团队成员可以忽略所有 thumbs.db。
5.5 忽略已提交到 Git 仓库的文件
如果你已经将一个文件添加到你的 Git 仓库,现在想要停止跟踪它(以便它不会出现在未来的提交中),可以从索引中移除它:
git rm --cached <file>
这将从仓库中删除文件,并防止 Git 进一步跟踪该文件的更改。--cached 选项将确保文件不会被物理删除。
请注意,文件之前添加的内容仍然可以通过 Git 历史记录查看。
需要记住的是,如果你从索引中删除了文件,之后其他人从仓库中拉取时,他们的副本将被物理删除。
你可以让 Git 假装工作目录中的文件是最新的,并转而读取索引版本(从而忽略对其的更改),这可以通过设置 “skip worktree” 标志来实现:
git update-index --skip-worktree <file>
此标志不会影响写入操作,内容安全仍然是首要任务。你永远不会丢失你珍贵的被忽略的更改;另一方面,此标志与暂存冲突:要移除此标志,使用
git update-index --no-skip-worktree <file>
有时会错误地建议向 Git 说谎,让它假设文件未被更改而无需检查它。
乍一看,这似乎是忽略文件的进一步更改,而不将其从索引中删除:
git update-index --assume-unchanged <file>
这将强制 Git 忽略文件中的任何更改(请记住,如果你拉取此文件的任何更改,或者暂存它,你的被忽略的更改将丢失)。
如果你希望 Git 再次 “关心” 这个文件,运行以下命令:
git update-index --no-assume-unchanged <file>
5.6 本地忽略文件而不提交忽略规则
.gitignore 本地忽略文件,但其目的是提交到仓库并与其他人共享。你可以设置全局 .gitignore,但这样所有仓库都会共享这些设置。
如果你只想在某个仓库中本地忽略某些文件,而不让这些文件成为任何仓库的一部分,可以编辑仓库中的 .git/info/exclude 文件。
例如:
# 这些文件仅在此仓库中被忽略
# 这些规则不会与任何人共享
# 因为它们是个人的
gtk_tests.py
gui/gtk/tests/*
localhost
pushReports.py
server/
5.7 忽略文件的后续更改(而不删除它)
有时你希望在 Git 中保留一个文件,但忽略对其的后续更改。
使用 update-index 告诉 Git 忽略文件或目录的更改:
git update-index --assume-unchanged my-file.txt
上述命令指示 Git 假设 my-file.txt 未被更改,不会检查或报告更改。文件仍然存在于仓库中。
这在提供默认值并允许本地环境覆盖时很有用,例如:
# 创建一个包含一些值的文件
cat <<EOF
MYSQL_USER=app
MYSQL_PASSWORD=FIXME_SECRET_PASSWORD
EOF > .env
# 提交到 Git
git add .env
git commit -m "Adding .env template"
# 忽略 `.env` 的后续更改
git update-index --assume-unchanged .env
# 更新你的密码
vi .env
# 没有更改!
git status
5.8 忽略任何目录中的文件
要忽略任何目录中的 foo.txt 文件,只需写其名称:
foo.txt # 匹配任何目录中的 `foo.txt` 文件
如果你想仅在部分目录树中忽略该文件,可以使用 ** 模式指定特定目录的子目录:
bar/**/foo.txt # 匹配 `bar` 及其所有子目录中的 `foo.txt` 文件
或者,你可以在 bar/ 目录中创建一个 .gitignore 文件。与前一个示例等效的是创建一个包含以下内容的 bar/.gitignore 文件:
foo.txt # 匹配 `bar/` 下任何目录中的 `foo.txt` 文件
5.9 预填充的 .gitignore 模板
如果你不确定 .gitignore 文件中应列出哪些规则,或者你只想为项目添加一些通常被接受的例外,可以选择或生成一个 .gitignore 文件:
https://www.gitignore.io/ https://github.com/github/gitignore
许多托管服务(如 GitHub 和 BitBucket)提供基于你可能使用的编程语言和 IDE 生成 .gitignore 文件的功能。
5.10 在子文件夹中忽略文件(多个 .gitignore 文件)
假设你的仓库结构如下:
examples/
output.log
src/
<files not shown>
output.log
README.md
examples 目录中的 output.log 是有效的且项目需要它来理解情况,而 src/ 下的 output.log 是在调试过程中创建的,不应出现在历史记录或仓库中。
有以下两种方法可以忽略这个文件。你可以在工作目录根目录的 .gitignore 文件中放置一个绝对路径:
# /.gitignore
src/output.log
或者,你也可以在 src/ 目录中创建一个 .gitignore 文件,并忽略相对于此 .gitignore 的文件:
# /src/.gitignore
output.log
5.11 创建空文件夹
由于 Git 管理文件并将其目录附加到文件上,这可以减少提交并提高速度,因此无法在 Git 中添加和提交空文件夹。要解决此问题,有两种方法:
方法一:.gitkeep
一种变通方法是使用 .gitkeep 文件来为 Git 注册文件夹。为此,只需创建所需的目录并在其中添加一个 .gitkeep 文件。此文件为空,除了注册文件夹外没有其他用途。在 Windows(其文件命名约定有些奇怪)中,只需在目录中打开 Git Bash 并运行以下命令:
$ touch .gitkeep
此命令仅在当前目录中创建一个空白的 .gitkeep 文件。
方法二:dummy.txt
另一种变通方法与上述方法非常相似,可以遵循相同的步骤,但使用 dummy.txt 而不是 .gitkeep。这样做的好处是可以在 Windows 中使用上下文菜单轻松创建它。你还可以在其中留下一些有趣的信息。你也可以使用 .gitkeep 文件来跟踪空目录。.gitkeep 通常是一个空文件,添加到跟踪空目录。
5.12 查找被 .gitignore 忽略的文件
你可以使用以下命令列出当前目录中被 Git 忽略的所有文件:
git status --ignored
例如,假设你的仓库结构如下:
.git
.gitignore
./example_1
./dir/example_2
./example_2
并且 .gitignore 文件包含以下内容:
example_2
那么运行命令的结果将是:
$ git status --ignored
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
example_1
Ignored files:
(use "git add -f <file>..." to include in what will be committed)
dir/
example_2
如果你想要递归列出目录中的被忽略文件,需要使用额外的参数 --untracked-files=all。
结果将如下所示:
$ git status --ignored --untracked-files=all
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
example_1
Ignored files:
(use "git add -f <file>..." to include in what will be committed)
dir/example_2
example_2
5.13 仅忽略文件的一部分 [草稿]
有时你可能希望在文件中保留一些本地更改,但不想提交或发布这些更改。理想情况下,本地设置应该集中在 .gitignore 中的单独文件中,但有时作为短期解决方案,可以在已检出的文件中保留一些本地内容。
你可以使用清理过滤器让 Git “看不见” 这些行。它们甚至不会出现在差异中。
假设文件 file1.c 中有以下片段:
struct settings s;
s.host = "localhost";
s.port = 5653;
s.auth = 1;
s.port = 15653; // NOCOMMIT
s.debug = 1; // NOCOMMIT
s.auth = 0; // NOCOMMIT
你不想发布任何带有 NOCOMMIT 的行。
通过在 Git 配置文件(如 .git/config)中添加以下内容创建 “nocommit” 过滤器:
[filter "nocommit"]
clean = grep -v NOCOMMIT
然后在 .git/info/attributes 或 .gitmodules 中添加以下内容:
file1.c filter=nocommit
这样,带有 NOCOMMIT 的行就被 Git 隐藏了。
注意事项:
使用清理过滤器会减慢文件处理速度,尤其是在 Windows 上。
被忽略的行可能会在 Git 更新文件时消失。可以通过使用 “smudge” 过滤器来解决这个问题,但这更复杂。
未在 Windows 上测试。
5.14 忽略已跟踪文件的更改
.gitignore 和 .git/info/exclude 只适用于未跟踪的文件。
要为已跟踪的文件设置忽略标志,可以使用 update-index 命令:
git update-index --skip-worktree myfile.c
要撤销此操作,可以使用:
git update-index --no-skip-worktree myfile.c
你可以将以下片段添加到全局 Git 配置中,以便更方便地使用 git hide、git unhide 和 git hidden 命令:
[alias]
hide = update-index --skip-worktree
unhide = update-index --no-skip-worktree
hidden = "!git ls-files -v | grep ^[hsS] | cut -c 3-"
你也可以使用 update-index 函数的 --assume-unchanged 选项:
git update-index --assume-unchanged <file>
如果你希望再次关注这个文件的更改,可以使用:
git update-index --no-assume-unchanged <file>
当指定 --assume-unchanged 标志时,用户承诺不会更改文件,并允许 Git 假设工作树文件与索引中记录的内容一致。如果 Git 需要修改索引中的此文件(例如在合并提交时),Git 将失败。因此,如果假设未跟踪的文件在上游被更改,你需要手动处理这种情况。在这种情况下,重点是性能。
而 --skip-worktree 标志在你指示 Git 永远不要触碰特定文件时很有用,因为该文件将在本地更改,并且你不想意外提交这些更改(例如为特定环境配置的配置/属性文件)。当同时设置这两个标志时,skip-worktree 的优先级高于 assume-unchanged。
5.15 清除已提交但包含在 .gitignore 中的文件
有时会发生这种情况:一个文件最初被 Git 跟踪,但后来被添加到 .gitignore 中,以停止跟踪它。在将其添加到 .gitignore 之前忘记清理此类文件是很常见的。在这种情况下,旧文件仍然会在仓库中挂起。
要解决此问题,可以执行以下操作:先对仓库中的所有内容进行 “干运行” 删除,然后重新添加所有文件。只要你没有待处理的更改,并且传递了 --cached 参数,此命令相对安全:
# 从索引中删除所有内容(文件将保留在文件系统中)
$ git rm -r --cached .
# 重新添加所有文件(它们将以当前状态被添加,包括更改)
$ git add .
# 如果有任何更改,执行提交。你应该只看到删除操作
$ git commit -m 'Remove all files that are in the .gitignore'
# 更新远程仓库
$ git push origin master