Git Workflow
Generally, we utilise a "semi-linear merge" flow with all of our git projects. By keeping all of our projects using this flow, it makes new project creation and adoption much simpler.
Why?
There are many ways to organise git repositories (Git Flow, forking etc.) and each have their advantages and disadvantages. Some of these approaches would be acceptable for the work we do, however we have chosen to adopt the "Semi-Linear Merge" workflow for its simplicity to both interact with and review. By choosing this workflow, it creates a convention that all developers can follow and creating new projects is much simpler.
This git workflow suits our "real" workflow much better than other workflows, as generally speaking:
We are constantly iterating on the production code
We want to avoid having long-lived branches in our source control
Once a feature is "complete", we push this to master and any other work should be applied over this change. This encourages frequent, but smaller commits.
Regression is simpler to detect - when the production release is essentially a linear list of commits, finding problematic commits is much easier.

How?
There are multiple ways of following the semi-linear workflow
Gitlab
By default, GitLab projects should be automatically setup for semi linear workflows. All MRs to the default branch will actively require all MRs to be fast-forwarded and rebased on top of the latest main/master branch. If GitLab can automatically rebase without conflicts, this can be done in the UI:

If there are conflicts, the merge will be blocked until the rebasing has been performed locally and force pushed.

General Guidelines
As with most rulebooks, there are exceptions, but generally speaking, we try to always follow the following rules:
Branch Naming
We only utilise 3 types of branch - the purpose of each is slightly different.
master - The code in master should be: production-ready (to the best of a developer's knowledge), tested and documented. All other branches for issues and milestones should be branched from (and regularly rebased to) master.
ID-short-description (e.g. 22-graphql-permission-issue) - these are issue branches. They are specifically for resolving an issue. These should be written in such a way that only the issue referenced in ID is resolved. This is by default how Gitlab will create branches when creating MRs.
short-description (e.g. mod-collections) - these are feature branches. They are more generic than issue branches, as they can encompass multiple issues, such as a new milestone or large feature set across multiple issues. This format could also be used for minor fixes/chores not realated to an issue.
Commit Messages
All commit message should follow the Conventional Commits guidelines. More can be read here: https://www.conventionalcommits.org. By utilising this, our commit process is much simpler. Changelogs and semantic versions can be determined by the simple commit messages used.
fix(#22): fixed issue with rabbit disconnecting- this would cause the semantic version (semver) to be bumped a patch version.feat(#42): added new graphql permissions- this would cause the semver to be bumped a minor versionfeat!(#123): removed ability to login via old OAuth process- because of the!, this would cause the major version to be bumped, indicating a breaking change.
Versioning
As a side note to commits, we utilise Gitversion to automatically "predict" the next version of the application. This is performed by prepare_build in our pipeline and will provide the semantic version to the rest of our CI process.