A's Commit Messages Guide: Location, Action, Rationale

By Artyom Bologov Warm colors thumbnail.On the left, 'Artyom's Commit Messages Guide' is written in dark orange.In the center, template 'file(function): Unfuck—because yes.' is written in contrasting black.In the upper right corner, 'aartaka.me' address is written in the same dark orange.

I'm writing and searching lots of commit messages every day. It's easy to get lost in them. Yet I don't. The reason is that I write reasonable commit messages. (I'm saying it in such a blunt way because it's not necessarily my merit—I had great teachers on Nyxt team and Guix community!) And I figured I should explain the reasoning behind my style. Might be helpful to someone starting out or looking for better conventions.

The heuristic is simple:

Location
Localize the change to file/function/object etc.
Action
Explain what the change does.
Rationale
Clarify the reasons and context for it.

Yes, all in one message without description (if possible.) I'll go through these parts in the order. Hopefully explaining things clearly enough for you to embrace the system.

Location: Isolating the Change

Filesystem is a huge helper and marker for project structure. Java classpaths, C include paths, conventional Open Source project files (README, LICENSE/COPYING, CONTRIBUTING.) Locating the change via file structure is half of the problem for change explanation. That's why my commit structure is so elaborate on locations.

First, file paths. Include full paths to files so that it's clear where the change happened. If your project structure is (already) reflecting the code structure then listing the file/directory should be enough to show change scope. Some exceptions I use:

Second, exact change location. This one is elaborate! There are several syntactic markers for the location that I use: Parentheses, square brackets, angle brackets, and some regex syntax. Mostly in the order of increasing specificity:

Some examples:

// From cl-blc:
cl-blc.asd: // Plain file with extension.
reductions: // Obvious extension (.lisp in this case), not included
cl-blc(eval): // Function eval in cl-blc.lisp
(compile): // Omitting the obvious file name (if there is one?)—cl-blc.lisp

// From this website
ideas(How I write commit messages): // Section name as location
screen.css(code,kbd,sample): // CSS selector as location
markup(DETAILS #GEMTEXT): // C Macros with #ifdef-s

// From my SRFI-253
srfi-253.html(#spec--values-checked): // HTML ID as the unit
impl.{chicken,kawa}.scm: // Bracket replacement
impl.*.scm(%lambda-checked): // Wildcard for all the matching files

// From Nyxt
buffer(buffer,panel-buffer[style]): // Square bracket slots in comma-separated classes
mode((dis|en)able-modes): // Complex regex use

// Not giving examples for angle brackets, sorry
Examples of locations I use in my projects

The trend is somewhat traceable: simple projects often only need file/function locations. Complex projects (like SRFIs and Nyxt) need more concrete locations.

Action: Explaining the Change

Commit messages are the part most commit guidelines focus on. Writing in imperative mood, expressing the "why" etc. It's Writing 101 all over again. The vital thing I took (and ignored on this blog) from writing classes? Put important things first. With commits/changes/alterations/modifications... the most important thing is the action. Verb, in other words.

Some possible verbs/actions:

The pattern is simple—whatever happens to the code, ends up in commit message. The person reading the logs immediately gets the idea of what you did (and where, if you followed previous section.) Commits tell a story of what one did to the code/data/narrative.

The action-ability of my commit messages is the reason I dislike Conventional Commits and the kind. You don't need to say whether the thing is a feature or a bugfix, you just say what's there. Directly. Add and Optimize also reads better than feat and perf.

Oh, and, this goes without saying? You can only distill the commit to one verb if it does only one thing. Make changes atomic and self-contained. You'll have no trouble explaining what you did then.

Rationale: Contextualizing the Change

Put rationale (the "why") into commit message whenever possible. That's where my style breaks some of the conventions. The reason? No one reads commit descriptions! What was the last time you, dear reader, intentionally looked through commit descriptions? Especially so—using the un-ergonomic CLI git display? Ugh.

That's why I try to put rationale for the change into commit message. The reader (often the future Artyom himself!) will thank me later.

Luckily, verb/action-orientation is good for rationale. All the "Fix" messages almost automatically hint at what was broken. All the "Optimize" messages hint at what was slow/over-allocating. And the "Remove" messages (my favorite!) often end up with "Remove X—unused!" or "Remove Y—useless!" A relief.

My favorite composition of the three things I suggested is "Add" message.

Formalities: Capitalization, Punctuation, etc.

The main structure (Where, What, Why?) out of the way. Time to get to formalities:

Real World Inspiration: Guix!

I am not trying to say that Guix uses my convention! It's rather the reverse: my style is heavily influenced by Guix. And stripped down for my small scale projects.

So what Guix does is

Here's a sample commit:

gnu: kvantum: Update to 1.1.2.

* gnu/packages/qt.scm (kvantum): Update to 1.1.2.
    [source]: Switch to git-fetch.
    [arguments]: Set #:qtbase to qtbase.
    <#:phases>: Adjust patch-style-dir phase.
    [inputs]: Remove libx11, libxext, qtbase-5, qtsvg-5, and qtx11extras; add qtsvg.
    [native-inputs]: Remove qttools-5; add qttools.
One of the (more involved) Guix commit messages

It's big and has lots of syntax, but it also gives you a lot of information about what's changed! That's what I strive for in my commits—maximum meaning per minimum space. Preferably fitting things into one commit message without description (unlike Guix):

packages/qt(kvantum): Update to 1.1.2 & switch to git-fetch.
Guix commit refactored in my style

Summary: Write Commits!

file(function): Unfuck—was preventing further refactoring.
Reference for my commit style

Hopefully y'all learned something from this posts and I can see more informative commits around! Thanks for your care 🖤

Leave feedback! (via email)