Mike Higginbottom

A Digital Garden - frequent growth but a bit weedy in the back corner

Gitignore Is Greedy

I was out in the garden today chasing a drain that was in a place it had no business being and apparently doing a thing I was not expecting it to be doing. Naturally this prompted an epiphany in relation to a deeply unrelated yet strangely related puzzle I’d come across the night before.

I’d been making some notes on a recorded Zoom call about Claude Code scaffolding Python projects and I came across a weird error log in one of my monitoring scripts. One of the projects I’d built out had some custom settings stored in a JSON file in a .claude subfolder. The solution to that turned out to be me passing in a flag to git ls-files that was doing stuff I didn’t expect it to be doing due to something else that was in a place it had no business being. Which is just a long and rambling way of saying this drain had me thinking about .gitignore and what it’s actual role is.

The epiphany was one of those slightly amorphous ‘something doesn’t smell right’ things that you can’t quite put your finger on but it was along the lines of “.gitignore is actually doing two things isn’t it?” Now the two things I was thinking of were:

  1. You use it to tell git to not version control certain files and folders
  2. You use it to tell git to not publish your private files to the upstream repo

The ‘something doesn’t smell right’ bit was that I could, theoretically at least, see a scenario in which you only wanted to do one of those two things. And .gitignore is a single list of files and folders that makes no distinction between the two functions. If you put something in .gitignore it’s not going to be tracked for version control and it’s not going to be published. If you don’t put it in .gitignore it will be tracked and it will be published.

But what if I wanted to track a file but not publish it? Or publish a file that I didn’t want to track?

So I had a bit of a chat with your mate and mine, Claude, who claimed that my question was a ‘sharp observation’. Unnervingly, I felt simultaneously both flattered and patronised by this.

It pointed out that gitignore really only does the tracking functionality and further, that it’s only really by convention that we use it as a mechanism for adding public/private permissions; a.k.a. filtering what gets published.

So the TLDR is that there’s no way to publish an untracked file since pushing is just sending commits. And commits, be definition, only contain tracked files. However you can, kinda sorta, track an unpublished file using git update-index --skip-worktree <file>. This does publish (push) the initial version but local changes won’t get pushed even though they will get tracked.