[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)