Skip to main content

Git Workflow


A shared, explicit git-workflow is essential for efficient collaboration, especially for new team members. In this chapter we document our git-workflow in order to reduce uncertainty and ambiguity in our collaboration.

We have a centralized git-workflow with feature branches. The workflow is centralized in the sense that internal team members have write access to our online repository, and are contributing directly to the online repository, rather than contributing via forks. The latter is the case for external contributors who don't have write access to the online repository.

All changes are contributed by opening pull requests from feature branches against the default branch of our online repository. The default branch is protected, only administrators can push to it directly. In the following, we illustrate our git-workflow by showing the life-cycle of a feature branch feature: from creating the branch, to opening a pull request, to eventually merging it into the default branch main.

We start with a visual representation of the git-workflow, showing how two developers collaborate on feature. Each of the developers has a branch that's tracking feature locally on their machine. Let's assume for the remaining chapter that you have a branch feature-dev-a on your machine, that's tracking the feature branch. The arrows don't represent git-specific technical details. They represent the flow of information: commits from a source branch are integrated into a receiving branch, with the arrow-head pointing towards the receiving branch.


Creating a branch for your feature

Make sure that you're branching feature off of the up-to-date main branch. When creating a feature branch, please consider the following guidelines about naming the branch:

  • choose a short, concise name
  • if the name consists of multiple words, separate those words with hyphens (-)
  • if there's an open issue about your feature, prefix the branch name with the issue number (e.g., 123-feature-name)

Keeping your feature branch up to date

It's important to keep your local feature-dev-a branch in sync with main and feature. In doing so, you can always be sure that your changes on feature-dev-a integrate well with updates that have been pushed to main and/or feature while you were working on feature-dev-a. Note that you don't need to worry about staying in sync with feature if you're the only one working on that branch.

When you sync your feature-dev-a branch regularly, it will be a lot easier to resolve merge conflicts, and you're always testing your changes against the current "ground truth" on main and feature. Ideally, you make a habit of regularly (e.g., every time you resume working on feature-dev-a) integrating the latest changes.

On this project, we merge branches without separate merge commits. That means we're integrating changes by rebasing in order to maintain a clean git history.

What does that mean in practice? Let's assume you currently have feature-dev-a checked out (i.e., git status returns "On branch feature-dev-a ...").

If there are commits on main that you don't have on feature-dev-a yet, you can pull in those changes by rebasing feature-dev-a onto main. Note that this applies to the feature branch as well: if your collaborator has pushed changes from their feature-dev-b branch to feature, you can pull in those changes by rebasing your feature-dev-a onto feature.

You can achieve this conveniently with the following command:

git pull --rebase origin main
git pull --rebase origin feature

During rebasing you might have to resolve merge conflicts.

Committing changes to your feature branch

The scope of a commit

It's difficult to formulate an exact rule when it comes to the scope of a commit. How many changes should a commit include? A useful guideline is to avoid extremes. Cramming a large number of (potentially unrelated) changes into one commit makes it hard to review and, if need be, difficult to revert. On the other hand, commits that contain only tiny changes (e.g., fixing a typo) can, in aggregate, have the same effect: by polluting the git history they can make a branch difficult to review. Aim for a focussed scope that encapsulates a meaningful unit of change that is as independent as possible of other changes on the same branch.

Commit messages

Writing descriptive and concise commit messages is very helpful for reviewing a feature branch and searching the git history. When writing commit messages, stick to the following conventions. The first line (subject line):

  • is as short as possible
  • starts with a capitalized letter
  • contains no punctuation characters
  • is written in imperative form (i.e., "Add", not "Added", or "Adds")

Additionally, you can add a more lengthy description after the subject line. Separate the subject line and description with a blank line.

Add example commit message

More detailed description of the example message,
it can contain URLs, and lists:
- foo
- bar
- baz

Pushing your feature branch

Push your commits early and frequently. Keep evolving your feature branch publicly, rather than committing locally and pushing only "once the feature is done". By publishing your commits as early as possible, you'll facilitate collaboration, as well as early, focussed review and feedback.

If you've previously rebased the branch you're about to push, you might have to force push your rebased commits to feature in case the commits had been pushed before (with a different hash). To avoid trouble with collaborators, use --force-with-lease instead of --force! If new commits are added to feature (i.e., pushes from feature-dev-b), that you haven't pulled into feature-dev-a yet, --force-with-lease would abort the push. Note that --force would overwrite / remove your collaborator's changes. In addition to the precaution of using --force-with-lease, make sure to frequently and proactively communicate with anyone collaborating on feature. It's the best way to build a shared context and make it easier to integrate your collaborative work.

Opening pull requests from your feature branch

Pull requests (in the following referred to as PR), allow the team to review features, and eventually merge them into the default branch. As a rule of thumb, open a PR sooner rather than later. In other words, you don't have to wait with opening a PR until you feel like the feature is ready and polished. On the contrary: early feedback is extremely valuable. It's much easier to make feedback-based changes in the early stages of development, rather than having to correct course once the feature-ship has already sailed.

Scoping a pull request

Avoid large pull requests that propose changes to many files. Also try to keep the size of diffs per file as small as possible. Introduce only one change at a time, and split multiple changes across multiple PRs. Well-scoped PRs speed up code review and reduce the cognitive demand on reviewers.

Naming a pull request

Give your PR a descriptive and concise title. Do not include issue numbers in the title. Consider the bigger project context when choosing a title.

Describing a pull request

In the description of the PR, try to provide as much context as possible. Especially answering why the changes were made is very useful for reviewers.

Inviting reviewers

Be proactive about inviting reviewers to your PR, and follow up with them if they don't respond. It's easy to forget about PR invites, or responding to comments on a PR, and reminders are very valuable.

PR checklist
  • if the changes are visible to the end-user, consider adding videos or screenshots to the PR description, in order to facilitate reviewing and testing
  • new features are covered with tests
  • add new features or significant other changes to the CHANGELOG
  • contribute documentation for new features by opening a PR on Docusaurus

Merging pull requests

Before a PR can be merged, a number of quality checks have to be passed:

  • linting
  • code coverage
  • approving review by at least one maintainer
  • all review discussions are marked as resolved

Once the review is finished and all quality checks pass, a maintainer with push access can merge feature into main. We merge PRs by squashing all commits on the feature branch, using the Squash and merge option in the GitHub UI. Consequently, we can conveniently trace which features have been merged into main and in which order. The merge-commit message by default includes the PR number, such that we can conveniently access the full history of a PR (despite it being squashed).