This article details changelogs and follows my earlier article “Modern software versioning” where I explain Semantic Versioning.
Aside from a version number, most software is distributed with a changelog. A changelog will tell you — log — what has changed with a new release of software. These changes are tied back to version numbers. Historically programmers describe changes in two places,
user-facing changelogs.
commit messages which are only viewable by developers
Despite being conceptually different, for efficiency most simply concentrate change documentation into commit messages killing two birds with one stone. But, we can do better than that. In this article we will kill three birds with one stone.
Recap on Semantic Version
Recall that Semantic Versioning is a standard for version numbers that allows you to compare any two versions and state affirmatively the type of change between them as one of either
“major” (also known as a “breaking change”): incompatibility with potentially more features and bug fixes
“minor”: more features and potentially bug fixes
“patch”: just bug fixes
Automating version management
Let’s assume we have source-control management (SCM) like git. Going back to our,
semvarAfter = bump(semvarBefore, change)
Where change
is of type ENUM[MAJOR,MINOR,PATCH]
.
In order to provide full automation of releases, we have to be able to derive both arguments to bump
:
semvarBefore
This is easy to determine: version numbers are typically stored in git tags. You can determine the last version by applying the coalition in the Semantic Versioning spec (the standardized sorting order).
change
This is the problem: we need a deterministic method to get a value for
change
by deciding which variant of theENUM[MAJOR,MINOR,PATCH]
represents the changes in code want to publish.
One solution that enables automation is to simply tag each commit with a token that represents the change type.
Enter Conventional Commits
Conventional Commit is one of many standards for tokens, and token placement. The tokens used by Conventional Commits, are
“
BREAKING CHANGE:
” For a major change.“
feat:
” For a minor change, abbreviation of “feature”“
fix:
” For a patch level change
Where “type” is our token above.
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
These map directly onto our ENUM[MAJOR,MINOR,PATCH]
. With each commit message standardized to use the tokens above, we need only
Scan all commits from the last published version, to the code we want to publish (typically
HEAD
). And,Track the most significant token identified in the scan
Pseudo-code
change = scanCommits(gitTag, HEAD)
We can return to our original method of calculating the bumped version,
semvarAfter = bump( semvarBefore, change )
And substitute change
,
semvarAfter = bump( semvarBefore, scanCommits(gitTag, HEAD) )
This is how Semantic Release paired with Conventional Commits enables automated release management. If you’re already using Semantic Versioning, adopting Conventional Commits is the one stone that kills,
Versioning
User-facing changelogs
Git Commit message standardization
Conventional Commit Scoping
Conventional commits support something called scoping. Which looks like the following (the scope below is lang
):
feat(lang): add Polish language
Scoping allows you to develop tooling that reacts differently to changes of different feature sets and projects in your repository. It’s left for the implementer to decide how to use it.
Revisiting changelogs and commit messages
It’s worth revisiting the distinction between user facing changelogs and commit messages. Conventional Commit does not address this distinction. But in practice, most of the changelog-generating tools only make available to the user-changelogs the first line of the commit message. Simply keep that title line (first line of the commit message) succinct and user-focused and place the details restricted to developers in the body below.