Git Repository Standards

Tools

We use the following tools:

  • Git

  • GitLab

  • Commitizen:

    enforce Conventional Commits, maintain changelog and semantic versioning.

  • Branch Detective:

    detect which commits are different between branches; build merge request messages.

Git Strategies

Collaboration Workflow

For testing and release purposes, we use the Gitflow, strategy. We reserve and protect three branches in our Git repositories:

  • devel is for the latest development version.

  • fresh is for the latest release candidate.

  • stable is for the latest stable release.

For development purposes, however, we still use trunk-based development in relation to devel, which we treat in the same manner as main in a standard trunk-based project.

In other words, we use trunk-based development against devel, but use Gitflow for handling actual releases.

Merge Strategy

We use the rebase merge strategy, meaning that a feature branch must be rebased against devel with git rebase devel before being merged.

We merge with fast-forward only (ff-only), meaning we never create dedicated merge commits.

Since we enforce Conventional Commits (see Commit Messages), we do NOT squash commits.

Commit Messages

Starting from January 2022, we require the Conventional Commits 1.0.0 standard to be used for all commit messages.

In short, a standard commit message will follow this template:

type(scope): commit title

Detailed commit summary.
This can span multiple lines.

BREAKING CHANGE: Describe breaking change, if relevant.
Co-Authored-By: Other Authors <other@example.com>, If Any <ifany@example.com>
Refs: Ticket number or Discourse link

We utilize the BREAKING CHANGE footer over only the ! annotation on the type, although both may be used.

The possible types come from the Angular style guide

  • build: Changes that affect the build system, packaging, or external dependencies.

  • ci: Changes to our CI/CD configuration files and scripts.

  • docs: Documentation-only changes.

  • feat: A new feature.

  • fix: A bug fix.

  • perf: A code change that improves performance.

  • refactor: A code change that neither fixes a bug nor adds a feature.

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

  • test: Adding missing tests or correcting existing tests.

Each commit should have a single, well-defined purpose, and all changes within the commit should relate to the purpose and the commit type. Where possible, all related changes with a given purpose and type should be grouped into a single commit.

The Changeset Approach

The goal of a Merge Request is not merely to modify code, but to present a clean changeset of neat, well-structured commits. Typically, each commit should be capable of passing all static analysis, linting, and automated tests by itself.

You also want to avoid “confetti commits”, such as multiple “fix typo” commits in the same merge request.

In practice, it’s very difficult to create such a clean set of commits on the first try. Thankfully, you can modify your commits without losing your changes.

First, check how many commits you need to rework. Make sure you do not modify any commits which are already on devel, but rather only those that were added in your current branch.

git log

At this point, you should open a text document and plan out your new commits. Look at your existing commit messages, as well as your changes themselves. Write a good commit message for each commit you plan to create, and save anything useful from your existing commit messages (which you’re about to undo).

Next, undo your commits with the following command, where # is the number of commits to undo. You’ll also stash and reapply your changes, so you have a backup in case of a mistake.

git reset --soft HEAD~#
git stash push -um "Changes before reworking commits"
git stash apply

Your changes are now all _uncommitted_. You can begin building your new commits. There are a couple of good ways to do this.

The first is to use your IDE (such as Visual Studio Code) to actually _revert_ any pending changes you don’t want in any given commit. Modify your pending changes using the source control panel to only reflect the changes you want in the one commit. Then, run your static analysis tools, tests, and so on, and create your commit. Once you’ve done that, run git stash apply again to recover any other pending changes. Resolve merge conflicts that have formed, and repeat the process with the next commit.

The other way is to use the command line to interactively pick the changes you want to include:

git add -p .

Once you’ve added the changes you want, stash the rest and commit.

git stash push -um "Changes after <title of commit>"
git commit

Run your static analysis tools, linters, tests, and so on. Fix any errors directly, amending your commit as necessary.

Then, apply your last stash…

git stash apply

And repeat for the next commit.

If at any point during either process, you realize you forgot to include a change in a previous commit, you can go back and modify that commit. Finish your current commit first, stash if necessary (to get a clean repository), and then run the following, where # is a number greater than or equal to how many commits back the oldest commit you want to change is:

git rebase -i HEAD~#

This will open a file listing the commits to be changed. Find the commit(s) you want to modify, and change pick to edit next to them. Save and close the file. You will now be guided through the rebase process by the command line prompts (so read them carefully!)

Merge Requests

Changes to devel should be proposed by creating a Merge Request on our GitLab instance. This Merge Request should include one or more commits.

A Merge Request should be related to a single primary goal, generally a single task in our issue tracker. It may include other minor improvements, but should NOT include changes for multiple issues, unless those issues are unavoidably related.

Every Merge Request must…

  1. Accomplish the goals(s) it was designed to accomplish.

  2. Comply with Conventional Commits for all commit messages.

  3. Be rebased against the latest version of devel (or whatever branch is targeted), and all conflicts resolved ($ git pull origin devel). (We do NOT use the “squash” or “merge” Git strategies.)

  4. Have binaries and unnecessary cruft untracked and removed. (Keep an eye on .gitignore!)

  5. Compile and run properly. (Confirmed via the CI/CD Pipeline.)

  6. Be free of compiler errors and warnings; for C++, must compile with -Wall -Wextra -Werror. (Confirmed via the CI/CD Pipeline.)

  7. For C++, be Valgrind pure, meaning no memory leaks are detected. (Confirmed via the CI/CD Pipeline.)

  8. Comply with Coding and Technical standards.

  9. Include tests validating the accomplishment of goals in (1). These tests must be written in the project’s test framework, if relevant.

  10. Be fully Commenting-Showing Intent commented.

  11. Have an up-to-date build script (generally CMake) if relevant.

  12. Be reviewed, built, tested, and approved by at least one trusted reviewer.

  13. Have up-to-date Sphinx documentation, which compiles with no warnings.

  14. Have all reviewer comments processed and marked “Done”.

Versioning

Starting from January 2020, we use Semantic Versioning 2.0.0 for all software development projects. Our version numbers follow the format X.Y.Z

  • X is a major release, reserved for changes to the API or CLI.

  • Y is a minor release, for releases with new features.

  • Z is a patch release, for bugfix-only releases.

We use Commitizen to automatically generate semantic versions from Conventional Commit messages.

About Zero-Versions

Zero-Versions are versions at Major Release 0. They are considered inherently unstable and feature incomplete, and thereby un-releasable. They cannot be promoted to either fresh or stable.

A project remains at a Zero-Version until it is considered complete and stable enough to promote to version 1.

To enforce this rule, projects at version zero should NEVER have BREAKING CHANGE commits. When it is time to promote the project to version 1, a single, dedicated commit with an insignificant change should be created with BREAKING CHANGE.