Skip to main content

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 a post: in the log, something was written.
  • edit: Corrections, revisions, or expansions to existing written content. Replaces fix: 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 using feat:.
  • design: Visual, layout, and CSS changes. Replaces style:.
  • build: Site infrastructure, config, tooling, and dependency changes. Replaces chore:.
  • 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.

Howdy! As a professional software developer and avid reader of your blog, I'd like to weigh in. Conventional commits are starting to get push back amongst developers, with Github posting a lovely blog post on writing quality commits and just earlier this month a nice blog article arguing specifically against conventional commits and especially the idea of automatically generating changelogs from git logs.

While we're talking about questioning long-held development beliefs/flows, I've also been only somewhat jokingly passing around this article at work. I'm a big fan of questioning long-held beliefs/assumptions. Experimenting and finding what works best for you is the way to go!


Webmentions

No webmentions yet. Be the first to send one!


Related Posts

↑ TOP