{
"$type": "site.standard.document",
"canonicalUrl": "https://rednafi.com/misc/audit-commit-messages-on-github/",
"description": "Automate commit message validation with GitHub Actions. Enforce refs and closes patterns to maintain clean Git history and link commits to issues.",
"path": "/misc/audit-commit-messages-on-github/",
"publishedAt": "2022-10-06T00:00:00.000Z",
"site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
"tags": [
"GitHub",
"DevOps",
"Shell",
"Unix"
],
"textContent": "After reading Simon Willison's [amazing piece on how he builds a feature], I wanted to adopt\nsome of the good practices and incorporate them into my own workflow. One of the highlights\nof that post was how to kick off a feature work. The process roughly goes like this:\n\n- Opening a new GitHub issue for the feature in the corresponding repository.\n- Adding a rough description of the feature to the issue.\n- Creating a feature branch off of main/master/trunk. If the feature is trivial or just a\n doc update, this step can be skipped.\n- Referring to the issue in every commit message as you start working on the feature:\n - Appending #refs <issue-number> to every commit message. This will attach the commit\n to the concerning issue on the GitHub UI.\n - Appending #closes <issue-number> to the final commit message when the feature is\n complete.\n - If you need to refer to an issue after it's closed, you can still do that by appending\n #refs <issue-number> to the commit message. So a commit message should look similar\n to Feature foo, refs #120 or Update foo, closes #115. The comma (,) before\n refs/closes is essential here. I like to enforce it.\n\nThis pattern also works for bugfixes without any changes. Here's an [example issue] that\nshows the workflow in action. Plus, I follow a similar pattern to write the blogs on this\nsite as well. This is what a feature issue might look like on GitHub:\n\n![GitHub issue showing commits linked with refs and closes keywords][image_1]\n\nWhile I'm quite happy with how the process is working for me, often time, I get careless and\npush commits without a reference to any issue. This pollutes the Git history and breaks my\nstreak of maintaining good hygiene. So, I was looking for a way to make sure that the CI\nfails and reprimands me whenever I'm not following the process correctly. It's just one less\nthing to worry about.\n\nI've decided to use GitHub Actions to audit the conformity of the commit messages. The CI\npipeline is orchestrated as follows:\n\n- After every push and pull-request, the audit-commits job in an audit.yml workflow file\n will verify the conformity of the commit messages. This job runs a regex pattern against\n every commit message and fails with exit code 1 if the message doesn't respect the\n expected format.\n- If the audit-commits job passes successfully, only then the primary jobs in the ci.yml\n workflow will execute. The entire pipeline will fail and the primary CI workflow won't be\n triggered at all if the audit-commit job fails at any point.\n\nOn GitHub, you're expected to place your workflow files in the .github/workflows\ndirectory. If you inspect this blog's [workflows folder], you'll see this pattern in action.\nHere, the directory has three workflow files:\n\nThe automerge.yml file automatically merges a pull-request when the primary CI jobs pass.\nI wrote about it in more detail in [another post about automerging Dependabot PRs]. We'll\nignore the automerge.yml file for now. Here, the audit file runs after every push and\npull-request and verifies the structure of the commit message. I picked a generic name like\naudit.yml instead of a more specific one like audit-commit.yml because in the future if\nI want to add another check, I can easily extend this file without renaming it. Here's the\nunabridged content of the audit.yml file:\n\nI've defined this workflow as a reusable one. A reusable workflow can be called like a\nfunction with parameters from another workflow. The workflow_call node the audit.yml\nfile makes it a reusable one and you can define additional parameters in this section if you\nneed to do so. However, in this particular case, I don't need to pass any parameters while\ncalling the audit.yml workflow from the ci.yml workflow. You can find more details on\nhow to define [reusable workflows] in the docs.\n\nIn the jobs section of the audit.yml file, we define a single audit-commits job that\nruns a bash script against every incoming commit message and verifies its structure. The\ncommit messages can be accessed from the '${{ toJSON(github.event.commits) }}' context\nvariable. Then the script loops over every commit message and verifies the structure. It'll\nterminate the job with exit code 1 if the incoming message doesn't match the expected\nstructure. Otherwise, the script will gracefully terminate the job with exit code 0.\n\nIn the main ci.yml file the audit.yml workflow is called like this:\n\nThe ci.yml file roughly looks like this:\n\nHere the needs: [\"audit\"] node in the build section ensures that the build will only\ntrigger if the audit job passes successfully. Otherwise, none of the build, test, or\ndeploy jobs will run and the CI will fail with a non-zero exit code. Here's the [fully\nworking ci.yml file].\n\nNotes\n\nGitHub Actions terminology can be confusing.\n\n- A workflow is a separate file that contains one or more jobs.\n- A job is a set of steps in a workflow that executes on the same runner.\n- A runner is a server that runs your workflows when they're triggered. Each runner can\n run a single job at a time.\n- A reusable workflow can be called from another workflow file.\n\nThe docs have more information on [understanding GitHub Actions].\n\n\n\n\n[amazing piece on how he builds a feature]:\n https://simonwillison.net/2022/Jan/12/how-i-build-a-feature/\n\n[example issue]:\n https://github.com/rednafi/reflections/issues/170\n\n[workflows folder]:\n https://github.com/rednafi/reflections/tree/master/.github/workflows\n\n[another post about automerging Dependabot PRs]:\n /misc/automerge-dependabot-prs-on-github/\n\n[reusable workflows]:\n https://docs.github.com/en/actions/using-workflows/reusing-workflows\n\n[fully working ci.yml file]:\n https://github.com/rednafi/reflections/blob/master/.github/workflows/ci.yml\n\n[understanding GitHub Actions]:\n https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions\n\n[image_1]:\n https://blob.rednafi.com/static/images/audit_commit_messages_on_github/img_1.png",
"title": "Auditing commit messages on GitHub"
}