Skip to content

Source Control

Roman Ludwig edited this page Jun 19, 2025 · 5 revisions

We follow some conventions and standard practice when it comes to git and GitHub usage. This document outlines these conventions and explains what depends on them.

Branching Model

We follow the git flow branching model somewhat loosely. This means we try to have a clean main branch, where we only push versions of the code that are ready to be released.

All development should happen on the dedicated dev branch. From this dev branch, we create feature or bugfix branches.

When we are ready to release a new version of the code, we create a release branch and e.g. name it release-x.y.z from the dev branch. Then, on GitHub, we create a pull request to merge this release branch into the main branch.

This pull request should also trigger one or two GitHub Actions workflows. Typically, before one merges the release branch into the main branch, we want to ensure that e.g. all automated tests pass, or the package and documentation are built correctly. These checks are defined in the .github/workflows directory.

Here is a visual representation of how this may look like:

%%{init: {'gitGraph': {'rotateCommitLabel': false}, 'theme': 'base'} }%%
gitGraph
    commit id: "initial commit"
    branch dev
    checkout dev
    commit id: "some dev stuff"
    branch feature-1
    checkout feature-1
    commit id: "add this"
    commit id: "fix this"
    commit id: "refactor"
    checkout dev
    merge feature-1
    branch release-x.y.z
    checkout release-x.y.z
    commit id: "update changelog"
    checkout main
    merge release-x.y.z tag: "x.y.z"
    checkout dev
    merge main
    branch feature-2
    commit id: "add feat"
    commit id: "new test"
Loading

Commit Messages

We use conventional commits for our commit messages. This is a standardized way of writing commit messages that allows us later to automatically compile a changelog from the commit history using a tool like git-cliff (see also the next section).

These conventional commits allow a range of commit types, contexts, and so on, but we typically use the following simple "sub-scheme":

<type>: <short description>

<optional long description>

<optional footer>

Where <type> may be one of the keywords that are defined in each .pre-commit-config.yaml file. We currently use the following types:

  • build: changes of the build system or dependencies
  • change: commit alters the implementation of an existing feature
  • chore: technical or maintenance task not related to feature or user story
  • ci: edits to the continuous integration scripts/configuration
  • deprecate: a feature or functionality will be deprecated
  • docs: add, update of revise the documentation
  • feat: a new feature was implemented (bump MINOR version)
  • fix: an issue or bug has been fixed (bump PATCH version)
  • perf: performance improvements that do not alter existing behaviour
  • refactor: update shuffles code around but does not alter functionality
  • remove: a feature or functionality is removed
  • style: source code is improved w.r.t. its code quality
  • test: commits enhance or add to the test suite
  • merge: merge one branch into another. Should be ignored by git-chglog
  • revert: revert previous commit(s). Should be ignored by git-chglog

The first line should be shorter than 50 characters and should be written in the imperative mood (e.g. "add", "fix", "update"). The second line should be empty, and the third line can contain a longer description with at most 72 characters per line.

The <footer> can contain e.g. references to issues on GitHub. If a commit fixes issue #123, then the footer could contain Fixes: #123. This will automatically close the GitHub issue when the commit is merged into the main branch.

Changelog Generation

To generate the changelog, we use the git-cliff tool. So, after a release branch has been created, we can run the following command to output the changes since the last version 1.2.3 to the terminal:

git cliff 1.2.3..

The we copy that and paste it to the top of the CHANGELOG.md file. Don't forget to update the version number in the CHANGELOG.md file, as well as any links to issues and GitHub diffs.

Note

We do recommend you go through the generated changelog and make any necessary adjustments. Sometimes the commit messages are overly brief or abbreviated, and it might make sense to add some more context to the changelog entry.

Semantic Versioning

We follow semantic versioning for our releases. This means that we use a version number in the format MAJOR.MINOR.PATCH, where:

  • MAJOR version is incremented when we make incompatible API changes,
  • MINOR version is incremented when we add functionality in a backwards-compatible manner,
  • PATCH version is incremented when we make backwards-compatible bug fixes.

We try to follow this as closely as possible, but being fully compliant with semantic versioning is not always possible.

To fix the version number, we use git tags on the main branch. For example, after the release branch has been merged into the main branch, we can tag the commit with the new version number:

git tag 1.2.4
git push --tags

Since the version of the Python package is derived from the git tag (see the dynamic field in the pyproject.toml file), this will also update the version of the Python package when anybody clones and installs the code from the main branch. This is achieved by a package called setuptools-scm, which - at build time - reads the latest git tag and uses it as the version number for the package. Note that you do not need to install this package separately. In the pyproject.toml file, we have the following configuration:

[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"

[project]
dynamic = ["version"]

This means that when you install the package using e.g. pip install ., pip will ensure that both setuptools and setuptools-scm are installed and the version number is dynamically determined.

Similarly, since the GitHub Actions workflow will also use the defined build-system, it too automatically picks up the new version number and builds and publishes the package using that new version number.

See also the python project documentation in this wiki for more info.

Further Reading

Clone this wiki locally