Gitflow Concepts

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Overview

Gitflow is a development and release model built around git, a branching strategy involving a small set of branch types ("main", "develop", "release-*", "hotfix-*" and feature), a and procedures around those branch types. Gitflow is used to manage releases, it is suited for projects that have a planned release cycle. The branching model that is tightly coupled with the project release cycle. It offers an elegant metal model that is easy to comprehend and allows team members to develop a shared understanding of the branching nd releasing process.

The model was conceived in 2010. Some articles designate Gitflow as obsolete. In our opinion, Gitflow continues to be relevant for all applications that need to be versioned, including applications delivered as web services or web applications. Even if you don't typically have to support multiple versions of the same application, versioning the application is still useful for planning and rollout. Rollbacks are still relevant. Gitflow helps with consistent versioning and integrates well with automated release systems. "release" branches nicely isolate the last moment release tweaks from ongoing development that can interact with "develop" at will. "hotfix" branches support cleanly patching production with critical bug fixes that can’t wait until the next scheduled release. In the best case, no critical production bugs occur, so no "hotfix" branches are used, and the entire team is working off develop towards the next release, so the release is performed straight from develop by merging it into main and no "release" branch is necessary. But these are edge cases. Using all conventional Gitflow branches and procedures introduces clarity to the development process.

Central Repository

The model relies on a central, "authoritative source of truth" repository. Git does not have a concept of "central repository" built into its domain model, the assignment is purely conventional. We will refer to this repository as origin. Each developer pulls and pushes to origin. Developers are free to push and pull among their own repositories as they jointly work on features, but ultimately, the pre-releases and releases are made from the "develop" and "main" branches contained by origin, presumably with automation that executes around the origin repository.

Branching Strategy

Gitflow mandates two specific branches, the "main" and the "develop" branches, an arbitrary number of feature branches, and release and hotfix branches as needed.

Permanent Branches

The "main" Branch

The origin/main branch lasts as long as the project is maintained. The HEAD of this branch is always tagged with a Semantic Versioning normal version and represents a version that has been deployed or in process of being deployed to production.

The production functionality come from the "develop" branch, via a temporary release branch, and each time a release branch is merged into "main", this represents a new production release by definition. This is a strict rule, and production release automation is built around the event of merging the release branch into "main".

Should the master branch contain the "abridged" history, containing only merge commits, or it should be a reflection of "develop"?

The "develop" Branch

The origin/develop branch is also referred to as the integration branch. The origin/develop branch also lasts as long as the project is maintained. Its HEAD always contains the latest delivered changes that will be included in the next release. The "develop" branch is used to build the pre-release images, which are continuously deployed and tested in a staging environment. The pre-release images can be built "nightly" or upon each merge.

When the source code in the "develop" branch reaches a stable point, or all the features that were planned for a certain release are delivered and merged into "develop", its HEAD is used as a base for the next release - it is merged into "main" via a release branch, as part of a process that will be described below.

Temporary Branches

Unlike "main" and "develop", which live indefinitely, or at least as long as the project is maintained, the feature, release and hotfix branches have a limited life time and they are deleted after they achieve their purpose. None of these branches is special from a technical perspective. They're all Git regular branches, and what distinguishes them from one another is how they're used.

Feature Branches

A feature branch, also referred to as a topic branch, always branches off from "develop" and it is merged back into "develop". It can be named anything except "main", "develop", "release-*" or "hotfix-*".

A feature branch is used to develop a well-defined, planned feature. Ideally, each logically distinct feature should be developed on its own branch. The development takes place in parallel with anything else going on in "develop". The feature branch exists as long as the feature development is on-going. The branch is merged back into "develop" as soon as the feature is complete. The branch might be discarded if it is decided that the feature is not required anymore, or the experiment failed.

Feature branches may exist in developer repositories only, if a decentralized development model is used. There is nothing wrong with maintaining the feature branches in the origin repository and deleting them as soon as they fulfill their purpose.

Feature Branch Lifecycle

A feature branch is branched off "develop":

git checkout develop
git pull
git checkout -b of/111 develop

Development should take place on the branch, across multiple commits. However, when it is time to merge the feature into develop, we prefer to coalesce the changes into just one commit, which atomically represents the feature. After the PR is reviewed, the feature branch should be merged with "Rebase and Merge". This approach avoids creating additional merge commits. If the feature needs to be rolled back, there's just one commit to rollback.

The alternative is to allow multiple commits per feature, but then when the feature branch is merged into "develop", --no-ff should be used. The --no-ff flag causes the merge to always create a new commit object, even if the merge could be performed with a fast-forward. This avoids losing information about the historical existence of a feature branch and groups together all commits that together added the feature. If no --no-ff is used, it is impossible to see from the Git history which of the commit objects together have implemented a feature - you would have to manually read all the log messages. Reverting a whole feature, as a group of commits, is a true headache in this situation, whereas is easier done if the --no-ff flag was used. Coalescing all the feature commits in one is the cleanest solution.

Upon PR merge, the feature branch must be deleted. GitHub allows this as configurable behavior.

Release Branches

A release branch always branches off from "develop" and it is merged back into "main" and "develop". It is named according to the following pattern:

release-<version>

where the "version" string is a normal Semantic Versioning-compliant version label.

release-1.0.0

Release Preparation Work. Release branches are used to prepare a release, isolating the release-specific preparation work from feature development, which can continue in "develop". Release preparation includes fixing minor bugs, updating CHANGELOG.md, documentation, etc. Adding large features on the release branch is strictly prohibited. No version update should be required, the correct version should already be in ./VERSION. All this work will be later also merged in "develop", see below.

Release Branch Lifecycle

Branch off the release branch when all features planned for release have been merged in "develop". From the moment the release branch is created, no additional features will be allowed in the current release. All features merged in develop from that moment on will be released as part of the next release.

The correct version should already be available in ./VERSION. Conventionally, this should be readable by using build automation logic with get-current-build-version --normal.

git checkout develop
git pull
git checkout -b release-$(get-current-build-version --normal)

The release branch is used to wrap up the release. When all the details are addressed, we prefer coalescing all commits issued against the release branch into just one commit, which will represent the release, as an atomic event. The commit message should be "Release 1.0.0".

Finishing the Release Branch. After coalescing the commits, the release is triggered by merging the release branch into "main" via a PR. Each commit into "main" is a release, by definition. The PR, which triggers the production release automation. Once the release completes successfully, and the release image is generated, the CI system triggers a chained post-release pipeline that 1) tags "main" with the release tag (maybe the CI system can automatically tag?), 2) merges the release commit into develop (merging the release branch back into "develop" is critical because important updates may have been added to the release branch during the release process and we want those into the main development state) and 3) increments "develop" ./VERSION to the next minor version and commits. If we know that the next planned version is a major release, we will adjust ./VERSION from a dedicated feature branch.. After merge, the release branch must be deleted. Finishing a release branch is completely similar to how hotfix branches are finished.

Hotfix Branches

A hotfix branch always branches off from "main" and it is merged back into "main" and "develop". By definition, the HEAD of "main" contains the current production version, and the patch number can be read from its version string. It is named according to the following pattern:

hotfix-<version>

where the "version" string is a normal Semantic Versioning-compliant version label. For a hotfix branch, the patch version will always be non-zero.

release-1.0.1

Hotfix branches are very much like release branches in the they are meant to prepare a new production release, albeit unplanned. They arise from the necessity to act immediately upon the undesired state of a live production version. When a critical bug in a production version cannot wait until the next planned release and must be resolved immediately, a hotfix branch may be branched off from the corresponding tag on the master branch that marks the production version. Of course, work on "develop" can continue unperturbed, while the production fix is prepared on the hotfix branch.

Hotfix Branch Lifecycle

The hotfix branch will be created from "main":

git checkout main
git checkout -b hotfix-$(get-current-build-version --next-patch)

The patch number will be obtained by automatically incrementing the previous patch number, read from HEAD. The automation must also update version metadata, usually incrementing the patch in ./VERSION. The major and minor version must remain unchanged.

The behavior that required issuing the patch is fixed on this branch.

Finishing the Hotfix Branch. The new patched production release is released with a PR into "main". This should be completely similar to how the release branches are finished.

Relationship Between Release and Hotfix Branches

When a release branch currently exists, the hotfix changes, aside from merging into "main", need to be merged into that release branch, instead of "develop". Back-merging the bug fix into the release branch will eventually result in the bug fix being merged into "develop" too, when the release branch is finished. If work in "develop" immediately requires the bug fix and cannot wait for the release branch to be finished, you may safely merge the bug fix into "develop" now already as well.