Blog Product Tutorial: Automate releases and release notes with GitLab
November 1, 2023
9 min read

Tutorial: Automate releases and release notes with GitLab

With the GitLab Changelog API, you can automate the generation of release artifacts, release notes, and a comprehensive changelog detailing all user-centric software modifications.


When you develop software that users rely on, effective communication about changes with each release is essential. By keeping users informed about new features and any modifications or removals, you ensure they maximize the software's benefits and avoid encountering unpleasant surprises during upgrades.

Historically, creating release notes and maintaining a changelog has been a laborious task, requiring developers to monitor changes externally or release managers to sift through merge histories. With the GitLab Changelog API, you can use the rich history provided in our git repository to easily create release notes and maintain a changelog.

In this post, we'll delve into automating releases with GitLab, covering the generation of release artifacts, release notes, and a comprehensive changelog detailing all user-centric software modifications.

Releases in GitLab

First, let's explore how releases work in GitLab.

In GitLab, a release is a specific version of your code, identified by a git tag, that includes details about changes since the last release (and release notes) and any related artifacts built from that version of the code, such as Docker images, installation packages, and documentation.

You can create and track releases in GitLab using the UI by calling our Release API or by defining a special release job inside a CI pipeline. In this tutorial, we'll use the release job in a CI/CD pipeline, which allows us to extend the automation we're using in our pipelines for testing, code scanning, etc. to also perform automated releases.

To automate our releases, we first need to answer this question: Where are we going to get the information on changes made for our release notes and our changelog? The answer: Our git repository, which provides us with a rich history of development activity through commit messages and merge commit history. Let's see if we can leverage this rich history to automatically create our notes and changelogs.

Introducing commit trailers

Commit trailers are structured entries in your git commits, created by adding simple <HEADER>:<BODY> format messages to the end of your commit. The git CLI tool can then parse and extract these for use in other systems. An example you might have already used is git commit --sign-off to sign off on a commit. This is implemented by adding a Signed-off-by: <Your Name> trailer to the commit. We can add any arbitrary structured data here, which makes it a great place to store information that could be useful for our changelog.

In fact, if we use a Changelog: <added/changed/removed> trailer in our commits, the GitLab Changelog API will parse these and use them to create a changelog for us automatically!

Let's see this in action by making some changes to a real codebase and performing a release, and generating release notes and changelog entries.

Our example project

For the purposes of this blog, I'm using a simple Python web app repository. Let's pretend Version 1.0.0 of the application was just released and is the current version of the code. I've also created a 1.0.0 release in GitLab, which I did manually because we haven't created our automated release pipeline yet:

A screenshot of the GitLab UI showing a release for version 1.0.0

Making our changes

We're in rapid development mode, so we're going to be working on releasing Version 2.0.0 of our application today. As part of our 2.0.0 release, we're going to be adding a new feature to our app: A chatbot! And we're also going to be removing the quantum blockchain feature, because we only needed that for our first venture capital funding round. Also, we're going to be adding an automated release job to our CI/CD pipeline for our 2.0.0 release.

First, let's remove unneeded features. I've created a merge request that contains the necessary removals. Importantly, we need to ensure we have a commit message that includes the Changelog: removed trailer. There's a few ways to do this, such as including it directly in a commit, or performing an interactive rebase and adding it using the CLI. But I think the easiest way in our situation is to leave it until the end and then use the Edit commit message button in GitLab to add the trailer to the merge commit like so:

A screenshot the GitLab UI showing a merge request removing unused features

If you use this method, you can also change the merge commit title to something more succinct. I've changed the title of my merge commit to 'Remove Unused Features', as this is what will appear in the changelog entry.

Next, let's add some new functionality for the 2.0.0 release. Again, all we need to do is open another merge request that includes our new features and then edit the merge commit to include the Changelog: added trailer and edit the commit title to be more succinct:

A screenshot of the GitLab UI showing a merge request to add new functionality

Now we're pretty much ready to release 2.0.0. But we don't want to create our release manually this time. So before our release we're going to add some jobs to our .gitlab-ci.yml file that will perform the release for us automatically, and generate the respective release notes and changelog entries, when we tag our code with a new version like 2.0.0.

Note: If you want to enforce changelog trailers, consider using something like Danger to perform automated checks for MR conventions.

Building an automated release pipeline

For our pipeline to work, we need to create a project access token that will allow us to call GitLab's API to generate changelog entries. Create a project access token with the API scope, and then store the token as a CI/CD variable called CI_API_TOKEN. We'll reference this variable to authenticate to the API.

Next, we're going to add two new jobs to our gitlab-ci.yml file:

  stage: prepare
  image: alpine:latest
  - if: '$CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+$/'
    - apk add curl jq
    - 'curl -H "PRIVATE-TOKEN: $CI_API_TOKEN" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/repository/changelog?version=$CI_COMMIT_TAG" | jq -r .notes >'

  stage: release
    - job: prepare_job
      artifacts: true
  - if: '$CI_COMMIT_TAG =~ /^v?\d+\.\d+\.\d+$/'
    - echo "Creating release"
    name: 'Release $CI_COMMIT_TAG'
    tag_name: '$CI_COMMIT_TAG'
    ref: '$CI_COMMIT_SHA'
        - name: 'Container Image $CI_COMMIT_TAG'

In the above configuration, the prepare_job uses curl and jq to call the GitLab Changelog API endpoint and then passes this to our release_job to actually create the release. To break it down further:

  • We use the project access token created earlier to call the GitLab Changelog API, which performs the generation of the release notes and we store this as an artifact.
  • We're using the $CI_COMMIT_TAG variable as the version. For this to work, we need to be using semantic versioning for our tags (something like 2.0.0 for example), so you'll notice I've also restricted the release job using a rules section that checks for a semantic version tag.
    • Semantic versioning is required for the GitLab Changelog API to work. It uses this format to find the most recent release to compare to our current release.
  • We use the official release-cli image from GitLab. The release-cli is required to use the release keyword in a job.
  • We use the release keyword to create a release in GitLab. This is a special job keyword reserved for creating a release and populating the required fields.
  • We can pass a file as an argument to the description of the release. In our case, it's the file we generated in the prepare_job, which was passed to this job as an artifact.
  • We've also included our container image that is being built earlier in the pipeline as a release asset. You can attach any assets you'd like from your build process, such as binaries or documentation by providing a URL to wherever you've uploaded them earlier in the pipeline.

Performing an automated release

With this setup, all we need to do to perform a release is push a tag to our repository that follows our versioning scheme. You can simply push a tag using the CLI, this example uses GitLab's UI to create a tag on the main branch. Create a tag by selecting Code -> Tags -> New Tag on the sidebar: A screenshot of the GitLab UI illustrating how to create a tag

On creation, our pipelines will start to execute. The GitLab Changelog API will automatically generate release notes for us as markdown, which contains all the changes between this release and the previous release. Here's the resulting markdown generated in our example:

## 2.0.0 (2023-08-25)

### added (1 change)

- [Add ChatBot](gl-demo-ultimate-bridley/super-devsecops-incorporated/simply-notes-release-demo@0c3601a45af617c5481322bfce4d71db1f911b02) ([merge request](gl-demo-ultimate-bridley/super-devsecops-incorporated/simply-notes-release-demo!4))

### removed (1 change)

- [Remove Unused Features](gl-demo-ultimate-bridley/super-devsecops-incorporated/simply-notes-release-demo@463d453c5ae0f4fc611ea969e5442e3298bf0d8a) ([merge request](gl-demo-ultimate-bridley/super-devsecops-incorporated/simply-notes-release-demo!3))

As you can see, GitLab has extracted the entries for our release notes automatically using our git commit trailers. In addition, it's helpfully provided links back to the merge request so readers can see more details and discussion around the changes.

And now, our final release: The GitLab release UI showing a release for version 2.0.0

Creating the changelog

Next, we want to update our changelog (which is basically a collated history of all your release notes). You can use a POST request to the changelog API endpoint we used earlier to do this.

You can do this as part of your release pipeline if you like, for example by adding this to the script section of your prepare job:

'curl -H "PRIVATE-TOKEN: $CI_API_TOKEN" -X POST "$CI_API_V4_URL/projects/$CI_PROJECT_ID/repository/changelog?version=$CI_COMMIT_TAG"

Note that this will actually modify the repository. It will create a commit to add the latest notes to a file: A screenshot of the repository which shows a commit updating the changelog file

And we are done! By utilizing the rich history provided by git with some handy commit trailers, we can leverage GitLab's powerful API and CI/CD pipelines to automate our release process and generate release notes for us.

We want to hear from you

Enjoyed reading this blog post or have questions or feedback? Share your thoughts by creating a new topic in the GitLab community forum. Share your feedback

Ready to get started?

See what your team could do with a unified DevSecOps Platform.

Get free trial

New to GitLab and not sure where to start?

Get started guide

Learn about what GitLab can do for your team

Talk to an expert