Git 忽略规则影响普通路径发现和添加操作如何处理尚未跟踪的文件,适合管理生成产物、编辑器状态和本地配置。但它不会形成访问控制边界,不会把已进入索引的文件移出去,也不会抹掉更早的提交。因此,应该把忽略文件看成具有可验证匹配规则的项目约定,不能把它当作敏感文件绝对进不了历史的保证。
坏掉的样子
某个文件被跟踪之后,再为它添加忽略规则,并不会停止跟踪。继续修改它并执行普通添加操作,新内容依然可能进入暂存区。这就是为什么泄漏配置后才把文件名补进 .gitignore,不能完成修复。索引里有没有这个路径、工作区文件现在是什么内容、历史提交保留了哪些对象,是三个不同状态,需要分别观察,而不是只看文件是否还显示在默认状态列表中。
否定规则也容易令人误判。排除整个目录,会阻止 Git 继续遍历该目录,后面针对某个子文件的例外便无法挽救它。可以只忽略目录里的内容,让父目录仍能被访问;也可以先逐层恢复被排除的父目录,再恢复后代。若 Git 根本不会进入某个目录,藏在里面的嵌套忽略文件也无从发挥作用,规则写得再靠近文件也没有帮助。
优先级并不是“看上去更精确的模式一定获胜”。Git 对命令行模式、目录忽略文件、仓库本地排除项和全局排除项定义了顺序;同一优先级内部,以最后匹配的模式为准。更深目录的忽略文件,可以针对它所管理的路径覆盖上层规则。因此,模式的写法与来源位置都重要,不能仅比较哪个字符串更长,就推导最终结果。
快速验证
在安装了 Git 的环境中运行下列 Python 脚本。它创建临时仓库,执行后自动删除;对子进程清除继承的 Git 路径与配置环境变量,关闭系统、全局配置及钩子,并使用空模板和虚构样本文件。原记录的版本为 Git 2.50.1;修订后的隔离检查也已在 Git 2.52.0、Python 3.12.14 下通过。即使从已有项目目录启动,实际 Git 操作也都以临时目录为工作目录,不会修改启动位置的仓库:
from pathlib import Path
from tempfile import TemporaryDirectory
import os,subprocess
env={key:value for key,value in os.environ.items() if not key.startswith('GIT_')}
env.update(GIT_CONFIG_NOSYSTEM='1',GIT_CONFIG_GLOBAL=os.devnull)
with TemporaryDirectory(prefix='git-ignore-lab-') as directory:
root=Path(directory)
hooks=root/'.no-hooks';hooks.mkdir()
template=root/'.no-template';template.mkdir()
def git(*args, expected=0):
result=subprocess.run(['git','-c','core.hooksPath='+str(hooks),'-c','core.excludesFile='+os.devnull,*args],cwd=root,env=env,text=True,capture_output=True)
assert result.returncode==expected,(args,result.returncode,result.stderr)
return result.stdout
def write(path,content='fixture\n'):
target=root/path;target.parent.mkdir(parents=True,exist_ok=True);target.write_text(content)
git('init','-q','--template='+str(template))
git('config','user.name','Ignore Lab')
git('config','user.email','ignore@example.invalid')
git('config','commit.gpgsign','false')
write('tracked.txt','first version\n')
git('add','--','tracked.txt');git('commit','-qm','Track fixture')
write('build/keep.txt');write('build/output.bin')
write('.gitignore','/build/\n!/build/keep.txt\n')
git('check-ignore','-q','build/keep.txt')
print('excluded parent prevents child exception')
write('.gitignore','/build/*\n!/build/keep.txt\n*.log\n/root-only.txt\ntracked.txt\n')
git('check-ignore','-q','build/keep.txt',expected=1)
rule=git('check-ignore','-v','build/keep.txt')
assert ':2:!/build/keep.txt\t' in rule
git('check-ignore','-q','build/output.bin')
print('ignoring contents permits keep.txt; verbose output includes its negation')
write('debug.log');write('nested/debug.log');write('nested/.gitignore','!debug.log\n')
write('root-only.txt');write('nested/root-only.txt')
git('check-ignore','-q','debug.log')
git('check-ignore','-q','nested/debug.log',expected=1)
git('check-ignore','-q','root-only.txt')
git('check-ignore','-q','nested/root-only.txt',expected=1)
print('nested exception and root anchor checked')
write('tracked.txt','second version\n')
git('add','-A')
assert 'tracked.txt' in git('diff','--cached','--name-only').splitlines()
git('check-ignore','-q','tracked.txt',expected=1)
assert 'tracked.txt' in git('check-ignore','--no-index','-v','tracked.txt')
git('rm','--cached','--','tracked.txt')
assert (root/'tracked.txt').is_file()
assert 'tracked.txt' not in git('ls-files').splitlines()
git('cat-file','-e','HEAD:tracked.txt')
print('tracked file still staged; cached removal keeps disk copy and old commit')
git('add','-f','--','build/output.bin')
assert 'build/output.bin' in git('ls-files').splitlines()
print('force-add bypasses ignore rules')
第一项确认,排除 build/ 会使子文件例外失效。修改后的规则只排除目录里面的条目,因此允许 keep.txt,同时继续排除 output.bin。安静模式检查的是实际忽略结果,退出码一表示被检查的路径没有被忽略。这里不是根据文件名或规则长相猜测,而是把预期写成了真实 Git 命令的返回值断言。
对于已经允许的文件,详细输出仍然会显示那条带感叹号的匹配规则。所以,看到 git check-ignore -v 输出一行内容,并不能直接证明路径被排除;必须检查报告的模式,特别是有没有否定前缀。如果脚本需要布尔判定,应使用对应的安静模式结果,避免把诊断说明误当作阻止提交的判断条件。
嵌套目录的日志例外覆盖了根目录的日志规则,而 /root-only.txt 的前导斜杠把范围锚定在根忽略文件所在目录,因此同名的嵌套文件仍有资格被添加。这几项断言针对实际创建的路径执行,也说明“裸文件名”和“从本层目录开始的路径”不能随意互换。
已跟踪文件在修改后仍然进入暂存区,即使新规则匹配了它的名字。诊断命令的 --no-index 让我们暂时忽略普通的已跟踪判断,查看该模式本身。随后使用 git rm --cached,工作区副本仍在磁盘上,索引条目被移除,但旧提交中的对象依然存在。最后,强制添加把原本被忽略的构建产物重新放入索引,直接证明忽略规则并不是不可绕过的提交权限。
审阅下一次提交的真实内容
项目共同约定放进共享的 .gitignore;只影响某个仓库检出的本地排除项,可放在 .git/info/exclude;个人编辑器残留可以交给配置好的全局排除文件。不要让团队能否正确构建,依赖某台电脑上没人知道的全局规则。即使生成产物与源码放在相邻位置,也要保证真正的构建输入仍能被纳入版本管理。
提交前应检查暂存区差异和已跟踪路径,而不是只看默认状态是否显得干净。过宽的模式可能隐藏刚新增的源码,强制添加也可能突破原本的排除意图。如果要提交一个停止跟踪本地配置的变更,应同时具备替代的配置供应方式,因为其他检出应用这次删除时,可能真的移除原本被跟踪的副本,不能假定每个人都像当前工作区一样保留它。
若发生真实凭据暴露,应撤销或替换凭据,并调查已经传播到哪里。移除索引项不能让已经授予的访问失效,重写一个仓库也无法控制其他地方持有的副本。忽略规则能够减少常见操作失误,暂存内容审阅与合适的秘密检测可以补充检查,但它们各自都有边界,不能合起来就宣称泄漏不可能发生。
来源
- Git 忽略规则,作用范围、优先级与被排除父目录的行为,已核验 2026-09-12。
- git check-ignore,详细输出中的否定模式与索引诊断,已核验 2026-09-12。
- git rm,移除索引项同时保留工作区文件,已核验 2026-09-12。
- Git 环境与配置,仓库路径和隔离配置所用的环境变量,已核验 2026-10-15。
- Git 配置,钩子与排除规则的路径,已核验 2026-10-15。