DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

.gitignore 写了 !.keep,子目录中的文件仍被忽略

.gitignore 的文件例外无法穿过已被忽略的父目录。用 check-ignore 定位规则,先放行目录再保留 .keep,并通过添加预览确认例外范围。

想忽略一个目录里的运行产物,只保留每层目录中的 .keep 文件,却发现最外层文件能加入 Git,深层文件始终不出现在未跟踪列表里。根目录的 .gitignore 往往写成这样:

/storage/*
!.keep

例如 storage/.keep 可以被重新包含,但 storage/cache/.keep 仍被忽略。原因是 storage/cache/ 本身也匹配第一条规则。Git 不再遍历这个被排除的父目录,后面的文件例外就没有机会生效。

下面针对 Git 命令行处理尚未跟踪的普通文件,不把 IDE 的文件颜色作为判断依据。示例路径 storage 和文件名 .keep 都应按项目替换;如果实际文件叫 .gitkeep,规则也必须使用那个名字。

先找出排除文件的那一条规则

在仓库根目录,对确实存在的目标文件执行:

git ls-files -- storage/cache/.keep
git check-ignore -v -- storage/cache/.keep

第一条命令查看该路径是否已在 Git 索引中。有输出说明它已经受跟踪,不能再把“没出现在未跟踪文件列表”当成忽略规则失效。.gitignore 不会取消已跟踪文件的跟踪状态。

第二条命令用于定位忽略规则。在上述示例中,可能看到类似输出,行号以实际文件为准:

.gitignore:1:/storage/*    storage/cache/.keep

它给出规则文件、行号、匹配模式和被检查路径。如果显示的是仓库的本地排除文件、用户全局忽略文件,或某一层子目录里的 .gitignore,应查看那个来源,而不只是反复修改根目录的文件。

注意 -v 有输出不一定代表文件被排除。如果模式以 ! 开头,输出的是重新包含该路径的规则。也不要只根据无输出下结论:默认情况下,已跟踪文件不会参与这项检查。确实需要分析已跟踪文件将如何匹配规则时,可以使用:

git check-ignore -v --no-index -- storage/cache/.keep

--no-index 只让这次诊断忽略索引状态,不会取消文件的跟踪,也不会修改文件。

让 Git 能进入子目录,再保留 .keep

如果目标是“storage 下所有层级只保留 .keep,其他未跟踪文件都忽略”,可以把根目录中针对这棵目录的规则整理为:

/storage/**
!/storage/**/
!/storage/**/.keep

三行有各自的作用。第一行排除 storage 内的所有内容,包括深层内容;第二行重新包含其中的目录,让 Git 能继续向下遍历;第三行重新包含各层的 .keep。中间的 /**/ 可以匹配零层或多层目录,所以第三行也包含 storage/.keep。

按这组规则,下面的普通文件应得到这些结果。表格是规则含义的示例,不是命令实测输出:

路径 预期处理
storage/.keep 允许加入
storage/cache/.keep 允许加入
storage/cache/tmp/.keep 允许加入
storage/cache/result.json 继续忽略
storage/cache/README 继续忽略
docs/guide.md 不受这组三行规则影响

这里假定 storage/ 自身没有被另一条规则整体排除。已有 /storage/、更上层目录排除规则,或后面再次排除目录的规则时,要一并核对。仅把上述三行附加到复杂文件末尾,不能保证消除所有冲突。

也不要把例外移进已经被排除的 storage/cache/ 内,期待那里的 .gitignore 能修复根目录规则。应先在 Git 能读取的位置处理父目录的排除。多层规则会互相影响,改完仍需对完整目标路径重新运行诊断。

只保留一个文件时,缩小例外范围

如果项目只需要 storage/cache/.keep,没有必要让所有子目录的 .keep 都成为候选文件。可以改用更窄的根目录规则:

/storage/*
!/storage/cache/
/storage/cache/*
!/storage/cache/.keep

这组规则先重新包含 cache 目录,再排除它内部的内容,最后只保留指定文件。它不会同时保留 storage/.keep 或 storage/cache/tmp/.keep。这是另一种需求对应的规则,不要把两组示例同时粘贴后再猜哪组生效。

如果目标文件位于更深的路径,应逐层保证它的父目录可进入,再设置文件例外。规则中的路径相对于所在 .gitignore 的目录;把根目录示例直接搬进 storage/.gitignore,路径含义会改变。

不要用扩展名或强制添加掩盖规则问题

把排除模式改成 *.*,可能让没有点号的子目录重新可见,但也会漏掉 README 这类无扩展名文件。名称带点号的目录又可能被整体排除,不能用它表达“除 .keep 外忽略一切”。

git add -f 能强行把被忽略的文件加入索引,但不会修正这套规则。今天强加一个文件,明天另一层新增的 .keep 仍可能消失在列表中;已强加的文件又会因为受跟踪而绕过通常的忽略检查,干扰后续诊断。需要团队共享的规则应写入项目的 .gitignore,再按正常方式添加文件。

用未跟踪列表和添加预览验收

调整后,在仓库根目录查看这一范围内的状态,并预览目标文件能否正常加入:

git status --short --untracked-files=all -- storage/
git add --dry-run -- storage/.keep storage/cache/.keep

第二条命令对应“各层都保留”的示例,并假定这两个文件已经存在。若采用单文件方案,只保留需要的路径。--dry-run 不实际暂存;不存在的文件导致的错误,也不能当成忽略规则仍有问题。

验收时既看目标 .keep 是否出现,也要看普通运行产物是否仍被排除。已有受跟踪文件可能照常显示修改,不能要求整份状态输出里只剩 .keep。重新运行 git check-ignore -v 时,结合具体模式判断,而不是把出现任何输出都判为失败。

确认文件内容适合提交后,再明确添加规则文件和选定的占位文件,避免顺手暂存整个工作区:

git add -- .gitignore storage/.keep storage/cache/.keep
git status --short

修改规则前保留原有内容,尤其是文件中尚未提交的其他修改。若例外范围放得过大,恢复本次修改的规则后重新查看状态即可,规则编辑本身不会删除磁盘文件。已经暂存的文件不会因为恢复忽略规则而自动退出索引,需要另行核对暂存区。

在已有提交、HEAD 可用且只想取消这些路径本次暂存内容的仓库中,可使用支持 git restore 的版本执行:

git restore --staged -- .gitignore storage/.keep storage/cache/.keep

这会把这些路径的索引状态恢复到 HEAD,保留工作区文件,但也会取消这些路径上此前已有的其他暂存修改,执行前应核对范围。不要附加 --worktree 来处理忽略规则问题,也不需要删除目录、清理整个索引或运行破坏性的清理命令。