忽略模式与索引状态共同决定普通 Git 路径处理。

.gitignore 控制未跟踪路径的处理

Git 忽略规则影响普通路径发现和添加操作如何处理尚未跟踪的文件,适合管理生成产物、编辑器状态和本地配置。但它不会形成访问控制边界,不会把已进入索引的文件移出去,也不会抹掉更早的提交。因此,应该把忽略文件看成具有可验证匹配规则的项目约定,不能把它当作敏感文件绝对进不了历史的保证。 坏掉的样子 某个文件被跟踪之后,再为它添加忽略规则,并不会停止跟踪。继续修改它并执行普通添加操作,新内容依然可能进入暂存区。这就是为什么泄漏配置后才把文件名补进 .gitignore,不能完成修复。索引里有没有这个路径、工作区文件现在是什么内容、历史提交保留了哪些对象,是三个不同状态,需要分别观察,而不是只看文件是否还显示在默认状态列表中。 否定规则也容易令人误判。排除整个目录,会阻止 Git 继续遍历该目录,后面针对某个子文件的例外便无法挽救它。可以只忽略目录里的内容,让父目录仍能被访问;也可以先逐层恢复被排除的父目录,再恢复后代。若 Git 根本不会进入某个目录,藏在里面的嵌套忽略文件也无从发挥作用,规则写得再靠近文件也没有帮助。 优先级并不是“看上去更精确的模式一定获胜”。Git 对命令行模式、目录忽略文件、仓库本地排除项和全局排除项定义了顺序;同一优先级内部,以最后匹配的模式为准。更深目录的忽略文件,可以针对它所管理的路径覆盖上层规则。因此,模式的写法与来源位置都重要,不能仅比较哪个字符串更长,就推导最终结果。 快速验证 在安装了 Git 的环境中运行下列 Python 脚本。它创建临时仓库,执行后自动删除;对子进程关闭系统与全局 Git 配置,并且只使用虚构样本文件。记录中的版本为 Git 2.50.1。即使从已有项目目录启动,实际 Git 操作也都以临时目录为工作目录,不会修改启动位置的仓库: from pathlib import Path from tempfile import TemporaryDirectory import os,subprocess env=dict(os.environ,GIT_CONFIG_NOSYSTEM='1',GIT_CONFIG_GLOBAL=os.devnull) with TemporaryDirectory(prefix='git-ignore-lab-') as directory: root=Path(directory) def git(*args, expected=0): result=subprocess.run(['git',*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') 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 命令的返回值断言。 ...

十一月 15, 2027 · 1 分钟 · XB Field Notes