Crocker's central thesis is that Git ships four distinct ignore mechanisms designed for different jobs, and the industry has collapsed them all into .gitignore. Editor cruft like .idea/, .vscode/, .DS_Store, and *.swp are personal preferences that should live in the per-repo, never-committed .git/info/exclude file, not be forced on every collaborator.
The editorial frames stuffing editor-specific patterns into shared .gitignore as 'a small act of vandalism' — forcing your tooling choice on the whole team. It argues the four mechanisms differ along scope (who sees the rule) and state (is the file already tracked), and treating them as interchangeable is what produces bloated .gitignore files industry-wide.
The submitter surfaced the post to a 339-point reception, and the thread filled with developers confessing they'd added .idea/ to every repo's .gitignore for a decade without realizing a per-repo alternative existed. The volume of 'I've been doing this wrong' replies validates that the misuse is industry-wide, not isolated.
Replies in the thread pointed out that GitHub's official gitignore template repository ships editor-specific templates (JetBrains, VSCode, Vim) that teams then copy-paste into project roots. The upstream source of truth is itself encouraging the anti-pattern, which is why the misuse propagates so reliably across new projects.
A post by Nelson Crocker titled ".gitignore Isn't the Only Way to Ignore Files in Git" climbed to 339 points on Hacker News, mostly on the strength of devs in the comments admitting they'd been doing it wrong for a decade. The piece is a tight walkthrough of the four ignore mechanisms Git ships with — `.gitignore`, `.git/info/exclude`, `core.excludesFile`, and the `update-index` flags `--skip-worktree` and `--assume-unchanged` — and the specific job each one is designed for.
None of this is new. All four have been in Git since well before 2.0. The reason the post resonated is that the entire industry has collapsed four distinct tools into one, and the cost shows up as `.gitignore` files bloated with `.DS_Store`, `.idea/`, `.vscode/`, `*.swp`, and `Thumbs.db` — personal preferences masquerading as project policy.
The HN thread filled fast with the same confession: "I've been adding `.idea/` to every repo's `.gitignore` for years." A few replies pointed out that GitHub's own `gitignore` template repo perpetuates this by shipping editor-specific templates, which teams then copy-paste into project roots.
The four mechanisms are not interchangeable. They differ along two axes: scope (who sees the rule) and state (does the file exist in the index yet).
`.gitignore` is committed. It is a statement to every collaborator and every CI job: *this pattern is not part of the project.* Build artifacts (`dist/`, `target/`, `node_modules/`), generated files, secrets-shaped files like `.env` — these belong here. The rule is shared because the decision is shared.
`.git/info/exclude` is the per-repo, never-committed sibling. It lives inside `.git/`, so it cannot leak. This is where `.idea/` belongs if your team is split between JetBrains and VSCode — your editor choice is not your team's problem, and forcing it into the shared `.gitignore` is a small act of vandalism. The file ships empty by default in a fresh `git init`, which is probably why nobody uses it.
`core.excludesFile` is the global version. Set it once: `git config --global core.excludesFile ~/.gitignore_global`, then put your `.DS_Store`, `*.swp`, `*.swo`, and `Thumbs.db` in there. It applies to every repository on your machine, forever. GitHub even publishes [a recommended global template](https://github.com/github/gitignore/blob/main/Global). If you ever find yourself adding `.DS_Store` to a project `.gitignore`, you have failed at this step.
The last two mechanisms operate on a different plane entirely. `.gitignore` and friends only affect untracked files — once a file is tracked, Git watches it forever. To make Git stop watching a tracked file without removing it, you need `update-index`.
`git update-index --assume-unchanged
`git update-index --skip-worktree
The pair is one of the most consistently misexplained features in Git. The official docs themselves warn that the distinction is subtle. The simplest mental model: `assume-unchanged` is a lie you tell Git for speed; `skip-worktree` is a contract Git keeps with you for safety.
Three concrete moves, in order of payoff:
1. Set up a global ignore file today. If you're on macOS or have ever committed `.DS_Store` to a project repo, do this now:
```bash git config --global core.excludesFile ~/.gitignore_global curl -o ~/.gitignore_global https://raw.githubusercontent.com/github/gitignore/main/Global/macOS.gitignore ```
Add your editor's directory (`.idea/`, `.vscode/`, `.zed/`) and your shell's debris (`*.swp`, `.~lock.*`). Then audit every project `.gitignore` you've authored and delete the patterns that belonged in global all along. Future-you will thank you when reviewing a PR that doesn't have `+.DS_Store` in the diff.
2. Use `.git/info/exclude` for per-project quirks. Spike file you generated for a one-off investigation? Personal `notes.md` in the repo root? A `scratch/` directory of debugging dumps? These go in `.git/info/exclude`. They're project-specific, so global won't work, but they're personal, so the shared `.gitignore` is wrong too.
3. Reserve `--skip-worktree` for shared, tracked config files where local divergence is the intended workflow. The classic case: a `config/local.yaml` committed with sane defaults, but every dev needs to edit it with their own API keys. Tracking it shares the schema; `--skip-worktree` lets each dev keep their secrets without polluting `git status` or risking a commit. The runner-up case: a temporary local patch to a CI script you don't want to commit but need to keep applied across branches.
The one footgun: if you ever need to actually update that file across the team — say, the schema changes — you have to coordinate a `--no-skip-worktree`, pull, re-edit, re-skip dance. It's painful enough that some teams just template the file with `.example` extensions and `.gitignore` the real one. Either pattern works; pick the one your team can remember.
The quiet trend here is that newer tools are starting to recognize this mess. `jj` (Jujutsu) collapses tracked/untracked semantics differently and may make the `update-index` flags obsolete for jj users. Sparse-checkout, partial clones, and Scalar have largely replaced `--assume-unchanged`'s original performance use case. But `.gitignore` itself isn't going anywhere, and neither is the temptation to dump everything into it. The fix is not a new tool — it's spending 10 minutes setting up the three you already have.
Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.