Automatically generate a Git changelog

How to automatically create a changelog from git commit messages

In order to get an overview of the history of a project under Git administration, it makes sense to create a changelog. A simple solution lists all commit messages sorted by tags.

There is little to argue about the purpose of a changelog. Although a good changelog should contain more than a simple chronological listing of commit messages, if it were easy to create such a list, it would be useful.

The git log command

The easiest way is to run the git log command in a Git repository. This lists all commit messages with some additional information in reverse chronological order, i.e. from the newest to the oldest.

The output can be adjusted using additional parameters. For example, the output format can be adapted or the messages that relate to the specified path can be selected by specifying a path (see this documentation with further options and examples).

However, more is needed to create a more meaningful changelog. Perhaps you would like to structure the changelog according to versions (tags), possibly with a further breakdown of which new features the version contains and which errors have been fixed.

To achieve this, you usually need to install additional software and you are forced to adhere to certain rules when creating commit messages (see e.g. this guidance with further links to various programs for creating a changelog).

My requirements at this point are less demanding:

  • The command to create the changelog must be easy to remember. No parameters for any variants.
  • No post-processing of the changelog required.
  • No additional software required, just get by with what is already installed.
  • The changelog should summarize the changes for tags/releases.
  • The commit messages don't have to be of any particular form.

The following bash script was created for these reasons. It works in an appropriate shell and with a reasonably up-to-date Git version.

Script to create a changelog

The following script can be saved somewhere in the path under the name and called without parameters. The output can optionally be redirected to a file, e.g. to the file to be stored in a Git repository.


## Files/Paths which matches pattern are excluded from report

## Format for pretty printing the log entry (markdown list entry)
FORMAT="- %s [%an] %h"

## First check if we are running in a git repo or exit
git rev-parse > /dev/null 2>&1
[[ $? != 0 ]] && echo "This is not a git repo!" && exit 1

## Get the very first commit to report commits before first tag
FIRSTCOMMIT=$(git rev-list --max-parents=0 HEAD)

## Get the list of all TAGS prepended by first commit
declare -a TAGS=($FIRSTCOMMIT $(git tag) )

## Output header in markdown
echo -e "# Changelog\n"

## Recurse list of tags in reverse order
for (( i=$(( ${#TAGS[@]} - 1)); i>0; i-- ));do
#  echo "Commits from ${TAGS[$i-1]} to ${TAGS[$i]}"

## Get date of this tag
  DATE=$(git tag --list ${TAGS[$i]} --format '%(creatordate:short)')

## Output subheader in markdown
  echo -e "## ${TAGS[$i]} ($DATE)\n"

## Get list of commit messages between previous and this tag
  git --no-pager log --pretty="format:$FORMAT" ${TAGS[$i-1]}..${TAGS[$i]} -- . ":(exclude,glob,icase)$IGNORE"

  echo -e "\n"

exit 0

As you can see, you can get by with less than 15 lines of code. Only commands are used that are already available due to the installed programs Bash and Git.

In order to explain:

  • The IGNORE variable is used to define a file pattern that is ignored when the commit messages are output. The point is so that e.g. not to include the commit messages for the changelog itself in the changelog. In this case, the file name must begin with "CHANGELOG".
  • The FORMAT variable saves the format in which the commit messages are output with git log. An explanation of the placeholders can be found in the Git documentation. Since the output is to be in the Markdown format as a list, every message is prefixed with - .
  • The output of the git rev-parse command is used to check whether you are in a Git repository at all. If not, the script is canceled.
  • In order to also record the commit messages that are before the first tag/release, the SHA value of the first commit is determined and stored in the FIRSTCOMMIT variable.
  • Then all tags/releases are determined and saved together with the SHA value of the first commit in a bash array (TAGS).
  • Before the list of commit messages is retrieved, a title in Markdown format is output.
  • The actual commit messages are fetched in a loop, with the TAGS list being traversed backwards in reverse order.
  • In the loop, the date of the current tag/release is first determined and stored in the DATE variable.
  • A subtitle is then output in Markdown format that contains the tag/release and its date.
  • The commit messages are then output in the defined format and without a pager. With the construction ${TAGS[$i-1]}..${TAGS[$i]} only the commits are recorded that took place between the previous and the current tag/release. The output is made for all files starting with the current directory and excludes files that begin with "CHANGELOG" (case is ignored).

The best way to create a complete changelog is to be in the main directory of the relevant Git repository and redirect the output to the file:

$ >

The result could then look like the following example: of a Git repository