Collocating My .gitignore Configuration Files With The Omitted Files
I believe that maintainable code is code that's easy to find and easy to delete. And, a big part of what make's code easy to find and easy to delete is collocation. Which is why I've been experimenting with collocating my CFML, JavaScript, and CSS files; as well as my collocating my ColdFusion partials with my CFML views. This morning, as I was setting up a new project, it occurred to me that my .gitignore
files might present another opportunity for collocation.
In a git repository, the .gitignore
file tells git which files to exclude from source control. For example, a common entry in this file is .DS_Store
, which is a MacOS system file that gets automatically generated by the Finder app. And, if you need to compile files, it's common to add temporary directories like dist
and .parcel-cache
to the .gitignore
.
Historically, I put one .gitignore
file in my repository root; and then add deep file paths to it as needed. But, a git repository can contain any number of .gitignore
files. And it occurs to me now that my project files might be easier to maintain if I use "local" .gitignore
file with "local" file entries.
To illustrate what I mean, when I'm not using containers for deployment (with injected runtime secrets) my projects often have a /config
directory that contains two files:
config.json
config.template.json
The config.json
file contains secrets and should never be committed to source control. So, I create a sibling file, config.template.json
, which codifies the shape of the configuration, but which omits any secret values.
To make sure that config.json
never gets committed, I would normally go into my root .gitignore
and add the entry:
config/config.json
But now the logic for this /config
directory is spread across two different locations. Which feels unnecessarily complex, harder to maintain, and non-obvious while looking at the file system.
So, what I'm going to start doing—as much as it makes sense—is create small, localized .gitignore
files that are collocated with the files that they manage. In this case, it means putting a .gitignore
file directly in my /config
directory that simply contains the one file entry:
config.json
My /config
directory would then end up with this structure:
/config/.gitignore
← one entry forconfig.json
/config/config.json
/config/config.template.json
Now, when I'm looking at the /config
directory, all of the code and the source control logic is collocated in one place. And, it becomes much more obvious that source control has caveats relating to this folder due to the existence of the .gitignore
file.
Another potential "win" here is that the .gitignore
file can contain comments. Which provides both an opportunity to omit files from source control and explain why they are being omitted.
As an example of this, one Pro Tip that I learned from Jamie Krug many years ago was to always have a /scribble
folder in my web root that gives me a place to experiment with code that won't be committed to the project. And, historically, I've managed this folder by adding a scribble
entry to the root .gitignore
:
wwwroot/scribble/
But, the problem with this is that if a new developer clones the given repository, there's no scribble
folder in their file system. Which means that every new developer has to be taught that they can create a scribble
folder and put experimental code in there without having to worry about accidentally committed code to the repository.
A local .gitignore
file, located withing the scribble
folder, would solve this problem.
wwwroot/scribble/.gitignore
This local .gitignore
will:
Commit the folder to the repository without committing any of the files. Which means each new developer won't have to create it.
Give me a place to document why the folder exists and how to use it.
For example, here's the .gitignore
that I just created this morning during this thought experiment:
# The scribble folder is a place to put random scripts that don't get committed to the
# source control. This is a good place to experiment with ideas and to keep small
# utilities that are helpful to you but which are not helpful to others. That said, it
# runs as part of the application context and has access to the Injector.
# Ignore all files and folders in this directory.
*
# ... except for the gitignore file itself.
!.gitignore
Now, if a developer clones this git repository, they will get an empty scribble
folder and some directions about why that folder exists. All in one place.
Over the years, one of my most common pain points in web development has been that files that "change together" don't always "live together". Which means that implementing one change means opening files both "here" and "over there". What I'm trying to do now is become more aware of the files that change together; and, see if I can move them closer to each other in the file system.
Want to use code from this post? Check out the license.
Reader Comments
Our main dev/architect has forbidden me from committing any .gitignore files! Ha.
Ha ha ha, wat?! I've never heard of such a thing - is there an explanation?
@Ben Nadel, Nope. Maybe he considers ignored files a personal selection.
I'm suddenly thinking of Fatal Attraction, "I'm not going to be ignored, Dan!"
I like your /scribble example, Ben. It's like
.gitkeep
except you are expecting the future files in that folder to NOT be committed to the repo, which.gitkeep
wouldn't help you with. Slick.@Jason,
I've definitely used the
.gitkeep
concept in the past, which I believe is just a usage pattern explored in "user land", not something officially part of git.For anyone reading this that might not be familiar with the
.gitkeep
technique, it's important to understand that git can't commit empty folders, it can only commit files. So, if your repo needs to have an empty folder in place, developers can put a.gitkeep
folder in there as a way for git to see it and track it (technically it's tracking the.gitkeep
file, not the folder, but the file lives in the folder). There's nothing special about the.gitkeep
file, it's just being used for its side-effects.I've used this in the past to keep "processing" folders in the project definition. For example, we would sometimes designate a "scratch" folder at work where intermediary files could be written during processing workflows (such a ZIP or Image generation). And, if the folder didn't exist, ColdFusion commands would throw an error that the path doesn't exist. So, to cut down on the developer complaints (or having to hard-code
directoryCreate()
initialization scripts), we would just throw a.gitkeep
in there.This was before I realized that you could have multiple
.gitignore
files. Now, the secondary.gitignore
files tend to be my first choice.@Ben Nadel,
Regarding .gitkeep, since I hadn't heard of it I googled it, some folks (okay, StackOverflow) think a README might be a better choice, with explanations if not obvious. I would probably agree with that, since .gitkeep isn't "official", though you could put text in it, certainly?
@Will,
Dang it, a README is actually a really good idea!! You could even combine the two in places where you do want to exclude files. Basically just add the README to exclusion:
That feels like the best of both worlds!
Yeah, I have definitely used README.md files in folders that are "there to be used" but not containing actual parts of the codebase. I think combining that with your /scribble approach might be a great solution in a number of different situations.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →