GitLab flow
lastmod 2026-03-24created 2022-10-2611 edits
The flow described by GitHub is so simple that it left many issues about deployment, environment configuration, releases, and integration unaddressed. This refers to GitLab's additional explanation of related topics to complement it.
About
Git allows a wide variety of branching strategies and workflows. Because of this, in many organizations workflows are too complicated, not clearly defined, or not integrated with issue tracking systems. Therefore, we propose GitLab flow as a clearly defined set of best practices. It combines feature-driven development (FDD) and feature branches with issue tracking.
Release branches with GitLab flow
You should work with release branches only if you need to release software to the outside world.
Release_branches_with_GitLab_flow.jpg
Create stable branches using main as a starting point, and branch as late as possible. This minimizes the time during which you have to apply bug fixes to multiple branches.
After a release branch is announced, only add serious bug fixes to the branch. If possible, first merge these bug fixes into main, and then cherry-pick them into the release branch.
If you start by merging into the release branch, you might forget to cherry-pick them into main, and you'll encounter the same bug in subsequent releases.
Merging into the release and then cherry-picking is called an "upstream first" policy, and it is also practiced by Google and Red Hat.
Every time you include a bug fix in a release branch, raise the patch version by setting a new tag. In doing so, follow Semantic Versioning.
Some projects also have a stable branch that points to the same commit as the latest release branch. In this flow, it is not common to have a production branch (or Git flow branch).
Merge/pull requests with GitLab flow
If you work on a feature branch for more than a few hours, share the intermediate result with the rest of the team. To do this, create a merge request without assigning it to anyone. Instead, mention people in the description or a comment (for example, /cc @mark @susan) This indicates that the merge request is not yet ready to be merged, but feedback is welcome.
Team members can comment on the merge request in general or on specific lines with line comments. The merge request serves as a code review tool, and no separate code review tool is needed. If the review reveals shortcomings, anyone can commit and push a fix. Usually, the person who does this is the author of the merge request. The diff in the merge request updates automatically when new commits are pushed to the branch.
When the feature branch is ready to merge, assign the merge request to the person who knows most about the codebase you are changing. Also mention anyone else you would like feedback from. If the assigned person is satisfied with the result, they can merge the branch. If the assigned person is not comfortable, they can request more changes or close the merge request without merging.
In GitLab, it is common to protect long-lived branches, such as the main branch, so that most developers can't modify them. So, to merge into a protected branch, assign the merge request to someone with the Maintainer role.
After you merge a feature branch, you should remove it from the source control software. In GitLab, you can do this when merging. Removing finished branches ensures that the list of branches shows only work in progress. It also ensures that if someone reopens the issue, they can use the same branch name without causing problems.
Issue tracking with GitLab flow
GitLab flow is a way to make the relationship between the code and the issue tracker more transparent.
Any significant change to the code should start with an issue that describes the goal. Having a reason for every code change helps to inform the rest of the team and to keep the scope of a feature branch small. In GitLab, each change to the codebase starts with an issue in the issue tracking system. If there is no issue yet, create the issue if the change will take more than an hour of work. In many organizations, raising an issue is part of the development process because issues are used in Korean: 스프린트 planning.
The issue title should describe the desired state of the system.
For example, the title As an administrator, I want to remove users without receiving an error is better than "Admin can't remove users." [^0]
When you are ready to code, create a branch for the issue from the main branch. This branch is the place for all work related to this change.
The name of a branch might be dictated by organizational standards. When you are done or want to discuss the code, open a merge request. A merge request is an online place to discuss the change and review the code.
If you open the merge request but do not assign it to anyone, it is a draft merge request (Draft). These are used to discuss the proposed implementation but are not yet ready for inclusion in the main branch.
Start the title of the merge request with [Draft], Draft: or (Draft) to prevent it from being merged before it's ready.
When you think the code is ready, assign the merge request to a reviewer.
The reviewer can merge the changes when they think the code is ready for inclusion in the branch. When they press the merge button, GitLab merges the code and creates a merge commit that makes this event visible later. Merge requests always create a merge commit, even when the branch could be merged without one. This merge strategy is called "no fast-forward" in Git. After the merge, delete the feature branch, since it is no longer needed. In GitLab, this deletion is an option when merging.
Suppose that a branch is merged but a problem occurs and the issue is reopened. In this case, there is no problem reusing the same branch name, since the first branch was deleted when it was merged. At any time, there is at most one branch for every issue. One feature branch may solve more than one issue.
Linking and closing issues from merge requests
Link to an issue by mentioning it in a commit message or the description of a merge request. (for example, "Fixes #16" or “Duck typing is preferred. See #12.”)
GitLab then creates links to the mentioned issues and creates comments on the issues linking back to the merge request.
To automatically close linked issues, mention them using the words "fixes" or "closes," - the comma (, is included too! - . (for example, “fixes #14” or “closes #67.”). GitLab closes these issues when the code is merged into the default branch.
If you have an issue that spans multiple repositories, create an issue for each repository and link all issues to a parent issue.
Squashing commits with rebase
With Git, you can use an interactive rebase (rebase -i) to squash multiple commits into one or reorder them. This feature helps when you want to replace a couple of small commits with a single commit, or make the order more logical.
pick c6ee4d3 add a new file to the repo
pick c3c130b change readme
# Rebase 168afa0..c3c130b onto 168afa0
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
~
~
~
"~/demo/gitlab-ce/.git/rebase-merge/git-rebase-todo" 20L, 673C
However, you should not rebase commits you have pushed to a remote server if there are other active contributors on the same branch. Since rebasing creates new commits for all your changes, it can cause confusion because the same change would have multiple identifiers. It may cause merge errors, because the history of everyone working on the same branch doesn't match yours. It can be a real headache for the author or other contributors. Also, if someone has already reviewed your code, rebasing makes it hard to tell what changed since the last review.
You should never rebase commits authored by other people unless you've agreed otherwise. Not only does this rewrite history, but it also loses authorship information. Rebase prevents the other authors from being attributed and sharing part of the git blame.
If a merge involves many commits, it may seem more difficult to undo. You can solve this problem by using the GitLab Squash-and-Merge feature to squash all changes into a single commit just before merging. Fortunately, you can undo a merge with all its commits. The way to do this is to revert the merge commit. Preserving this ability to revert a merge is a good reason to always use the "no fast-forward" (--no-ff) strategy when you merge manually.
Reducing merge commits in feature branches
Having lots of merge commits can make your repository history messy. Therefore, you should avoid merge commits in feature branches. Often, people avoid merge commits by using rebase to reorder their commits after committing on the branch. Using rebase prevents a merge commit when merging main into your feature branch, and creates a neat linear history.
Rebasing may create more work, since every time you rebase, you may have to resolve the same conflicts. Sometimes you can reuse recorded resolutions (rerere), but merging is better, since you only have to resolve conflicts once. Atlassian explains the tradeoffs between merging and rebasing more thoroughly on their blog.
A good way to prevent creating many merge commits is to not frequently merge main into the feature branch.
There are three reasons to merge in main:
- utilizing new code
- resolving merge conflicts
- updating long-running branches
If you need to use some code that was introduced in main after you created the feature branch, you can solve this by cherry-picking a commit.
If your feature branch has a merge conflict, creating a merge commit is the standard way of solving this.
.gitattributescan sometimes be used to reduce merge conflicts. For example, you can set your changelog file to use theunion merge driverso that multiple new entries don't conflict with each other.
The last reason for creating merge commits is to keep long-running feature branches up to date with the latest state of the project. The solution here is to keep your feature branches short. Most feature branches take less than a day of work.
If feature branch work often takes more than a day, split the feature into smaller units of work.
If you need to keep a feature branch open for more than a day, there are a few strategies to keep it up to date.
- One option is to use CI (continuous integration) to merge in main at the start of the day.
- Another option is to only merge in at well-defined points in time, such as a tagged release.
- You could also use Feature Toggle to hide incomplete features, so you can still merge back into main every day.
Don't confuse automatic branch testing with continuous integration. Martin Fowler makes this distinction in hisarticle about feature branches: “[People] say they are doing CI because they are running builds using a CI server on every branch with every commit. That's continuous building, and a good thing, but there's no integration, so it's not CI.”
In conclusion, you should try to prevent merge commits, but not eliminate them. Your codebase should be clean, but your history should represent what actually happened. Developing software happens in small, messy steps, and it is good to have your history reflect this. You can use tools to view the network graphs of commits and understand the messy history that created your code. If you rebase code, the history is incorrect, and there is no way to remedy this because tools can't deal with changing commit identifiers.
Commit often and push frequently
Another way to make your development work easier is to commit often. Every time you have a working set of tests and code, you should make a commit. Splitting up work into individual commits provides context for developers looking at your code later. Smaller commits make it clear how a feature was developed. They help you roll back to a specific point in time, or revert one code change without reverting several unrelated changes.
Committing often also helps you share your work, which is important so that everyone is aware of what you are working on. You should push your feature branch frequently, even when it is not yet ready for review. By sharing your work in a feature branch or a merge request, you prevent your team members from duplicating work. Sharing your work before it's complete also allows for discussion and feedback about the changes. This feedback can help improve the code before it gets reviewed.
How to write a good commit message
A commit message should reflect your intention, not just the contents of the commit. You can see the changes in a commit, so the commit message should explain why you made those changes.
An example of a good commit message is "Combine templates to reduce duplicate code in the user views." The words "change," "improve," "fix," and "refactor" don't add much information to a commit message. For more information, see Tim Pope's note about formatting commit messages.
To add more context to a commit message, consider adding information regarding the origin of the change. For example, the URL of a GitLab issue or a Jira issue number includes more information for users who need in-depth context about the change.
Properly escape special characters in XML generation.
Issue: gitlab.com/gitlab-org/gitlab/-/issues/1
Testing before merging
In old workflows, the CI (continuous integration) server commonly ran tests on the main branch only. Developers had to ensure their code did not break the main branch. When using GitLab flow, developers create their branches from this main branch, so it is essential that it never breaks. Therefore, each merge request must be tested before it is accepted. CI software like Travis CI and GitLab CI/CD shows the build results right in the merge request itself to simplify the process.
There is one drawback to testing merge requests: the CI server only tests the feature branch itself, not the merged result. Ideally, the server could also test the main branch after each change. However, retesting on every commit to main is computationally expensive and means you are more frequently waiting for test results. Because feature branches should be short-lived, testing just the branch is an acceptable risk. If new commits in main cause merge conflicts with the feature branch, merge main back into the branch to make the CI server re-run the tests. As said before, if you often have feature branches that last for more than a few days, you should make your issues smaller.
Working with feature branches
When creating a feature branch, always branch from an up-to-date main. If you know before you start that your work depends on another branch, you can also branch from there. If you need to merge in another branch after starting, explain the reason in the merge commit. If you have not pushed your commits to a shared location yet, you can also incorporate changes by rebasing on main or another feature branch. Do not merge in from upstream if your code can work and merge cleanly without doing so. Merging only when needed prevents creating merge commits in your feature branch that later litter the main history.
Branch descriptions
- feature
- All feature implementation starts in a feature branch. Feature branches branch off from the master branch and are merged back into it.
- master
- The role of the master branch in gitlab flow is the same as the develop branch in git flow. The master branch runs tests on the features merged from feature branches. Once overall testing is done and the features are verified, it is merged into the production branch.
- If you want a staging step, merge into the pre-production branch.
- production
- The role of the production branch in gitlab flow is the same as the master branch in git flow. It is the branch for deploying features that have finished testing.
- pre-production
- A branch placed between master → production, so that changes are not deployed straight to production but deployed to a test server for integration testing, or applied after some time.