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 命令的返回值断言。
...