[HN Gopher] Don't create .gitkeep files, use .gitignore instead ...
___________________________________________________________________
Don't create .gitkeep files, use .gitignore instead (2023)
Author : frou_dh
Score : 119 points
Date : 2026-02-20 22:27 UTC (1 days ago)
(HTM) web link (adamj.eu)
(TXT) w3m dump (adamj.eu)
| yjftsjthsd-h wrote:
| I'm confused. Having a file gitignored doesn't stop you from
| committing it; AFAIK you can just touch
| build/.gitkeep git add build/.gitkeep git commit
| build/.gitkeep
|
| And that's it? There's no need to exclude anything.
| williadc wrote:
| The idea is that you don't want to check-in any builds.
| yjftsjthsd-h wrote:
| Sure, so gitignore build/ or whatever. But you don't need to
| unignore .gitkeep
| akerl_ wrote:
| The idea is that instead of adding a nonsense file, you use
| the native .gitignore functionality.
|
| ".gitkeep" is just a human thing; it would work the same if
| you called it ".blahblah".
|
| So their pitch is that if you want to explicitly keep the
| existence of the directory as a committed part of the repo,
| you're better off using the actual .gitignore functionality
| to check in the .gitignore file but ignore anything else in
| the directory.
|
| I don't find it amazingly compelling; .gitkeep isn't
| breaking anything.
| dwattttt wrote:
| This still confuses me. Do you mean to say "use the
| .gitignore functionality, and check in the .gitkeep
| file"?
| akerl_ wrote:
| No. Use a .gitignore _instead_ of .gitkeep. Instead of
| checking in build /.gitkeep, check in build/.gitignore.
| dwattttt wrote:
| I don't know that I like this approach. It certainly
| works, but it's not specifically what (people expect) a
| .gitignore file to be used for. That confusion isn't
| good: https://thecodelesscode.com/case/222 and
| https://thecodelesscode.com/case/223
| akerl_ wrote:
| .gitignore is the officially recommended way to do this:
| https://archive.kernel.org/oldwiki/git.wiki.kernel.org/in
| dex...
| Supermancho wrote:
| Granted, naming is hard. Routinely using a file named
| .deleteme or .rememberwalkthedog because it's recommended
| instead of a more readable solution, is not a compelling
| reason to switch.
| cortesoft wrote:
| Not sure why you can't just have your build script create the
| build directory?
| andybak wrote:
| Because you might not have a build script?
| drdec wrote:
| Then why do you need a build directory?
| himata4113 wrote:
| qemu: mkdir build; cd build; ../configure, some projects
| are like that
| xigoi wrote:
| Why can't the configure script do this?
| hn92726819 wrote:
| You _can_. But this makes intent clear. If you clone a
| git repo and see build / with only a gitkeep, you are
| safe to bet your life savings on that being the compiled
| assets dir.
| cortesoft wrote:
| Then how is anything ending up in the build directory?
| xg15 wrote:
| There may be other directories. I think it's useful to be able
| to see the entire directory structure of a repo when you check
| it out, and not just after running some scripts.
| twic wrote:
| Usually, you can. But occasionally you get mildly defective
| tools that require some directory to exist, even though it's
| empty. It's easier to add a gitkeep than fix them.
| taftster wrote:
| This used to happen a lot. But I don't think that many modern
| builders require existing directory these days.
|
| Your point is valid though. It would be much preferable to
| include build/ in your root .gitignore so that the directory
| is never tracked.
| suralind wrote:
| I want to like it, but I pretty much always have a "cleanup"
| script that just deletes the entire directory and touches a
| .gitkeep file. Obviously an even better pattern is to not have
| any .gitkeep files, but sometimes they are just handy.
| macote wrote:
| The author is misusing .gitkeep. I use it to keep source code
| folders that don't contain any code yet, but whose structure is
| already defined.
| xyzzy_plugh wrote:
| Truly, what purpose does this serve? Defining a hierarchy
| without using is injecting immediate debt. Just introduce it
| when stuff goes there! If you really insist then at least put
| _something_ in the folder. It doesn 't take much effort to make
| the change at least a tiny bit meaningful.
|
| Better yet just do the work. If you want make a commit in a
| branch that's destined to be squashed or something, sure, but
| keep it away from the shared history and certainly remove it
| when it's not needed anymore.
| akoboldfrying wrote:
| > Truly, what purpose does this serve?
|
| The simplest answer is that sometimes other existing software
| that I need to use treats an empty directory (or, hopefully,
| a directory containing just an irrelevant file like .gitkeep)
| differently from an absent directory, and I want that
| software to behave in the first way instead of the second.
|
| A more thorough answer would be: Filesystems can represent
| empty directories, so a technology that supports versioned
| filesystems should be able to as well. And if that technology
| can't quite support fully versioned filesystems -- perhaps
| because it was never designed with that goal in mind -- but
| can nevertheless support them well enough to cover a huge
| number of use cases that people actually have, then massaging
| it a bit to handle those rough edges still makes sense.
| xyzzy_plugh wrote:
| Legitimately asking, please share the name of software that
| expects/requires an empty directory and interprets .gitkeep
| in this way, but chokes on a README file.
|
| Many filesystems cannot represent empty directories. Many
| archive formats also do not. I don't think this a problem
| in practice. I find this argument extremely weak.
| Joker_vD wrote:
| > Many filesystems cannot represent empty directories.
|
| Like which ones? And how does mkdir(1) work on such
| filesystems?
| abustamam wrote:
| I play around with ComfyUI on my computer to make silly
| images.
|
| To manually install it, you must clone the repo. Then you
| have to download models into the right place. Where's the
| right place? Well, there's an empty directory called models.
| They go in there.
|
| IMO that's an effective use of gitkeep.
| xyzzy_plugh wrote:
| It's not. echo >repo/models/README.md
| "this is the directory you place models in"
|
| Is infinitely better.
| abustamam wrote:
| It could be better sure. In fact I think they use a file
| called PUT_MODELS_HERE not gitkeep
|
| https://github.com/Comfy-
| Org/ComfyUI/blob/master/models/diff...
|
| But in any case, that instruction was already in the
| readme as well.
| CGamesPlay wrote:
| You can rename `.gitkeep` to `.gitignore` and both be happy in
| that case.
| peter-m80 wrote:
| No, thanks
| Arrowmaster wrote:
| The author makes a very common mistake of not reading the very
| first line of the documentation for .gitignore. A
| gitignore file specifies intentionally untracked files that Git
| should ignore. Files already tracked by Git are not affected; see
| the NOTES below for details.
|
| You should never be putting "!.gitignore" in .gitignore. Just do
| `echo "*" > .gitignore; git add -f .gitignore`. Once a file is
| tracked any changes to it will be tracked without needing to use
| --force with git add.
| ekipan wrote:
| Yeah, this. Plus a mistake from the article: $
| echo '*\n!.gitignore' > build/.gitignore
|
| The \n won't be interpreted specially by echo unless it gets
| the -e option.
|
| Personally if I need a build directory I just have it mkdir
| itself in my Makefile and rm -rf it in `make clean`. With the
| article's scheme this would cause `git status` noise that a
| `/build/` line in a root .gitignore wouldn't. I'm not really
| sure there's a good tradeoff there.
| Aaron2222 wrote:
| > The \n won't be interpreted specially by echo unless it
| gets the -e option.
|
| Author's probably using Zsh, which interprets them by
| default.
| AgentME wrote:
| If you have a project template or a tool that otherwise sets up
| a project but leaves it in the user's hands to create a git
| repo for it or commit the project into an existing repo, then
| it would be better for it to create a self-excepting .gitignore
| file than to have to instruct the user on special git commands
| to use later.
| smrq wrote:
| Why is this approach better than the author's?
| nebezb wrote:
| This is functionally the same. What do you mean by "you should
| never"? According to who?
|
| What an arrogant take. This is preference. Don't mistake it for
| correctness.
| BlackFly wrote:
| The point of that line is to robustly survive a rename of the
| directory which won't be automatically tracked without that
| line. You have to read between the lines to see this: they
| complain about this problem with .gitkeep files.
| xg15 wrote:
| I think I'd prefer to have all ignores and un-ignores
| explicitly in the file and not have some of them defined
| implicitly because a file was added to tracking at some point.
| 8cvor6j844qw_d6 wrote:
| Is .gitkeep an established convention somewhere? I'm curious
| where the name originated.
| abustamam wrote:
| Seems to originate form this SO post
|
| https://stackoverflow.com/a/4250082/28422
| jkubicek wrote:
| I'm not sure if I'm the one to blame for this or not, but the
| earliest reference to ".gitkeep" I can find online is my 2010
| answer on Stack Overflow:
| https://stackoverflow.com/a/4250082/28422
|
| If this is all my fault, I'm sorry.
| selridge wrote:
| This is delightful. Accidental load-bearing SO post.
| jkubicek wrote:
| It's especially funny since my answer is wrong anyway! The
| other top answer is much better. I did get a lot of early SO
| brownie points from that one answer though.
| juggerl6 wrote:
| Thankfully AI has put an end to the scourge of confidently-
| wrong SO hallucinations.
| adastra22 wrote:
| Well, Claude is here making .gitkeep files like nobody's
| business.
| pilaf wrote:
| This Rails commit from May 2010 mentions gitkeeps and it's a
| few months older than your SO post, so it seems you're absolved
| from guilt:
|
| https://github.com/rails/rails/commit/785493ffed41abcca0686b...
| hn92726819 wrote:
| Yeah... I don't think you were wrong. Having 100 tiny
| gitignores makes finding out why something is excluded
| annoying. Our policy is one root level gitgnore and gitkeeps
| where required.
|
| Some devs will just open the first gitignore they see and throw
| stuff into it. No thank you.
| predkambrij wrote:
| I share your view. .keep and .gitignore are different things.
| Having one .gitignore caputuring everything is less mental
| load.
| zahlman wrote:
| I like to make a .local folder at the top of the project,
| which contains a .gitignore that ignores everything. Then I
| can effortlessly stash my development notes there without
| affecting the project .gitignore or messing around within the
| .git directory.
| nickysielicki wrote:
| > Having 100 tiny gitignores makes finding out why something
| is excluded annoying. Our policy is one root level gitgnore
| and gitkeeps where required.
|
| This is not a complicated or important enough problem to
| justify a team-wide policy. Let it work itself out naturally.
|
| https://git-scm.com/docs/git-check-ignore makes it trivial to
| debug repo-wide gitignore behavior.
| taftster wrote:
| I agree with you. Empty .gitignore would be a "smell" to me.
| Whereas .gitkeep tells me exactly what purpose it serves. I
| like the semantic difference here that you describe. I don't
| like when multiple .gitignore files are littered throughout
| the codebase.
| Kuraj wrote:
| If you need to do this, I think .gitkeep communicates intent
| better. You don't need to document it or risk it being removed as
| thought to be a left over.
| kderbyma wrote:
| Arent Gitkeep files specifically for empty folders that are
| intended to be there?
|
| That is what I have always used them for....
| OptionOfT wrote:
| For me, I put them in directories that have to be there, because
| the underlying code doesn't create the directory, and without it,
| it fails.
|
| Another example is where you want an empty directory mounted in
| Docker. If the directory is not there it is created with root
| permissions and then I can't even look into it.
| beej71 wrote:
| What am I missing about this use case? It seems like you should
| just create `build/.gitignore` with `*` in it and `add -f` it and
| be done.
|
| I'd use `.gitkeep` (or an empty `.gitignore`) if I needed to
| commit an otherwise-empty hierarchy. But if I'm going to have a
| `.gitignore` in there anyway, it's not empty.
|
| > The directory is now "tracked" with a single, standard file
| that will work even after renames.
|
| Does `.gitkeep` not work after renames? Or `.gitignore`?
|
| So I am missing something. :)
| KPGv2 wrote:
| That's a hack. What you should do is a .gitignore with * and
| then a whitelist of paths like src/**/*.
|
| If you rely on `add -f` you will forget to commit something
| important.
|
| For example, for a tree sitter grammar I developed a couple
| years ago, here is my .gitignore:
|
| ```
|
| # Ignore everything
|
| *
|
| # Top-level whitelist
|
| CHANGELOG.md
|
| # Allow git to see inside subdirectories
|
| !*/
|
| # Whitelist the grammar and tests
|
| !/grammar/*.js
|
| !/test/corpus/*.txt
|
| # Whitelist any grammar and tests in subdirectories
|
| !/grammar/**/*.js
|
| !/test/corpus/**/*.txt
|
| ```*
| beej71 wrote:
| > If you rely on `add -f` you will forget to commit something
| important.
|
| But isn't the idea in TFA to blacklist the entire `build/`
| tree? We don't want to add _anything_ there.
| aezart wrote:
| It makes the behavior more obvious from simply looking at the
| file, for one thing, and it means you can just lump it into
| your next `git add -A` without needing to handle it specially.
| GreenDolphinSys wrote:
| .gitkeep is intuitive and easy to understand. Unignoring a
| .gitignore is not intuitive. This falls squarely into "clever
| optimization tricks that obscure intent and readability". Don't
| do things like this.
|
| It's not that hard to update a .gitignore file every now and
| then.
| rswail wrote:
| Then put a comment in the .gitignore.
|
| Using the actual tools built in to git directly removes steps
| in the process, which is always a good thing, it's documented
| as part of the git documentation, so you don't have to create a
| wiki page explaining why there is a ".gitkeep" file that git
| doesn't recognize itself.
|
| Saying "It's not that hard..." is fine for projects with a few
| contributors but does not scale.
| cyberrock wrote:
| File filtering is so delightfully broken everywhere. Everytime I
| revisit git, rsync, restic, borg, etc. something just goes wrong
| somewhere on this seemingly simple task, and SO and thus LLMs are
| filled to the brim with slightly wrong answers. We need a
| xkcd/927 because it can't possibly get any worse.
| dmarinus wrote:
| if possible you can also just create directories if they don't
| exist (ie. mkdir -p) and just exclude it in your root .gitignore
| (ie. ignore all build directories). That would safe you from
| creating multiple .gitignore files.
| leecommamichael wrote:
| This doesn't solve a problem.
| deafpolygon wrote:
| Claims the wrong thing is common and tells you not to do it ,
| then tells you to do the right thing.
|
| I have never heard of .gitkeep before today, and if you need an
| empty directory to exist, use a build script.
|
| Don't do stupid workarounds.
| jiffygist wrote:
| I don't understand why would you ever want to have an empty
| directory. Besides if I see a directory named "build" I expect to
| be able to just nuke it any time without consequences.
| prmoustache wrote:
| How about fixing your build scripts and makefiles instead?
| Convoluted solutions for a non-existing problem.
| zahlman wrote:
| Oh, man, I'd forgotten about these negated .gitignore patterns
| entirely. It actually hadn't occurred to me that they could
| override the behaviour of ignoring empty directories.
|
| This is potentially actually useful for me, because I have a
| project with test data that consists of miniature filesystem sub-
| trees -- that should include empty directories to ensure edge
| cases are covered. I've been zipping them up and having the test
| harness unpack them in the test environment, but that's an
| unnecessary extra point of failure (and it stuffs undiffable
| binary files into the commit history).
|
| Edit: Ah, no, if this doesn't work from the project-global
| .gitignore (specifying a folder to keep, even though it's empty
| and doesn't even have its own .gitignore) then it doesn't solve
| the problem. :(
| ivan_gammel wrote:
| Why not having a txt file with indented tree, a bootstrap
| function that parses the file and creates the tree and a test
| for that function?
|
| You will have proper diff for the tree this way.
| hollasch wrote:
| My preference is to use the build system to create built
| artifacts, and I consider the build/ directory to be a built
| artifact. Wrangling Git into doing the first fundamental build
| step is off, in my opinion.
|
| However, if you disagree, my favorite "Git keep" filename is
| "README.md". Why is this otherwise empty directory here, how does
| it fit into my source tree, how is it populated, and so forth.
|
| One of my pet peeves with the latest AI wave is the time we spend
| creating files to help AI coding agents, but don't give the same
| consideration to the humans who have to maintain and update our
| code.
| taftster wrote:
| Both points here are appreciated. One that a README file as a
| "placeholder" for a directory gives the opportunity to describe
| why said empty directory exists. I would be slightly concerned
| though if my build process picked up this file during
| packaging. But that's probably a minor concern and your point
| stands.
|
| Additionally, the AI comment is ironic as well. It's like we're
| finally writing good documentation for the sake of agents, in a
| way that we should have been writing all along for other
| sentient consumers. It's funny to see documentation now as
| basically the horse instead of the cart.
| globular-toast wrote:
| I find this use of .gitignore far more common than .gitkeep. I
| did see one js tool creating them and did wonder what it was
| about.
| Joker_vD wrote:
| Why did Git decide to have no means to track fully empty
| directories? Like, I understand that e.g. doing "git rm *" inside
| a directory should probably delete this directory from the
| repository as well (although "git rm -r dir_to_delete" exists
| so...) but why not have a command to _explicitly_ force a
| directory to be tracked, whether it 's empty or not?
___________________________________________________________________
(page generated 2026-02-21 23:01 UTC)