The letter A styled as Alchemists logo. lchemists
Published February 1, 2024 Updated February 12, 2024
Cover
Git Trailers

Git Trailers are a powerful source of metadata for your Git commits. Unfortunately, they are severely underutilized. So I’d like to show you why they are important and how you can make the most of them to improve your workflow. 🚀

Quick Start

By default, you can leverage the --trailer option as first introduced in Git 2.32.0 when creating commit messages. Example:

git commit --message "Fixed log format" --trailer "Milestone: patch"
git show

When you run the above, you’ll end up with the following output (depending on how you like to format git show):

717dcffd2eed G Brooke Kuhlmann Fixed log format (HEAD -> demo) 1 second ago.

Milestone: patch

 lib/demo/container.rb | 8 ++++++++
 1 file changed, 8 insertions(+)

Notice the Milestone trailer is added at the bottom of the commit message. This where all trailers (metadata) exists which provides a rich source of information for post-processing tools like Milestoner (more on this shortly). Even better, you can add multiple trailers at once. Example:

git commit --message "Fixed log format" \
           --trailer "Issue: abc" \
           --trailer "Milestone: patch" \
           --trailer "Tracker: tana"
git show

# 181c9172ae4e G Brooke Kuhlmann Fixed log format (HEAD -> demo) 1 second ago.
#
# Issue: abc
# Milestone: patch
# Tracker: tana
#
#  lib/demo/container.rb | 8 ++++++++
#  1 file changed, 8 insertions(+)

Once again, notice we have the following trailer information at the bottom of the commit message:

Issue: abc
Milestone: patch
Tracker: tana

In this case, we know this commit is a patch for the abc issue as found within the Tana issue tracker. That’s a lot of useful information we can use later without degrading the readability of our commit messages. 🚀

Context

Now that you have a taste of what Git Trailers are, let’s talk about why trailers are important. The most obvious is avoiding these kinds of commit messages which are unnecessarily unfriendly to read due to not being proper human friendly sentences:

[FIX]: Corrected log output.
fix: Corrected log output
[Billy Bob]: Fixed log output
[ACME abc]: Fixed log output
#abc: Fixed log output
Fixed log output [ACME: abc]
Fixed log output [Project: ACME, Issue: abc]

Notice the examples above all deal with the same kind of commit message which could me more easily be distilled and reformatted to the following as previously discussed in my Git Commit Anatomy article:

Fixed log output by sorting all keys

Necessary to ensure each log message is consistent for post-processing.

Issue: abc

Now we have a nicely formatted Git commit message with a project subject (i.e. what), body (i.e. why), and trailers (i.e. metadata) which yields the following benefits:

  • 👀 Readability: Each commit reads like a proper English sentence without the need to filter additional noise.

  • 📖 Code Reviews: As more of these nicely formatted commit messages are made, we have commit messages that tell a story of how the implementation was architected. Each commit becomes a page in a book. This vastly improves the speed of code review feedback so you can get more code rebased onto the main branch.

  • 📰 Release Notes: Due to each commit message being human friendly, you can disseminate information to multiple parties at once: Engineering, Marketing, Project Managers, Customers, etc. All of this is possible by automatically generating release notes based on your Git Trailer metadata. Tools, like Milestoner, automate this process for you.

  • 🚀 Deploys: Again, using a tool like Milestoner, you can automate the versioning of your next deploy based on Git Trailer metadata. Even better, the next version can be automatically calculated for you based on the Milestone trailer.

Kinds

There is no specification on the kinds of trailers you can or can’t use because, by default, they only need to be key/value pairs at the bottom of your commit message. That said, I’ve been working to formalize this more to where I generally use all or some of the following:

Co-authored-by: River Tam <river@firefly.com>
Format: asciidoc
Issue: 123
Milestone: patch
Reviewer: github
Signed-of-by: Malcolm Reynolds <mal@firefly.com>
Tracker: tana

The above can be broken down as follows:

  • Co-authored-by: Optional. Any/all collaborators that you worked with. This is a great way to give credit and celebrate your fellow team members and/or open source contributors.

  • Format: Optional. The format (or MIME Type) you wrote your commit message in. This allows version release notes to be automatically rendered in multiple formats (i.e. HTML, RSS, JSON, ASCII Doc, Markdown, plain text, etc.) for beautiful and highly interactive release notes.

  • Issue: Optional. The ID of the issue you are working on. When automating version release notes and/or deployments, this information is highly valuable to fellow team members and/or stakeholders. In some cases, you might not have an issue because you took the initiative and improved/fixed the implementation anyway and that’s perfectly fine too. Kudos!

  • Milestone: Optional. The milestone you are targeting with your change. When supplied, this enables automatic version bumping by calculating the highest change (i.e. major, minor, or patch) and updating the version accordingly.

  • Reviewer: Optional. The code review system used. When combined with the tracker and issue keys, the associated code review link can be automatically generated for reporting purposes.

  • Signed-off-by: Optional. Denotes who signed off on the work. Works well with the git commit --signoff or git commit --no-signoff commands, for example.

  • Tracker: Optional. The issue tracking system used. When combined with the issue key, the associated issue tracker link can be automatically generated for version reporting purposes.

Formats

The Git Interpret Trailers specification doesn’t have a hard limit what you can use for a trailer but the general rule of thumb is to format your trailers as follows:

  • Must always be placed at the bottom of your commit message.

  • Must have a space between the end of your commit message and start of trailers.

  • Each trailer needs to be a key/value pair delimited by a colon. Example: Key: value.

  • Each key should have the first letter capitalized.

  • Each key can be delimited by dashes if necessary. Example: Co-authored-by.

Given the above, you could create the following commit with a subject, body, and trailer information:

touch demo.txt
git add demo.txt
git commit --trailer "Issue: abc" \
           --trailer "Milestone: patch" \
           --trailer "Tracker: tana"

Now you can use Git Interpret Trailers to inspect the trailers of any commit (be it saved or unsaved):

# Saved Commit (i.e. the last commit)
git log --format=%B -1 | git interpret-trailers --parse

# Issue: abc
# Milestone: patch
# Tracker: tana

# Unsaved Commit (i.e. the last commit made via your editor)
git interpret-trailers --only-trailers .git/COMMIT_EDITMSG

# Issue: abc
# Milestone: patch
# Tracker: tana

Keep in mind that trailers always go at the end of a commit message, after the body per Git specification, and should never be used in the subject or body. Doing so ensures a commit message remains readable by fellow engineers and keeps the metadata in a format that is useful for post-processing by a program. Git Lint can aid in this endeavor by ensuring each commit message is consistently formatted and prevent garbage in, garbage out situations.

Hooks

We’ve discussed what Git Trailers are and why they are important so now I want to teach you how to make use of them in an automated fashion. This is where Git Branch Descriptions and Git Hooks come into play.

Git Branch Descriptions, if not familiar, are a nice way to provide additional information about the current branch you are working with and you can add descriptions as easily as running the following from the command line:

git switch --create demo
git branch --edit-description

Assuming you saved the following message for your branch:

A demonstration branch

Issue: abc
Tracker: tana

You’d then be able to view this information via the following command:

git config --get branch.demo.description

# A demonstration branch
#
# Issue: abc
# Tracker: tana

💡 Ensure you always provide a subject for your branch description or git interpret-trailers won’t be able to parse your trailers. In other words, you can’t have only trailers in your branch description you must have a subject, space, and trailers.

Next, you can automatically apply the above to each commit message by adding a prepare-commit-msg Git Hook which triggers after preparing the default commit message but before your editor is launched. As a quick start, you can copy and edit the sample Git Hook provided for you when creating a new repository (assuming you are currently in the root of your repository). Example:

cp .git/hooks/prepare-commit-msg.sample .git/hooks/prepare-commit-msg
$EDITOR .git/hooks/prepare-commit-msg

💡 I use global Git Hooks so I don’t have to maintain duplicate copies of my Git Hooks across multiple repositories. That said, using your current repository for experimentation is a quick way to get started.

Now that you are editing your .git/hooks/prepare-commit-msg hook, you can copy and past the following implementation into it. Each line is documented so you can quickly grok the implementation:

#! /usr/bin/env bash

# Defines Git prepare commit message functionality.

# Enable safe defaults.
set -o nounset
set -o errexit
set -o pipefail
IFS=$'\n\t'

# The first argument, provided by Git, is the path to your commit message.
local commit_message_path="$1"

# The second argument, provided by Git, is the kind of commit being made.
local kind="$2"

# Set safe defaults.
local branch=""
local trailers=""

# We start by obtaining the name of the current branch.
branch="$(git branch --show-current | tr -d '\n')"

# Next, we let Git parse any trailers from the branch description as a new line delimited string.
# Use of (|| :) is a no operation to prevent premature Git hook abortion should the command fail.
trailers="$(git config --get "branch.$branch.description" | git interpret-trailers --parse || :)"

# Finally, we add trailers to the commit message based on kind of commit being created.
case "$kind" in
  # When you use `git commit --message`.
  message)
    printf "\n\n%s" "$trailers" >> "$commit_message_path";;
  # When you use `git commit`.
  template)
    if [[ -n "$trailers" ]]; then
      # String substitution is used to escape new line characters for `awk` to parse properly.
      awk -v trailers="${trailers//$'\n'/\n}\n" '
        BEGIN { inserted = 0 }
        /^#/ && !inserted { print trailers; inserted = 1 }
        { print }
      ' "$commit_message_path" > "$commit_message_path.tmp" \
      && mv "$commit_message_path.tmp" "$commit_message_path"
    fi;;
esac

You might be curious about the case statement. To elaborate, we can potentially have the following kinds of commits:

  • message

  • template

  • merge

  • squash

  • commit

We only care about the first two. The template (kind) uses awk because I want to be conscious of any custom Git commit templates (i.e. ~/.config/git/template, see my Git Configuration article for more info) so need the trailers to be added before the commit message comments begin. In other words, the end of the commit message.

When you combine the above together, you get a nice workflow. Example:

git switch --create demo

# Add description as shown above.
git branch --edit-description

touch one.txt
git add one.txt

# Add subject and body (notice your trailers are auto-injected!)
git commit

touch two.txt
git add two.txt
git commit

Now when you run git log on the above (depending on how you like to format your logs), you should something similar to the following output:

Git Log Screenshot

Tools

Assuming you’ve enjoyed learning about Git Trailers and would like to put them to use in your own projects, I highly recommend the following tooling to aid in this endeavor (you can pilfer from my Dotfiles project as well):

  • Git Lint: Lints your Git commits so they are of high and consistent quality.

  • Milestoner: Automates the generation of release notes, versioning, and deployment of your project.

While the above tools are architected by me, and apologies for the self promotion, I’ve been using these tools on all of my projects for years to great effect. If you’ll indulge me a bit longer — and using Milestoner — you can build nice release notes for the above. Example (i.e. milestoner build --format web):

Release Notes

All of this is made possible due to having tooling that can read and parse your Git Trailers so your commit messages remain enjoyable to read for faster collaboration with your colleagues. Not bad for an automated workflow! 🚀

Conclusion

I hope you’ve enjoyed learning about Git Trailers, what they are, why they are important, and how to leverage them so you can immediately apply them to your own workflow. Writing high quality software doesn’t have to be tedious nor does collaboration have to be grueling. With a little ingenuity and tooling, you can make your engineering workflows a breeze. Enjoy!