Git Flow - Using Git Branches for Effective Development Management
Git Flow is a popular branching model in Git that helps organize the development process and manage application versions. It was proposed by Vincent Driessen in 2010 and has since become a standard for many development teams.
In short, Git Flow looks like this:
---
config:
gitGraph:
parallelCommits: false
showCommitLabel: false
---
gitGraph TB:
commit tag: "1.0.0"
branch hotfix order: 2
branch develop order: 4
checkout develop
commit id: ""
branch feature-1 order: 5
branch feature-2 order: 6
branch feature-3 order: 7
checkout feature-1
commit
commit
checkout feature-3
commit
checkout feature-2
commit
checkout hotfix
commit
checkout feature-1
commit
checkout main
merge hotfix tag: "1.0.1"
checkout develop
merge hotfix
checkout feature-1
merge develop
commit
checkout develop
merge feature-1
checkout develop
commit
branch release order: 3
checkout release
commit
commit
checkout develop
merge release
checkout release
commit type: HIGHLIGHT
checkout develop
merge release type: HIGHLIGHT
checkout main
merge release tag: "1.1.0"
checkout feature-2
merge develop
checkout feature-3
merge develop
checkout main
See the link to the diagram code in the Live Mermaid editor below.1
Decentralized development
Git has long become the standard for version control systems. Created by Linus Torvalds in 2005, Git allows developers to manage code changes efficiently and collaborate on projects.
Git was built to work on the Linux kernel, meaning that it was built to handle repositories with tens of millions of lines of code from the start. Speed and performance has always been a primary design goal of Git.
By nature, Git was designed as a decentralized version control system, meaning every developer has a full copy of the repository, including its history. This allows developers to work independently and exchange changes conveniently. Everything looks fine until the next question appears: how do you organize project work so that it is effective and understandable for everyone? How do you overcome the chaos that can arise with many branches and changes?
Decentralized centralization
So, many developers are working on a project, and each has their own copy of the repository. All these copies are equal. By convention or historical circumstances, one of these copies is recognized as the central code repository where changes from other developers eventually land. This central repository can be hosted on GitHub, GitLab, or your own server. Such a repository is treated as the source of truth. Project members can make changes in their local branches and then send them to the central repository via pull requests or merge requests. This approach preserves a centralized structure where all changes pass through the central repository while still giving each developer the freedom to work independently in their own local copy.
sequenceDiagram
participant R@{ "type" : "collections" } as Central<br>Repo
participant JR as John<br>Repo
participant AR as Alice<br>Repo
participant JL as John<br>Local
participant AL as Alice<br>Local
par Repository cloning
R->>JR: John clones repo
and
R->>AR: Alice clones repo
end
par Pulling code into local repository
JR()->>()JL: John pulls code into local repository
and
AR()->>()AL: Alice pulls code into local repository
end
par Making code changes locally
JL->>JL: John makes changes<br>to his code
and
AL->>AL: Alice makes changes<br>to her code
end
par Pushing changes to personal remote repository
JL()->>()JR: John pushes changes to his remote repository
and
AL()->>()AR: Alice pushes changes to her remote repository
end
JR()-->>R: John opens a PR to the<br>central<br>repository
AR()-->>R: Alice opens a PR to the<br>central repository
R-)JR: John receives updates<br>from the central<br>repository
Main branches
In Git Flow, there are two main branches: main and develop. The HEAD of main contains the stable code version ready for release. It should always be in a working state and must not contain unfinished changes. The develop branch is used to integrate new features and changes. It may contain unfinished work but should remain stable and ready for testing. When the code in develop is ready for release, it is merged into main, and a new tag is created to mark the version.
---
config:
gitGraph:
parallelCommits: false
showCommitLabel: false
mainBranchOrder: 2
---
gitGraph LR:
commit tag: "1.0.0"
branch develop order: 2
checkout develop
commit id: ""
commit
checkout main
commit
checkout develop
commit
checkout main
merge develop tag: "1.1.0"
commit
branch hotfix
checkout hotfix
commit
checkout main
merge hotfix tag: "1.1.1"
checkout develop
merge hotfix
checkout main
commit
checkout develop
commit
Supporting branches
Along with the main branches, Git Flow defines several supporting branch types used for developing new features, fixing bugs, and preparing releases. These are feature, release, and hotfix branches. Each branch type has its own purpose and usage rules. Technically, these are just regular Git branches, but teams follow conventions about how they are used and merged.
feature branches
feature branches are used for developing new features or implementing changes. They are created from develop and merged back into develop after the work is finished. Branches can have arbitrary names, but they usually start with feature/* or feat/*. Names like main, develop, release-*, or hotfix-* should not be used for feature branches, because these names are reserved for other branch types.
Sometimes feature branches are called topic branches (topic) and used for work that does not necessarily have to be included in the next release. These branches live as long as needed to complete the feature and are eventually merged into develop to include the feature in a future release. Or they may be discarded if experiments around the feature are unsuccessful.
Usually, feature branches live in developers’ repositories rather than in the central repository, since they are temporary.
Creating a feature branch
To work on a new feature, create a feature branch from develop:
git checkout -b feature/my-new-feature develop
# or
git switch -c feature/my-new-feature develop
Merging changes from feature to develop
When feature work is complete, it is merged into develop so it can be included in the next release.2
git checkout develop
# or
git switch develop
git merge --no-ff feature/my-new-feature
git branch -d feature/my-new-feature
git push origin develop
The --no-ff flag is used to preserve feature branch history in the develop branch history, even when the merge could be done with fast-forward. This makes it easier to track which changes belong to a specific feature and simplifies rollback if needed. Compare the two examples below.
---
config:
gitGraph:
parallelCommits: false
showCommitLabel: true
mainBranchName: "develop"
rotateCommitLabel: false
---
gitGraph LR:
commit tag: "1.0.0"
commit
branch feat/new-f
checkout feat/new-f
commit
commit
commit
checkout develop
merge feat/new-f id: "[git merge --no-ff]" tag: "1.1.0"
Merge with git merge --no-ff
---
config:
gitGraph:
parallelCommits: false
showCommitLabel: true
mainBranchName: "develop"
rotateCommitLabel: false
---
gitGraph LR:
commit tag: "1.0.0"
commit
commit
commit
commit
commit tag: "1.1.0" id: "[git merge]"
Regular merge with git merge
In the second case, since all changes from the feature branch can be fast-forwarded into develop, the develop history does not preserve information that these commits came from a separate feature branch. This can make change tracking and context harder to understand. Using --no-ff (the first case) creates a dedicated merge commit, preserving that context.
release branch
A release branch is used to prepare a new version for release. It is created from develop when the code is release-ready but still needs final adjustments, such as updating the version number, fixing small bugs, or adjusting the release date. Minor fixes are allowed. After release preparation is complete, the release branch is merged into both main and develop, and a new tag3 is created to mark the version. After that, develop is ready to accept changes for the next release.
The release branch is created from develop when the state of develop matches the intended release state. At this point, all features planned for that release should already be in develop. New features intended for later releases should only be merged into develop after the release branch is created, so they do not affect release stability.
Creating a release branch
release branches are created from develop. For example, suppose the current production version in main is 1.1.25 and you want to ship a release with significant changes. The state of develop matches what you want to release as 1.2.0. You create a release branch from develop and name it release-1.2.0.
git checkout -b release-1.2.0 develop
# or
git switch -c release-1.2.0 develop
Then make the required release-preparation changes, such as updating the version number, fixing minor bugs, or adjusting the release date.
# Make release-preparation changes
git add .
git commit -m "Prepare release 1.2.0"
The release branch exists until release preparation is finished. During this time, minor fixes are allowed, but new feature work is not: new features should go into develop for future releases.
Completing the release process
Once release preparation is complete, merge release into both main and develop, and create a new tag to mark the version. After that, develop is ready for next-release work.
Every commit to main is considered a new version release by definition. To mark the released version, create a tag3 with the version number. This makes it easy to see which changes are included in each release and simplifies rollback to previous versions if necessary.
git checkout main
git merge --no-ff release-1.2.0
git tag -a 1.2.0 -m "Release version 1.2.0"
At this point, the new version has been released and marked with tag 1.2.0 for future reference.
You can also use
-sor-uto sign a tag with a GPG key if you want additional security and trust for your releases.
Changes made in release must also be merged back into develop.
git checkout develop
git merge --no-ff release-1.2.0
This may cause conflicts if develop changed after the release branch was created. In that case, resolve the conflicts.2
After this step, the release cycle is complete. The release branch is no longer needed and can be deleted.
git branch -d release-1.2.0
hotfix branches
hotfix branches are used for urgent fixes of critical bugs in stable code. They are created from main when a critical issue is found and needs immediate correction. After applying the fix, the hotfix branch is merged into both main and develop, and a new tag is created to mark the version. This lets you quickly fix production issues without disrupting feature development in develop.
hotfix branches use names like hotfix-*. They are created from the tag in main that marks the version where the bug was found. For example, if the bug is in version 1.2.0, the hotfix branch is created from tag 1.2.0 and named hotfix-1.2.1. This allows the team to continue feature development in develop while the critical fix is handled in hotfix.
---
config:
gitGraph:
parallelCommits: false
showCommitLabel: true
rotateCommitLabel: true
---
gitGraph LR:
commit tag: "1.2.0"
branch hotfix-1.2.1
branch develop
commit
commit
commit
commit
checkout hotfix-1.2.1
commit
commit
commit id: "Fixed critical bug in 1.2.0"
checkout main
merge hotfix-1.2.1 tag: "1.2.1" id: "git merge hotfix-1.2.1"
checkout develop
merge hotfix-1.2.1
commit
Creating a hotfix branch
Create a hotfix branch from main. For example, if the current production version in main is 1.2.0 and you need to fix a critical bug in that version, create a hotfix branch from main called hotfix-1.2.1.
git checkout -b hotfix-1.2.1 main
# or
git switch -c hotfix-1.2.1 main
Do not forget to update the version in code to indicate the bug-fix release.
# Update version in code
git add .
git commit -m "Bump version to 1.2.1"
Then add the actual bug fix in a separate commit.
# Apply critical bug fix
git add .
git commit -m "Fix critical bug ..."
Completing the hotfix process
When the bug fix is complete, merge hotfix into both main and develop, and create a new tag to mark the version.
First, merge hotfix into main and create a tag for the new version.
git checkout main
git merge --no-ff hotfix-1.2.1
git tag -a 1.2.1 -m "Fix critical bug in version 1.2.0"
You can also use
-sor-uto sign a tag with a GPG key if you want additional security and trust for your releases.
Then merge hotfix into develop to include the bug fix in the next release.2
git checkout develop
git merge --no-ff hotfix-1.2.1
However, if you currently have a release branch, merge hotfix into release instead of develop. After the release cycle is completed, release is merged into develop, so hotfix changes will still end up in develop.
Finally, after the hotfix process is complete, delete the hotfix branch.
git branch -d hotfix-1.2.1
Summary
So, the general Git Flow branch model shown at the beginning of this post is not rocket science. It is simply a set of conventions that helps organize development and version management. These conventions are not mandatory, but they can be very useful for maintaining order and efficiency. It is important to remember that Git Flow is only one of many possible branching models in Git. The right model depends on your project’s specific needs and context.
PS
During project development, you can also follow The Twelve-Factor Manifesto recommendations for dependency management, configuration, and other engineering concerns to build a more efficient and scalable development and runtime process. You can read more in the Twelve-Factor documentation.
You can view the diagram code in the Live Mermaid editor ↩︎
Do not forget to update your
featurebranches with the latest changes fromdevelopwhen needed to avoid merge conflicts. You can do this withgit merge developorgit rebase developfrom within yourfeaturebranch before merging intodevelop. ↩︎ ↩︎ ↩︎It is recommended to follow Semantic Versioning conventions for version numbers to ensure a clear and consistent versioning system for your project. See the Semantic Versioning documentation. ↩︎ ↩︎
Related posts
- OSM 2.0 API using git 07.05.2023