Gitflow Concepts: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 91: Line 91:


==<span id='hotfix'></span>Hotfix Branches==
==<span id='hotfix'></span>Hotfix Branches==
A '''hotfix''' branch always branches off from "main" and it is merged back into "main" '''and''' "develop". It is named according to the following pattern:
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:
<font size=-2>
<font size=-2>
  hotfix-<version>
  hotfix-<version>
</font>
</font>
where the "version" string is a normal Semantic Versioning-compliant version label. For a hotfix branch, the patch version will always be non-zero, <font color=darkkhaki>and it will be obtained by automatically incrementing the previous patch number.</font>
where the "version" string is a normal Semantic Versioning-compliant version label. For a hotfix branch, the patch version will always be non-zero, <font color=darkkhaki>and it 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 <code>./VERSION</code>.</font>
<font size=-2>
<font size=-2>
  release-1.0.1
  release-1.0.1
</font>
</font>
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 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.

Revision as of 03:17, 6 March 2024

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.

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".

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. 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, 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".

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 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..

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, and it 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.

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 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.