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.
Setup Link to heading
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 Link to heading
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 Link to heading
Another short coming of the way git-notes works is that by default they are not
included when a push
, pull
, or fetch
operation is ran. This can be
addressed by editing the projects .git/config file and add the following lines
to the remote
section(s):
fetch = +refs/notes/changelog:refs/notes/changelog
push = +refs/notes/changelog:refs/notes/changelog
This way when a push
, pull
, or fetch
operation is done in Git the
changelog notes will also be included.
Writing out the CHANGELOG file Link to heading
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!