TECH

Semantizing: Semantic Versioning, Releases, and Github

JARRETT RETZ July 29th, 2021 api versioning python fastapi github programming

Photo by Edward Howell on Unsplash

Introduction

Professional API workflows use versioning. However, this is not specific to APIs. Version control is a core principle in software engineering and is found wherever software is.

Principles do not change as frequently (if ever) as best practices do. In fact, principles inform best practices.

When working with APIs (or published libraries, for that matter), a best practice is to use semantic versioning (semver). This has become a common practice for adding features, fixes, and upgrading software. In addition, it communicates changes in the codebase between the development team and users.

Therefore, I wanted to add semantic versioning and releases to a private Github repository of mine. But, I wasn't quite sure how to do it with Python and make it easy with Github. I knew that other people have set up workflows that helped with versioning in Github, so I set out to implement something I could use.

This article explains a workflow combining semver, Conventional Commits, and Github actions. I'm delighted with the setup and think that it saves me time.

Semantic Versioning

To explain why the workflow solves the problem and how the pieces fit together, I'll start with semver.

I won't explain semver in detail because there is already a website that does that. However, I will copy the summary section over to give you an idea.

Given a version number MAJOR.MINOR.PATCH, increment the:

1. MAJOR version when you make incompatible API changes,
2. MINOR version when you add functionality in a backwards compatible manner, and
3. PATCH version when you make backwards compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

If you have spent any time downloading libraries, this format is recognizable. For example, ReactJS is currently in version 17.0.2. Python's current version is 3.9.6.

Conventional Commits

In the past, I was confused when I saw other peoples' commits prefixed with words like chore and fix. It seemed trivial.

This was before I learned about Convention Commits. Conventional commits outline a series of prefixes for code commits that categorize what is being done.

Why?

As stated on the website for Conventional Commits, using these prefixes can help with:

- Automatically generating CHANGELOGs.
- Automatically determining a semantic version bump (based on the types of commits landed).
- Communicating the nature of changes to teammates, the public, and other stakeholders.
- Triggering build and publish processes.
- Making it easier for people to contribute to your projects, by allowing them to explore a more structured commit history.

If you combine your understanding of semver and why you should use conventional commits, it's apparent (I hope) the usefulness in combining the two.

It's now time to talk about Github because you can't talk about version control without Git.

Github Actions and Releases

Repositories in Github have release data. Let's use React as an example.

To record a release in Github, you use tagging. Tags mark points in the repositories history. Take a look at the tags and corresponding releases for React.

Notice, all the tags use semver.

There are ways in Git to create tags manually. Then, you could draft a release and changelog notes manually. However, with a bigger project and many releases, this is a lot of manual work. Additionally, it's difficult to keep track of all the things that changed: what did that commit do, exactly?

Here's the cool part. You can use Github Actions (with conventional commits) to automate your semantic versioning, tagging, and releasing.

How does this work?

1. Use Conventional Commits

When committing code, use the prefixes described in conventional commits. There is a cheat sheet here, and I'll paste the descriptions below.

feat - Features

A new feature

fix - Bug Fixes

A bug fix

docs - Documentation

Documentation only changes

style - Styles

Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.)

refactor - Code Refactoring

A code change that neither fixes a bug nor adds a feature

perf - Performance Improvements

A code change that improves performance

test - Tests

Adding missing tests or correcting existing tests

build - Builds

Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)

ci - Continuous Integrations

Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)

chore - Chores

Other changes that don't modify src or test files

revert - Reverts

Reverts a previous commit

One of the most important syntax requirements is to append an exclamation point on any prefix that commits breaking changes. (i.e., feat!: commit message)

2. Add this Github Action To Your Repo

***

I pulled this Github Action code from Lukasz Gornicki's article that's published on the AsyncAPI blog. It's a two-part series and includes more context and information than I include below.

***

Github actions are included in repositories with the folder structure /.github/workflows/.

The .github folder needs to be at the root of the project. We define actions in a YAML file.

Create a new file in /.github/workflows/ named release.yml.

Paste in the following code:

name: Release

on:
  push:
    branches:
      - master

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repo
      uses: actions/checkout@v2
    - name: Setup Node.js
      uses: actions/setup-node@v1
      with:
        node-version: 13
    - name: Add plugin for conventional commits
      run: npm install conventional-changelog-conventionalcommits
      working-directory: ./.github/workflows
    - name: Release to GitHub
      working-directory: ./.github/workflows
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GIT_AUTHOR_NAME: enter author name
        GIT_AUTHOR_EMAIL: person@example.com
        GIT_COMMITTER_NAME: enter name
        GIT_COMMITTER_EMAIL: committer@example.com
      run: npx semantic-release

This Github Action runs when we push commits up to the master branch.

It checks our commits and decides whether it needs to bump the version. If it needs to bump the version, it collects the commits (based on the prefix category), creates a tag for the new version, and publishes the release.

If the commits have a fix prefix, the version is patched. If they have a feat prefix, the version is bumped by 1 minor increment. Finally, if any prefix has an exclamation point, an entire major version is released (1.0.0 -> 2.0.0).

Another cool feature is the commit messages are organized in the release notes.

When I tested this action out, my repository bumped the release version because I made two fix commits before getting the Github Action to work.

I didn't have to organize the commit messages and decide whether to bump the version or create a tag. It all just happened.

Redundant Commits

The two bug fix commits are redundant. I was testing out the Github Action and it failed a couple of times because my tests weren't set up properly.

I fixing the same problem, but in different files and separating the commits so my commits weren't just 'test' commits.

Conclusion

It's easy for me to get frustrated when things don't work properly when programming. It's harder to appreciate when something works better than you could expect it to work. Fortunately, this time, I can marvel at how cool and useful all these tools are.

As always, thanks for reading!


Have a thought about the article?

Send JRTS a message!

We'll use this email to respond to your message.
Contact