Automating Changelog Generation with GitHub Actions and Signed Commit

Managing changelogs manually can be tedious and error-prone, especially for a project with many contributors. In this post, I will share my experience with setting up automated changelog generation using GitHub Actions, commit it to github with signed commits for enhanced security.

Why Automated Changelog Generation?

  • Ensures consistency in changelog formatting
  • Reduces manual effort and human error
  • Maintains up-to-date documentation automatically

Prerequisites

These are the tools being used:

Initialize Cliff configuration

First, let’s install git-cliff CLI to do so. I will use the npm package for this example. However, you can visit Git-Cliff Installation for other package mangers.

npm install -g git-cliff@latest

Personally, I like the format cocogitto for its styling base on conventional commits.

git cliff --config cocogitto

This will generate a cliff.toml file in your repository root that contains info on how Git-Cliff processes your git commits:

# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration

[changelog]
# template for the changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
<!-- generated by git-cliff -->
"""
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
    { pattern = '<REPO>', replace = "https://github.com/my-organization/my-repo" }, # replace repository URL
]

[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
    { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"}, # replace issue numbers
]
# regex for parsing and grouping commits
commit_parsers = [
    { message = "^feat", group = "Features" },
    { message = "^fix", group = "Bug Fixes" },
    { message = "^doc", group = "Documentation" },
    { message = "^perf", group = "Performance" },
    { message = "^refactor", group = "Refactoring" },
    { message = "^style", group = "Style" },
    { message = "^revert", group = "Revert" },
    { message = "^test", group = "Tests" },
    { message = "^chore\\(version\\):", skip = true },
    { message = "^chore", group = "Miscellaneous Chores" },
    { body = ".*security", group = "Security" },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

Running Git-Cliff

Next, let’s do a dry run for the changelog generation. For example, generating changelog from version 0.9.2 to the latest commit.

git cliff v0.9.2..HEAD

The following output will be shown:

# Changelog

All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.

---
## [Unreleased](https://github.com/my-organization/my-repo/tree/HEAD)

### Bug Fixes

- handle invalid MIME format during image processing - ([f78c0d2](https://github.com/my-organization/my-repo/commit/f78c0d2de653ea46e13ea4c216b9ae951b339d50)) - Ryan Chang

### Lib

- bump webrick >= 1.8.2 ([#95](https://github.com/my-organization/my-repo/issues/95)) - ([9cc0e93](https://github.com/my-organization/my-repo/commit/9cc0e934c5cb64cee1ab3eedad2cd32e11c117de)) - Ryan Chang

---
## [1.0.0](https://github.com/my-organization/my-repo/compare/v0.9.2..v1.0.0) - 2024-08-18

### Refactoring

- use code generator only when activesupport >= 7.2 - ([95f9b41](https://github.com/my-organization/my-repo/commit/95f9b41de3107b685a1cd771061b209b79d21071)) - Ryan Chang

<!-- generated by git-cliff -->

To persist the changelog content, we need to add --ouput when running Git-Cliff command.

git cliff v1.0.0..HEAD --output CHANGELOG.md

Setting up the Workflow

Next, let’s create a GitHub Actions workflow file that handles both changelog generation and signed commits:

# changelog.yml
name: Generate Changelog

on:
  workflow_run:
    workflows: [CI]
    types: [completed]
    branches: [main]
  schedule:
    - cron: '0 0 * * *'  # Runs daily at midnight UTC

jobs:
  changelog:
    runs-on: ubuntu-latest
    permissions:
      contents: write # Required to update CHANGELOG.md

    # Generate changelog for successful build only
    if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Required to check out full git history

      - uses: orhun/git-cliff-action@v4
        with:
          config: cliff.toml
          args: --output CHANGELOG.md
          
      - uses: ryancyq/github-signed-commit@v1
        env:
          GH_TOKEN: ${{ github.token }}
        with:
          files: CHANGELOG.md
          commit-message: "docs: update CHANGELOG.md"

How It Works

  1. The workflow triggers when the CI workflow has completed on the main branch or runs daily at midnight UTC
  2. Git-Cliff analyzes your git history and generates a formatted changelog based on conventional commits
  3. github-signed-commit GitHub Action will commit CHANGELOG.md to the repository using GitHub Graphql API to guarantee verified commit on GitHub

Best Practices

  1. Use conventional commits in your workflow:
    • feat: for new features
    • fix: for bug fixes
    • docs: for documentation changes
    • chore: for maintenance tasks
  2. Review generated changelogs regularly:
    • Ensure proper categorization
    • Verify formatting
    • Check for sensitive information

Conclusion

Automating changelog generation with GitHub Actions streamlines your documentation process, combining Git-Cliff powerful parsing capabilities and signed commits ensures your changelog remains accurate, up-to-date, and trustworthy.

Remember to adjust the configuration to match your project’s needs and commit message patterns. This automation will save you time and ensure consistency across your project’s documentation.