Git 中你可能不知道的魔法文件
Git 会从仓库中读取一些特殊文件,用它们来控制自身行为。这些文件并不像 .git/ 里的配置那样留在本地,而是会随代码一起提交,从而影响 Git 对文件的处理方式。
如果你正在开发与 Git 仓库交互的工具(比如 git-pkgs),那么务必确保正确遵守这些配置。
.gitignore
指定 Git 永远不应追踪的文件模式。每行一个模式,支持通配符和目录标记。
|
|
Git 会按顺序检查多个忽略文件:包括每个目录下的 .gitignore、仅限本地的 .git/info/exclude,以及位于 ~/.config/git/ignore(或 core.excludesFile 指定的路径)的全局忽略文件。全局忽略适合 .DS_Store、Thumbs.db 这类操作系统专属文件,避免每个项目的 .gitignore 都要重复写这些内容。
模式匹配支持通配符(*.log)、目录标记(dist/)、取反(!important.log)和字符范围。** 模式可匹配嵌套目录。
.gitignore 只影响尚未被追踪的文件。如果某个文件在加入 .gitignore 之前已经被追踪,那么它会继续留在仓库中,并且在各代码托管平台的网页界面上依然可见(需要执行 git rm --cached 才能将其从追踪中移除)。另外,GitHub、GitLab、Forgejo 和 Gitea 的网页编辑器也不会阻止你创建符合忽略模式的文件并提交——它们不会给出任何警告。许多包管理器会附带自身的忽略模式(如 node_modules/、vendor/、target/),需要你手动将它们添加到 .gitignore 中。
完整的语法请参阅 gitignore 文档。另外,GitHub 维护了一份 .gitignore 模板集合,涵盖各种语言和框架。
.gitattributes
.gitattributes 用来告诉 Git 如何处理特定文件。你可以在这里配置过滤器(filter)、差异驱动(diff driver)、合并驱动(merge driver)、行尾规范化以及语言检测覆盖。
|
|
text 属性告诉 Git 规范化行尾。binary 属性告诉 Git 不进行 diff 或合并,直接选取一个版本。merge=ours 策略在合并冲突时始终保留本地版本。
GitHub 的 Linguist 工具(用于语言检测)会读取 .gitattributes 来覆盖检测结果。通过 linguist-vendored 可以标记第三方代码,将其排除在语言统计之外;linguist-generated 用来标记自动生成的文件,让它们在 diff 中默认折叠;linguist-documentation 则标记文档,同样不计入统计。
与 .gitignore 类似,Git 会检查每个目录下的 .gitattributes,同时也支持仅限本地的 .git/info/attributes。
完整属性列表请参考 gitattributes 文档,GitHub Linguist 的专属属性请参考 Linguist 覆盖文档。
.lfsconfig
.lfsconfig 是随仓库一起提交的 Git LFS 配置文件。它使用 git config 格式来设定 LFS 服务器的端点 URL、传输设置以及其他选项。
|
|
当执行 LFS 命令时,Git LFS 会自动读取 .lfsconfig。这样一来,你就可以把 LFS 配置也纳入版本控制,让所有协作者使用相同的设置。如果没有这个文件,每个开发者都需要手动配置本地 LFS 环境。
LFS 还会使用 .gitattributes 来标记哪些文件应由 LFS 处理(即上面示例中的 *.psd filter=lfs diff=lfs merge=lfs 模式)。.lfsconfig 文件则处理 LFS 专属设置,比如 LFS 服务器地址。如果你在文件已经提交之后才添加 LFS 的文件模式,需要运行 git lfs migrate 来重写历史记录,将这些文件迁移到 LFS。
全部可用选项请参考 Git LFS config 文档。
.gitmodules
.gitmodules 是 Git 子模块的配置文件。当你运行 git submodule add 时,Git 会自动写入这个文件;当运行 git submodule update 时,Git 则会读取它。
|
|
每个子模块对应一条记录,包含路径、URL,以及可选的追踪分支。该文件必须放在仓库根目录。
通过子模块,你可以把其他 Git 仓库作为依赖项嵌入到当前项目中。不过,git clone 默认不会拉取子模块内容,你需要额外运行 git submodule update --init --recursive,或者在克隆时加上 --recurse-submodules 参数。
子模块在版本管理上有些笨拙:你追踪的是某个特定 commit,而不是版本范围;它们会创建嵌套的 .git/ 目录;如果忘记更新,很容易导致令人困惑的状态。
不过,对于你自己维护的 vendor 代码管理,或者只需要检出部分目录的 monorepo 结构,子模块仍然是一个可用的方案。
完整工作流请参考 git submodules 文档,文件格式请参考 gitmodules 文档。
.mailmap
.mailmap 用来将作者的名字和邮箱地址映射到统一的规范身份。Git 会在 git log、git shortlog 和 git blame 的输出中使用这个映射。
|
|
格式是 规范名称 <规范邮箱> 提交名称 <提交邮箱>。Git 会查找与提交作者匹配的条目,并在输出时替换为规范身份。
git shortlog -sn、git log 和 git blame 都会遵循 mailmap,将同一作者的不同提交聚合到一起。不过,GitHub 的贡献者图表并不支持 mailmap,这意味着即使你的 mailmap 配置正确,网页上仍然会显示重复的条目。
没有 mailmap,那些更换过邮箱地址或修正过名字拼写的贡献者,就会被当作多个不同的人出现。有了它,他们的所有提交都会聚合到同一个身份下。
文件格式请参考 gitmailmap 文档。mailmap 默认放在仓库根目录的 .mailmap,也可以通过配置 mailmap.file 指向其他位置。
.git-blame-ignore-revs
.git-blame-ignore-revs 文件用来列出 git blame 应该跳过的提交。你可以把大规模代码格式化、lint 修复或其他无关紧要的提交的 SHA 写进去,这样 blame 就会穿透这些提交,指向真正有意义的变更。
|
|
要让 Git 使用这个文件,需要执行 git config blame.ignoreRevsFile .git-blame-ignore-revs。不过,GitHub、GitLab(15.4 及以上版本)和 Gitea 会自动读取该文件,无需额外配置。需要注意的是,如果你在全局 git config 中设置了 blame.ignoreRevsFile,那么当 git blame 在缺少该文件的仓库中运行时就会报错——因此,要么为每个仓库单独配置,要么确保所有工作仓库中都至少有一个空的 .git-blame-ignore-revs 文件。
这个文件解决了一个经典问题:对整个代码库运行格式化工具之后,git blame 会变得毫无用处。有了这个文件,blame 就会跳过那些格式化提交,显示真正的代码逻辑作者。
文件格式很简单:每行一个 commit SHA,# 开头的行为注释。详情请参考 git blame 文档。
.gitmessage
.gitmessage 是提交信息的模板。通过 git config commit.template .gitmessage 配置后,Git 会在打开提交信息编辑器时自动填充这些内容。
与其他文件不同,.gitmessage 需要每次克隆仓库后手动配置。每个开发者克隆后都要运行 git config commit.template .gitmessage。有些团队会通过初始化脚本自动完成这一步,或者借助 husky 在安装时设置本地配置。正因为多了这个步骤,大多数项目更倾向于使用 commit-msg Hook 来验证提交信息的格式,而不是用模板来引导写作。
git commit 文档 中提到了模板文件。prepare-commit-msg Hook 是另一种替代方案,可以动态生成模板。
代码托管平台专属目录
各大代码托管平台也在仓库中定义了自己的“魔法目录”:.github/、.gitlab/、.gitea/、.forgejo/、.bitbucket/ 等。这些虽然不是 Git 的原生功能,但遵循同样的理念——让配置随代码一起分发。
这些目录里通常存放 CI/CD 工作流定义、Issue 和 PR 模板、CODEOWNERS 文件(用于指定路径的代码审查者),以及其他平台专属配置。这样一来,托管平台就能在不污染仓库根目录的前提下扩展功能。
Forgejo 和 Gitea 支持回退机制:Forgejo 的查找顺序是 .forgejo/ → .gitea/ → .github/;Gitea 的顺序是 .gitea/ → .github/。这让你可以在同时托管于多个平台时,覆盖 GitHub 的专属配置。
SourceHut 使用根目录下的 .build.yml 或 .builds/*.yml 来配置 CI,没有专属的目录命名空间。
其他约定
.gitkeep 只是一个惯例,并非 Git 的原生功能。Git 本身不会追踪空目录。如果你想在仓库中保留一个空目录,可以往里面放一个 .gitkeep 文件,这样 Git 就有东西可以追踪了。这个文件名是约定俗成的,实际上你可以起任何名字。
.gitconfig 文件有时会出现在仓库中,作为推荐的配置供人参考。Git 不会自动加载这些文件(出于安全原因),但项目会附上说明,让你运行 git config include.path ../.gitconfig 或手动复制相关设置。这种做法常见于 monorepo 或希望统一特定 Git 设置的项目。
.gitsigners 或类似文件用于追踪可信贡献者的 GPG/SSH 签名密钥。这不是 Git 的原生功能,但一些项目(尤其是 Linux 内核)在签名工作流中使用它。Git 的 gpg.ssh.allowedSignersFile 配置可以指向一个可信 SSH 密钥文件,供 git log --show-signature 用于验证。
.gitreview 配置 Gerrit 代码审查集成。托管在 Gerrit 上的项目(如 OpenStack、Android、Eclipse)使用它来指定推送目标的 Gerrit 服务器和项目。
|
|
运行 git review 时会读取这个文件,并将提交推送到 Gerrit 进行审查,而不是直接推送到分支。这是一个通过提交配置文件来扩展 Git 工作流的典型案例。
.gitlint 配置 gitlint,用于检查提交信息格式。遵循同样的理念:把配置提交到仓库,所有人都使用相同的规则。
|
|
gitlint 读取这个文件来验证提交信息格式。类似于使用 commit-msg Hook,但配置随仓库一起分发。
.jj/ 是 Jujutsu 的工作区状态目录。Jujutsu 是一个兼容 Git 的版本控制系统,它将自己的元数据存储在 .jj/ 中,同时遵守所有 Git 魔法文件的规则。如果你使用 jj,你的仓库中会同时存在 .git/ 和 .jj/,而 .gitignore、.gitattributes、.mailmap 的行为完全一样。
Git 之外
这种模式并不止步于 Git。其他工具遵循同样的做法:在仓库中放一个点文件,工具自动检测并改变行为。
.editorconfig 跨团队统一编辑器行为。把它放在仓库根目录,编辑器就会读取它来配置缩进风格、行尾、行尾空白和字符编码。
|
|
VS Code、Vim、Emacs、Sublime 以及大多数其他编辑器要么原生支持,要么有插件支持。完整规范请参考 editorconfig.org。
.ruby-version、.node-version、.python-version 告诉版本管理器应该使用哪个语言版本。rbenv、nodenv、pyenv、nvm 和 asdf 等工具在你 cd 进该目录时会自动读取这些文件并切换版本。
|
|
.tool-versions 是 asdf 的多语言版本文件。用一个文件管理所有语言版本。
|
|
.dockerignore 的工作方式类似 .gitignore,但针对 Docker 构建上下文。运行 docker build 时,Docker 会将文件发送给守护进程。在 .dockerignore 中列出模式,Docker 就不会发送这些文件。
|
|
这可以加速构建,并防止密钥泄露到镜像中。语法与 .gitignore 一致:通配符、取反、目录标记。
支持这些文件
如果你正在开发与 Git 仓库交互的工具,可能需要留意并处理这些文件:
- 遍历仓库目录树时,需要读取
.gitignore - 读取
.gitattributes,了解哪些文件是二进制文件、第三方代码或生成代码 - 显示作者信息时,读取
.mailmap - 如果需要处理子模块,读取
.gitmodules
git config 格式(.gitmodules 和其他各种文件使用的格式)为 [section "subsection"] key = value。Git 自带 git config 命令可以正确地读写这些文件。大多数语言的 Git 库都内置了 git config 解析器。