It is generally seen as best practice for a project to keep a changelog as this gives people who interact with the project a easy why to see what has changed between releases. The problem is there is no real one size fits all solution to how changelogs should be maintained, some projects may have a text file that developers manually edit whenever something is changed with the project while others may parse this out of of their version control tool and auto generate one. In this blog post I will talk about a way to do the latter using git-notes.
Git-notes is a powerful tool that allows for adding additional information to a
commit that doesn’t show up in commit message itself. What is even better is
that git-notes doesn’t modify the object it is attached to, instead they are
stored as their own reference in Git and a repository can have as many note
references as is needed without causing any conflicts. By default when
git note add <commit hash> is called it creates a new object under refs/notes/commits
that points to the given commit hash, or if no commit hash is given then it
points to the most recent commit. One big downside to how git-notes works is
that it can be pretty hard to tell if a project is making use of them as by
default notes don’t show up in a standard
git log output and when inspecting a
commit that has one or more notes attached to it the only clue is that you will
see a unindented line saying
Notes (<refname>): followed by the notes. At
first I wrote off the idea of using git-notes due to this behavior, but after
seeing that Google built a whole code review tool for Git that
is backed by git-notes it started to get me thinking what else they can be used
for. Enter the project changelog.
Knowing that by default git-notes stores everything in refs/notes/commits,
this implies that git-notes could support many references, and in fact it does
looking at the documentation for git-notes there is a
--ref argument that
overrides the default note location. This means that changelog entries can be
stored in refs/notes/changelog and still be separate from the commit message
and leave the default note namespace clear for other things. To record a new
changelog entry the user can use
git notes --ref=changelog add <commit hash>.
While the syntax is not perfect due to how much typing this requires and leaves
space for user error due to typos, this short coming can be addressed by
creating a new alias in Git for it.
git config --global alias.changelog 'notes --ref=changelog add'
This creates a new
changelog alias in Git stored in the global git
configuration file $HOME/.gitconfig which now allows for changelog entries to
be recorded with
git changelog <commit hash>.
Adding a changelog entry
Thanks to the
changelog alias that was created earlier, adding a new changelog
entry is as easy as calling
git changelog which will then open a text editor
with a pretty familiar sight.
Syncing changelog entries between remotes and systems
Another short coming of the way git-notes works is that by default they are not
included when a
fetch operation is ran. This can be
addressed by editing the projects .git/config file and add the following lines
fetch = +refs/notes/changelog:refs/notes/changelog push = +refs/notes/changelog:refs/notes/changelog
This way when a
fetch operation is done in Git the
changelog notes will also be included.
Writing out the CHANGELOG file
The final thing needed to pull this all together is a way to take this information and write it out to in an easy way to read. Because the notes themselves are just objects in Git that means we can easily interact with them like we would anything else. It’s also important that the notes show up with the correct release of the project, project releases of course being tracked using git-tag also makes this easy. The way I approched this is with a simple bash script that walks the git commit history pulling the tags and changelog notes and writes out a markdown file following the format documented by Keep a Changelog.
This will produce output that looks something like the following:
There we have it, our changelog stored in a distributed and easy to generate way!