'Ledger Accounting', 2017. | PxHere (edited by the Author)
Every Commit A Sentence: Git Commit Messages for Bloggers
If you look at the changelog for my site, Brennan.day, you'll notice two things. First, I am working on this project way too much, and second, my attempt at good git commit message hygiene.
There is a problem, though. Across over a thousand commits, 448 were prefixed feat: and 417 were fix:. Nearly the same count, for wildly different kinds of work. A new 2,000-word essay? feat:. A corrected typo? fix:. An automated comment sync running at 3am? feat:. A CI pipeline toggle? feat:. Everything is flattened into two buckets.
I have been following what you might loosely call "best practices" for commit messages, but using conventions designed for software library authors is, at the end of the day, a writing practice of its own. I was losing meaningful signal in the process.
If you're a writer (or maybe you just call yourself a blogger) first and a developer second, using git as a way to maintain your writing more than anything else, then this blog post is for you.
A Brief History of the Typed Commit
You might be someone who doesn't even know what exactly to put for a commit message when you're pushing code to your repository. Or maybe you're more in the XKCD camp of ebbing and flowing between trying to be informative and just typing whatever-the-hell.
The modern convention for structured commit messages has lore. It starts with Tim Pope in April 2008, writing a now-famous post as a contributor to the Rails project.
His focus wasn't on type prefixes at all; rather, it was structural:
- keep the subject line under fifty characters
- wrap the body at seventy-two characters
- write in the imperative mood ("Fix bug", not "Fixed bug").
Pope established the grammar of a well-formed commit. Git itself, he argued, was designed with this structure in mind: git log --oneline, git shortlog, git rebase—all of these commands assume a subject line that can stand alone as a complete thought.
The type-prefix convention came later. Igor Minar and Vojta Jína introduced it while working on Angular in 2011. Their original AngularJS contributing guidelines specified a format of <type>(<scope>): <subject> with a fixed vocabulary: feat, fix, docs, style, refactor, perf, test, chore.
The stated goals were explicit: allow automated changelog generation, allow ignoring unimportant commits when using git bisect, and provide better information when browsing history.
That convention was eventually generalized into the Conventional Commits specification, a proper versioned standard that formalized the format. The specification's most important design decision was to tie feat: and fix: to Semantic Versioning: a feat: commit signals a minor version bump (e.g., version 0.0.1), a fix: signals a patch (e.g., version 0.1.0).
Tools like semantic-release can then read your commit history and automatically determine the next version of your npm package, without a human needing to make the call.
The Mismatch
This system I'm describing above was designed for programming, for people publishing npm packages and software library authors.
But I'm writing a blog. I don't have a public API, nor a semver. When I publish a new essay, it isn't like my site is deploying a MINOR release. When I correct a typo, it is not a PATCH. The vocabulary doesn't align.
The original Angular type list has no way to distinguish the writing of "new blog post published" from "automated comment sync." Both would reach for feat:. Likewise, there isn't any way to distinguish "corrected a sentence in an old post" from "fixed a null pointer in the comment fetcher" as both would use fix:.
The more I look at the history of my website, the more I saw the changelog becoming illegible because the categories weren't useful. As I've mentioned before, being an IndieWeb creator means you wear a lot of hats. There are writing-heavy days, there are design-heavy days, and there are coding-heavy days.
Under the feat:/fix: monoculture, that information had disappeared entirely.
My Redesign (Brennan's Type Prefix Conventions for Writers)
Since this is my site and I'm the sole maintainer, I get to change the rules. After auditing my commit history to understand the real distribution of work, I arrived at the following eleven types:
post:A new essay, blog post, or other published written content. When there's apost:in the log, something was written.edit:Corrections, revisions, or expansions to existing written content. Replacesfix:for prose, since a typo is not a bug.update:Routine data or content refreshes like comment syncs, stats refreshes, now page tweaks, adding friends to the blogroll. This absorbs the automated churn that was previously usingfeat:.design:Visual, layout, and CSS changes. Replacesstyle:.build:Site infrastructure, config, tooling, and dependency changes. Replaceschore:.fix:Actual code bugs only. Far more narrow than previously used.ci:Continuous Integration (CI) and Continuous Delivery/Deployment (CD) work.a11y:Accessibility improvements like ARIA labels, contrast fixes, alt text, and semantic HTML.perf:Performance improvements: build speed, asset sizes, caching. I've written a few blog posts about this kind of work.refactor:Code restructuring without behaviour change. Another topic I've written about.revert:Undoing a previous commit.
As you can see, this is opinionated. Maybe even a little controversial. Having content-focused prefixes reshapes the blog to be, well, a blog instead of a software project. There is the much more readable separation of edit: for typos, and fix: for bugs.
I've retired feat: entirely (no pun intended), and now a new CSS animation would go in design: and a new Eleventy shortcode would go in build:.
The Changelog as Parallel Text
There's something unexpectedly pleasing about thinking of the commit history as a parallel text to the blog itself. Both my posts themselves and the git commits are records of the same creative practice at different zoom levels.
When I look back at an active week in my changelog (which is essentially every week), I want to be able to read it as a diary in itself: a few post: entries, some edit: corrections as I noticed things after publishing, an update: or two from automated comment syncs, maybe a design: or a11y: if I'd been tinkering. That's a much more accurate account of what happened. A week that reads as twelve feat: commits and fifteen fix: commits is not.
Writing commits with more care is a small act of care toward future-me and others looking to build upon my work, and really anyone curious enough to browse the source on GitLab. Every commit is a sentence. It should say what it means.
The full convention, with a complete table and examples, is documented in my CONTRIBUTING.md doc and on the colophon page of the site.
Comments
To comment, please sign in with your website:
How it works: Your website needs to support IndieAuth. GitHub profiles work out of the box. You can also use IndieAuth.com to authenticate via GitLab, Codeberg, email, or PGP. Setup instructions.
Signed in as:
No comments yet. Be the first to share your thoughts!