Skip to main content
Published / updated

Team Workflows

Before you start

You need: pull requests (Article 05).

Time: about 45 minutes.

Learning objective

Understand the common branching workflows, know which one a team is using from its branch list, and follow it correctly.

Topics

  • Why a workflow is needed
  • GitHub Flow
  • Git Flow
  • Trunk-based development
  • Choosing between them
  • Hotfixes
  • Tags and releases
  • Working within someone else's workflow

Why a workflow is needed

With one developer, any approach works. With six, unstated rules produce:

  • Commits going straight to main
  • Nobody knowing which branch is deployed
  • A hotfix that cannot be applied because the release branch has moved on
  • Two people rewriting the same file
  • A release containing an unfinished feature

A workflow is an agreement about where work starts, where it merges and what is deployable. The specific workflow matters far less than everyone following the same one.

GitHub Flow

The simplest workflow that works for a team.

main ─────●───────●───────●───────●──── (always deployable)
\ / \ /
●───● ●───●
feature/search bugfix/fee-rounding

Rules:

  1. main is always deployable.
  2. Branch from main for anything.
  3. Push and open a pull request.
  4. Review and CI.
  5. Merge to main.
  6. Deploy.
AdvantageCost
Easy to explainNo place for a long stabilisation period
One long-lived branchRequires strong automated tests
Fast to productionRequires the ability to deploy on demand

Suits continuous deployment, web applications, and small-to-medium teams. It is the default for most modern projects and the right starting point unless something specific rules it out.

It depends entirely on main staying deployable, which is why branch protection and CI are not optional here.

Git Flow

Older, more structured, built around scheduled releases.

main ─────●───────────────●────── (production, tagged)
\ /
release ─●───●───────●
/ \
develop ────●───●───●───●───●───●──── (integration)
\ / \ /
●─● ●─●
feature/x feature/y
BranchPurposeLifetime
mainProduction; every commit is a tagged releasePermanent
developIntegration of finished featuresPermanent
feature/*One feature; branches from and merges to developDays to weeks
release/*Stabilisation; bug fixes only, no new featuresDays
hotfix/*Urgent production fix; branches from mainHours

A release:

git switch -c release/2.4.0 develop
# only bug fixes here
git switch main
git merge --no-ff release/2.4.0
git tag -a v2.4.0 -m "Release 2.4.0"
git switch develop
git merge --no-ff release/2.4.0 # fixes go back to develop
git branch -d release/2.4.0

The merge back into develop is the step teams forget, and the result is a bug fixed during the release reappearing in the next one.

AdvantageCost
Explicit stabilisation windowMany long-lived branches
Parallel release and developmentFrequent, large merges
Clear hotfix pathSlow; complex to explain

Suits versioned or on-premise software — installed products, mobile apps with store review, anything with a scheduled release. It is heavy for a continuously deployed web application, and using it there is a common mistake.

Trunk-based development

Everyone commits to main, with branches lasting hours.

main ──●──●──●──●──●──●──●──●──●──●── (many small commits daily)

Rules:

  1. Branches live less than a day, often less than an hour.
  2. Merge to main at least daily.
  3. Incomplete work hides behind a feature flag.
  4. Comprehensive automated tests.
  5. Deployment is automated.
if (_featureFlags.IsEnabled("StudentSearch", schoolId))
{
return await _searchService.SearchStudentsAsync(schoolId, query);
}

return await _studentService.GetAllStudentsAsync(schoolId);

Feature flags are what make this possible. Code ships to production disabled, gets enabled for one school, then for everyone. Deployment and release become separate events — and turning a feature off is a flag change, not a rollback.

AdvantageCost
Almost no merge conflictsRequires excellent test coverage
Continuous integration in the literal senseRequires flag infrastructure
Fast feedbackRequires discipline; unmerged work is the failure mode

Suits mature teams with strong automation. Adopted without tests it is simply "everyone commits to main", which is worse than any alternative.

Choosing

SituationWorkflow
Small team, continuous deploymentGitHub Flow
Scheduled releases, multiple versions supportedGit Flow
Mature team, strong tests, feature flagsTrunk-based
Learning, or a personal projectGitHub Flow
Regulated release processGit Flow

Start with GitHub Flow. Move to Git Flow if you genuinely need release branches, or trunk-based when your automation can support it. Choosing Git Flow because it looks professional is the most common wrong answer.

The workflows are also not exclusive — many teams run GitHub Flow with a release/* branch when a specific release needs stabilising.

Hotfixes

Production is broken; a fix must ship without shipping whatever else is on develop.

git switch -c hotfix/login-500 main # branch from what is in production
# fix, test
git switch main
git merge --no-ff hotfix/login-500
git tag -a v2.3.1 -m "Hotfix: login returns 500 for staff accounts"
git push origin main --tags
# deploy
git switch develop
git merge --no-ff hotfix/login-500 # do not skip this
git branch -d hotfix/login-500

Branch from main, not develop. Branching from develop drags in every unreleased change, so a one-line fix becomes an unplanned release.

Merge back into develop. Otherwise the next release reintroduces the bug you just fixed — a genuinely common and confusing failure.

Under pressure, both rules are the ones that get skipped. Write them into the runbook.

Tags and releases

git tag -a v2.4.0 -m "Release 2.4.0"
git tag -a v2.4.0 a3f9c1d -m "..." # tag an older commit
git push origin v2.4.0
git push origin --tags
git tag -l "v2.4.*"
git show v2.4.0
git checkout v2.4.0 # detached HEAD; expected

Use annotated tags (-a), which store the tagger, date and message as real objects. Lightweight tags are just a name on a hash and carry no record of who released what.

Tags are not pushed by git push. They need --tags or an explicit push — a frequent surprise.

Semantic versioning:

v2.4.1
│ │ │
│ │ └── PATCH — backwards-compatible fix
│ └──── MINOR — backwards-compatible feature
└────── MAJOR — breaking change

A tag is how you answer "what exactly is in production?" Without tags, that question requires archaeology through commit dates and deployment logs.

Working within someone else's workflow

Joining a team, read the branches first:

git branch -a
git log --oneline --graph --all -30
git tag -l
What you seeLikely workflow
Only main plus short-lived branchesGitHub Flow
main and develop both activeGit Flow
release/* branchesGit Flow
Almost everything on main, many small commitsTrunk-based
Tags like v2.4.0Versioned releases

Then ask four questions:

  1. Which branch do I branch from?
  2. Which branch do I target in a pull request?
  3. What is deployed to production right now?
  4. How is a hotfix handled?

Read CONTRIBUTING.md before asking — most teams have written this down.

Follow the existing workflow even if you prefer another. A team half on one workflow and half on another is worse than either.

Diagnosing

SymptomCause
A fixed bug reappears next releaseHotfix or release branch never merged back
A hotfix pulls in unrelated featuresBranched from develop instead of main
Nobody knows what is in productionNo tags
Constant conflictsLong-lived branches — merge from main more often
An unfinished feature shippedNo release branch and no feature flag
Tags missing on the remotePushed without --tags

Errors you will hit

What you seeCauseFix
A fixed bug reappears next releaseHotfix never merged backMerge it into the development branch
A hotfix drags in unreleased workBranched from the wrong placeBranch from what is in production
Nobody knows what is in productionNo tagsTag every release
Constant conflictsLong-lived branchesMerge from the main branch daily
Tags missing on the remotePushed without --tagsgit push origin --tags

Both hotfix rules get skipped under pressure, which is exactly when they matter. Write them into the runbook.

Common mistakes

  • Adopting Git Flow for a continuously deployed web application
  • Trunk-based development without test coverage
  • Hotfix branched from develop
  • Not merging a hotfix or release branch back
  • Lightweight tags instead of annotated
  • Forgetting to push tags
  • Feature branches alive for weeks
  • Mixing workflows within one team
  • Committing directly to main where the workflow forbids it

Practice

The course exercise is follow a branching workflow.

  1. Set up a repository and run GitHub Flow: two feature branches, two PRs, both merged to main.
  2. Set up a second repository with main and develop. Run a full Git Flow release, including the merge back into develop.
  3. Deliberately skip the merge back into develop, then start the next release and find the reintroduced bug.
  4. Branch a hotfix from develop and observe what it drags in. Redo it from main.
  5. Tag a release with -a, push it with --tags, and confirm it appears on GitHub.
  6. Create a lightweight tag and compare git show output with the annotated one.
  7. Check out a tag and note the detached HEAD message.
  8. Implement a feature flag around an unfinished feature and merge it to main disabled.
  9. Simulate trunk-based development: three commits a day for three days, all to main, nothing living longer than a day.
  10. Open an unfamiliar public repository and identify its workflow from git branch -a and the graph.
  11. Write a CONTRIBUTING.md answering the four questions for your own project.
  12. Keep a branch unmerged for a week while main moves, then merge it. Note the conflict count.

You can now

  • Identify a team's workflow from its branches
  • Follow it correctly, including for a hotfix
  • Branch a hotfix from production, and merge it back
  • Tag releases and push the tags
  • Choose a workflow that fits the team

Review questions

  1. What does GitHub Flow require in order to be safe?
  2. Why must a hotfix branch from main rather than develop?
  3. What makes trunk-based development possible without shipping unfinished features?
  4. Why are annotated tags preferred, and what stops them reaching the remote?

Next: Intermediate Git