Feature, bug-fix, hotfix, release workflows
Intermediate
Why this matters
Most work follows the same route, which is why lesson 13.1 is enough for the majority of days. The exceptions matter because they arrive under pressure: a hotfix at four in the afternoon is not the moment to work out where the branch should start.
The four flows
Feature
Starts from: current main. Ends at: main, through a merge request. Lives: days.
$ git switch main && git pull
$ git switch -c feature/21-difficulty-filter
# work, commit, push, merge requestThe only discipline that matters is size. A branch that lives two days and touches five files gets reviewed in twenty minutes; one that lives three weeks gets reviewed badly, or not at all. If a feature is genuinely large, split it into several merge requests that each work on their own.
Bug fix
Starts from: current main. Ends at: main. Lives: hours.
$ git switch -c fix/15-empty-distanceStructurally identical to a feature, with two differences in practice: the branch name says fix, and the merge request should say how to reproduce the bug and how you verified it is gone. That second part is what lets a reviewer, or a tester, confirm the fix rather than take your word for it.
Hotfix
Starts from: the released tag or release branch, not from main. Ends at: both the release and main. Lives: minutes to hours.
This is the one that is different, and the reason is simple: main has moved on since the release. Whatever is broken in production is broken in v1.2.0, and main may contain a dozen changes that are not ready to ship. So you start where production is:
$ git fetch --tags
$ git switch -c hotfix/23-wrong-region-name v1.2.0
# one small commit
$ git push -u origin hotfix/23-wrong-region-nameThen it has to reach two places:
- Into the release: merge request onto
release/1.2(or onto the branch your team ships from), then tagv1.2.1and release (lesson 13.6). - Into
main: a second merge request, or a cherry-pick (lesson 13.5). Skipping this is how a bug gets fixed in production and comes back in the next version.
That second step is forgotten often enough to be worth a checklist item in your team's hotfix process.
Release
Starts from: main, at the point a version is ready. Ends at: a tag, and possibly a long-lived branch. Lives: as long as the version is supported.
Two shapes, and which one your team uses decides how hotfixes work:
| Shape | How it works | Suits |
|---|---|---|
| Tag only | Tag main as v1.3.0 and ship it; the next release is the next tag |
Web services, continuous deployment |
| Release branch | Cut release/1.3 from main; only fixes go onto it, while main carries on with new work |
Software with supported older versions |
With a release branch, the rule that keeps it sane is: nothing new goes onto it. Every change on a release branch is a fix that also exists on main.
Which one am I doing?
| Question | Answer |
|---|---|
| Is production broken right now? | Hotfix |
| Does it fix behaviour that was supposed to work? | Bug fix |
| Does it add or change intended behaviour? | Feature |
| Am I packaging what already exists into a version? | Release |
If it is a hotfix and you are unsure where to branch from, ask before branching. It is the one case where starting in the wrong place costs real time.
How to do it
The only command that differs between the flows is the one that starts the branch:
$ git switch -c feature/21-difficulty-filter # from current main
$ git switch -c fix/15-empty-distance # from current main
$ git switch -c hotfix/23-wrong-region-name v1.2.0 # from the released tagFor the last one, git fetch --tags first; tags do not arrive with an ordinary fetch of branches unless they are on fetched history.
The branch picker's Create new branch from… lets you choose the starting point, which is what a hotfix needs. Otherwise every flow is the same set of buttons.
Git → New Branch creates from the current head; to start from a tag, find it in the Log, right-click, New Branch from Selected Commit. The log's tag markers make this the easiest place to do a hotfix correctly.
Creating a branch from a tag is available on the New branch page: choose the tag in Create from. Release branches are ordinary branches, usually protected (lesson 9.5), and the Cherry-pick button on a merged merge request is how a fix reaches the other branch.
The same: New branch offers a source, and a merged pull request can be cherry-picked locally onto the release branch. Rulesets usually protect both main and release/*.
Common mistakes
- Branching a hotfix from
main, which ships everything else onmainalong with the fix. - Fixing production and forgetting
main, so the bug returns in the next release. - Putting new features on a release branch, which turns it into a second
main. - A feature branch that lives three weeks. Split it.
- A bug-fix merge request that does not say how to reproduce the bug, leaving the reviewer unable to verify anything.
- Tagging before the pipeline is green. The tag is a promise about a state.
Try it yourself
Goal: do a hotfix properly, including both destinations.
- In your practice project, tag
mainasv1.2.0and push the tag. - Add two ordinary commits to
main, so it has moved on. - Create
hotfix/23-wrong-region-namefrom the tag, not frommain, and make a one-line fix. - Merge it into a
release/1.2branch cut from the same tag, and tagv1.2.1. - Get the same fix onto
main, by merge request or cherry-pick. - Check with
git log --oneline v1.2.0..v1.2.1that the release contains only your fix.
Expected result: a release containing exactly one change, and main containing the same fix, reached by two routes from one branch.
Show solution
Step 6 is the check that the hotfix was branched from the right place. If the range shows your two unrelated commits from step 2, the branch started from main and the release contains work that was never meant to ship. That is the entire reason hotfixes branch from the tag.