What is the best git workflow for DevOps teams? The most popular workflow for git is still git-flow. I did a poll on Twitch with a friend some time ago and still over 50% of the audience said they were using it. And this is strange, because even Vincent Driessen, the author of the original post about git-flow, wrote a note in March 2020 in the post that git-flow is not the best workflow for cloud services, apps, or web applications. So why is it still so popular? I think there are three reasons:
- It’s very detailed, specific, and it provides guidance for people that are new to git.
- It has a great name that is good to memorize.
- It has a good visualisation
And what’s the problem with GitHub Flow? Well, the name sounds too close to GitFlow and the description of the workflow is not very precise. How do you deploy? What do you do with long-lived feature branches? How can you support different versions for different customers?
I thinks what we need is a good new workflow with a good name and a nice chart! So let me introduce you to…
The best git workflow for DevOps teams: MyFlow
MyFlow is a lightweight, trunk-based workflow based on pull-requests. It is not a new invention! Many teams already work this way. It is a very natural way to branch and merge if you focus on collaboration with pull-request. I just try to give it a name and add some explanations for engineers that are new to git. Here is an overview:
This is part 1 of a series of posts that describe MyFlow: a collection of best practices on how to set up a successful branching model for GitHub.
Part 1 will cover the main branch and how to work with private topic branches. In part 2 I’ll explain release branches and semantic versioning. In part 3 I’ll show you how you can automate parts of the flow using git aliases.
The main branch
MyFlow is trunk-based – that means there is only one main branch called
main branch should always be in a clean state in which a new release can be created at any time. That’s why you should protect your
main branch with a branch protection rule. A good branch protection rule would include:
- Require a minimum of two pull request reviews before merging.
- Dismiss stale pull request approvals when new commits are pushed.
- Require reviews from Code Owners.
- Require status checks to pass before merging that includes your CI build, test execution, code analysis, and linters.
- Include administrators in the restrictions.
- Permit force pushes.
The more you automate using the the workflow that is triggered by your pull request, the more likely it is that you can keep your branch in a clean state.
All other branches are always branched of
main. Since this is your default branch, you never have to specify a source branch when you create new branches. This simplifies things and removes a source of error.
Private topic branches
Private topic branches can be used to work on new features, documentation, bugs, infrastructure, and everything else that is in your repository. And, They are private – so they belong to one specific user. This mOther team members can check out the branch to test it – but they are not allowed to directly push changes to this branch. Instead they must use suggestions in pull requests to suggest changes to the author of the pull request.
To indicate that the branches are private, I recommend a naming convention like
private/* that makes this obvious. Don’t use naming conventions like
features/* – they imply that multiple developers may work on one feature. I also recommend including the
id of the issue or bug in the name. This makes it easy to reference it later in the commit message. A good convention would be:
Private topic branches are low-complexity branches. If you work on a more complex feature, you should at least merge your changes back to main and delete the topic branch once a day using feature flags (aka feature toggles). If the changes are simple and can be easily rebased onto main, you can leave the branch open for a longer time. The branches are low-complexity and not short-lived.
Start work on a new topic
So, to start working on a new topic you perform three steps:
- Create a new branch
- Commit and push a small change
- Create a pull request in draft mode
Create a new branch
To create a new branch locally you use
$ git switch -c <branch-name>
$ git switch -c users/kaufm/42_my-new-feature main > Switched to a new branch 'users/kaufm/42_my-new-feature'
Commit and push a small change
Create your first modifications and commit and push them to the server. It does not matter what you modify – you could just add a blank line to a file. You can overwrite the change later anyway.
push the change:
$ git add . $ git commit $ git push --set-upstream origin <branch-name>
$ git add . $ git commit $ git push --set-upstream origin users/kaufm/42_my-new-feature
Note that I did not specify the
-m parameter in
git commit to specify the commit message. I prefer that my default editor opens the
COMMIT_EDITMSG so that I can edit the commit message there. This way I can see the files with changes and I have a visual help where the lines should break. Make sure you have set your editor correct if you want to do this. If, for example, you prefer visual studio code, you can set it as your default editor with:
$ git config --global core.editor "code --wait"
Create a pull request in draft mode
Now you create a pull request in draft mode. This way the team knows that you are working on that specific topic. A quick view on the list of open pull requests should give you a nice overview on the topics the team is currently working on.
Note that I use the <a href="https://cli.github.com/" target="_blank" rel="noreferrer noopener">GitHub CLI</a> to interact with pull requests as I find it easier to read and understand than to use screenshots of the web UI. You can do the same using the web UI.
$ gh pr create --fill --draft
--fill will automatically set the title and description of the pr from your commit. If you have omitted the
-m argument when committing your changes and if you have added a multi-line commit message in your default editor, the first line will be the title of the pull request and the rest of the message the body. You could also set title (
-t) and body (
-b) directly when creating the pull request instead of using the
Working on your topic
You can now start working on your topic. And you can use the full power of git. If you want to add changes to your previous commit, for example, you can do so with the
$ git commit --amend
Or, if you want to combine the last three commits into one single commit:
$ git reset --soft HEAD~3 $ git commit
If you want to merge all the commits in the branch into one commit you could run the following command:
$ git reset --soft main $ git commit
If you want complete freedom to rearrange and squash all your commits you can use interactive rebase:
$ git rebase -i main
To push the changes to the server you use the following command:
$ git push origin +<branch>
In our example:
$ git push origin +users/kaufm/42_my-new-feature
The plus before the branch name causes a force push to the specific branch only. If you are not messing with your branch history, you can perform a normal
git push without specifying origin and the branch name. If your branches are well protected and you know what you are doing a normal force push might be more convenient:
$ git push -f
In case you want help or opinions of your teammates on your code at that stage, you can mention them in comments in the pull request. If they want to propose changes, they use the suggestions feature in pull request comments. This way you apply the changes, and you can make sure that you have a clean state in your repository before doing so.
Finish your topic
Whenever you feel your work is ready, you change the state of your pull request from draft to ready and activate auto-merge:
$ gh pr ready $ gh pr merge --auto --delete-branch --rebase
--rebase here as the merge method. This is a good merge strategy for small teams that like to craft a good and concise commit history but still want it to be linear. If you prefer
--merge adjust your merge strategy accordingly. Squash is good for bigger teams or teams that are not used to craft very concise commit messages. Merge is only good for small teams with only short-lived branches that want to keep their branches visible in the history.
Your reviewers can still create suggestions in their comments, and you can keep collaborating. But once all approvals and all automated checks have completed, the pull request will be merged automatically, and the branch gets deleted. The automated checks run on the
pull_request trigger and can include installing the application in an isolated environment and running all sorts of tests.
If your pull request has been merged and the branch has been deleted, you clean up your local environment:
$ git switch main $ git pull --prune
This will change your current branch to main, pull the changes from the server, and delete the local branches that has been deleted on the server.
The perfect git workflow for your team depends on many things:
- The number of developers working on one product
- The complexity of the product
- If you use a mono repo approach or if you use multiple repos per product
- The experience with git your developers have
- Your git service (GitHub, GitLab, Bitbucket,…)
- How you release your product
- And so on
There is no one-size-fits-all solution. But, teams need some kind of guidance, either if they are new to git or change the platform. Or, if they have an old workflow that they have to adopt to a new release process or git system.
In this post I showed you the basics of MyFlow: a collection of best practices to work in a trunk-based workflow with git (in this case GitHub). In the next part I will cover releases, hotfixes, and semantic versioning.