














今天整理一个项目的文档目录时, 顺手把一个 plans/ 目录藏了起来. 藏完才发现, 自己以前一直用的是最笨的那种写法, 而 git 其实给了三层机制, 只是我从没认真看过中间那一层.
事情的起点很具体: 这个目录是我和 agent 用的开发计划, 里面写满了本机的绝对路径和没做完的打算, 它永远不该进公开发布的那个仓库. 但 .gitignore 本身是要提交的, 一旦在里面写上 plans/, 等于对着所有看仓库的人说"这里有个 plans 目录, 只是我不想给你看".
这是大家最熟的做法, 在仓库根目录的.gitignore写一条:
plans/
它能用, 但有个前提: 你得接受这条规则本身是公开信息. 对大多数项目这没关系, 毕竟 node_modules/ 也没什么好藏的. 可如果这个目录的存在本身就是"内部痕迹", 那这条写法就把你想藏的东西写在了门牌上.
第二种是我以前常用的, 在 plans/ 里放一个 .gitignore:
*
这个写法的结果我第一次见的时候觉得挺神奇: git status 里什么都看不到, 连那个 .gitignore 自己都不出现. 目录在磁盘上明明存在, 有十几个文件, 但 git 对它像没看见一样.
为什么? 我原来以为是"忽略规则不作用于忽略文件本身"之类的特殊照顾, 其实不是. 真实原因是 * 把 .gitignore 自己也匹配掉了 —— git 在决定"这个未跟踪文件要不要报给我看"时, 用的就是同一套忽略规则. 规则文件也是一个文件, 也会被自己的规则吃掉.
我用一个空仓库实测过, 对照很清楚. 目录里放一个 .gitignore 和一个 a.md, 让 .gitignore 内容为空:
?? plans/.gitignore
?? plans/a.md
把 .gitignore 内容改成 * 之后:
(空)
一个字符的差别, 目录就从"两个未跟踪文件"变成"不存在".
上面两种都属于"仓库里的规则". 而这次藏 plans/ 我用的其实是第三种, 也是我这次才认真用上的机制: 在 .git/info/exclude 里写一行.
plans/
.git/ 目录本身不会被提交, 所以这个文件:
.gitignore 一模一样这正好是"这个目录只在我这台机器上有意义、永远不该进仓库"的场景需要的. 对比一下藏目录的几种做法:
| 做法 | 效果 | 代价 |
|---|---|---|
根 .gitignore 写 plans/ |
全仓库生效 | 规则公开, 等于声明这里有个目录 |
plans/.gitignore 写 * |
全仓库生效, 且自吞 | 多一个文件; 换机器要重建 |
plans/.gitignore 写 /* |
只藏一层 | 深层文件照样露出来 |
.git/info/exclude |
全仓库生效, 不留痕迹 | 只对本机生效, clone 不到 |
还有一个更外层的: 全局的 core.excludesFile. 那个作用于你这台机器上的所有仓库, 我用得少, 因为过半年我就会忘了自己设过它, 然后对着一个"莫名其妙被忽略"的文件查半天.
plans/ 走 .git/info/exclude. 理由是这份东西跟机器绑定, 不存在"别人 clone 下来也需要"的情况, 而它的存在本身又不适合写在公开规则里.
代价是它变得不好发现: 后来的人在仓库里翻不到任何线索, 光看 ls plans/ 也看不出这个目录被忽略了 —— 而这恰恰是"忽略"这个动作的题中之义, 有点自相矛盾. 所以我在项目的 AGENT.md 里补了一句说明, 写清楚哪些目录是由本地 exclude 排除的. 规则可以隐形, "规则在哪儿"这件事最好别隐形, 不然下一个接手的人(或者下一个 agent)会重新踩一遍.
一个小项目上的小决定, 顺手把 git 这一层机制搞清楚了. 以前我只会一种写法, 现在知道有三层, 而且知道最里面的那层在哪、什么时候该用它.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。