LearnDevOps
DevOps02. Version Control·EXPERT·6 min read

5. Git Workflows

Git Workflows

TL;DR

  • The right workflow is a function of deploy cadence, not personal preference — Git Flow suits infrequent, versioned releases; GitHub Flow and trunk-based development suit continuous deployment.
  • Trunk-based development requires feature flags — without them, incomplete work merged to a single main branch ships to users unfinished.
  • Monorepos simplify cross-service atomic changes but require path-based/affected-package CI filtering to avoid rebuilding everything on every commit.
  • Polyrepos isolate services cleanly but make coordinated multi-service changes slower (separate PRs, separate versioning, separate releases).
  • Semantic versioning (MAJOR.MINOR.PATCH) is a trust contract with consumers: patch and minor bumps must be safe to pull without reading the changelog.
  • Biggest gotcha: workflow mismatches and versioning mistakes compound weekly — they are cheap to fix early and expensive to fix once a team has built habits around them.

Core Concepts

Git Flow

Git Flow defines a fixed set of long-lived and supporting branches: develop (integration branch), feature/* (per-feature work branched from develop), release/* (stabilization before a version ships), hotfix/* (urgent production fixes branched from a tagged release, merged back to both main and develop), and main/master (always reflects production).

It was designed for software with discrete, versioned, infrequent releases — desktop applications, SDKs, anything with a “version 2.4 ships next month” cadence where customers upgrade on their own schedule. The branch layers exist to support parallel work on the current release and the next one simultaneously.

Applied to a continuously-deployed service, the extra branches add merge overhead and release-coordination steps with no corresponding benefit, since there is no discrete version boundary to manage.

GitHub Flow

A simplified model: short-lived feature branches off main, opened as a pull request, reviewed, merged to main, deployed. No develop branch, no release branches.

It depends on fast review turnaround to work as intended. When review is slow, feature branches stay open for days, drift from main, and accumulate merge conflicts by the time they are finally reviewed and merged — a review-speed/process problem (review SLAs, smaller PRs), not a defect in the workflow itself. Any workflow with long-lived branches has the same conflict-accumulation risk.

Trunk-Based Development

Small, frequent commits land directly on (or are merged within minutes/hours into) a single main branch, avoiding long-lived branches entirely.

This is only viable paired with feature flags: without a flag to hide incomplete functionality from users, every commit to main is effectively shipped to production immediately, half-finished or not. Feature flags decouple “merged to main” from “visible to users,” which is the mechanism that makes trunk-based development safe at high commit frequency.

Monorepo vs. Polyrepo

A monorepo stores multiple services/packages in a single repository; a polyrepo gives each service its own repository.

Monorepos make cross-cutting atomic changes straightforward — a shared library and every service that consumes it can be updated in one commit/PR. The cost is CI: naively rebuilding and retesting every package on every commit does not scale once the repository holds dozens of services. The fix is path-based/affected-package build filtering — detecting which packages actually changed and building/testing only those. Tools like Bazel, Nx, and Turborepo are built specifically around this incremental-build problem.

Polyrepos avoid the CI-scaling problem entirely (each repo’s pipeline only ever builds that repo) but make cross-service coordinated changes slower: each affected repo needs its own PR, its own versioning, and a coordinated release sequence.

Semantic Versioning

Semver format is MAJOR.MINOR.PATCH:

  • MAJOR — incompatible/breaking API changes.
  • MINOR — backward-compatible new functionality.
  • PATCH — backward-compatible bug fixes.

The value of semver is that consumers can trust a minor or patch bump is safe to adopt without reading the changelog first. A maintainer who ships a breaking change as a patch bump violates that contract and causes real downstream breakage the moment a consumer upgrades expecting compatibility.

Git Flow vs. GitHub Flow vs. Trunk-Based Development

Aspect Git Flow GitHub Flow Trunk-Based Development
Branch structure develop, feature, release, hotfix, main short-lived feature branches off main commits land directly on (or near-instantly into) main
Best fit Versioned software, infrequent releases Continuously deployed products with fast review High-frequency continuous deployment
Requires Release coordination, merge-order discipline Fast PR review turnaround Feature flags
Deploy cadence Weeks to months Multiple times a day feasible Many times a day
Main failure mode Coordination overhead for fast-shipping teams Stale branches from slow review Half-finished features shipped without flags

Monorepo vs. Polyrepo

Aspect Monorepo Polyrepo
Cross-service atomic changes Single commit/PR across services Separate PRs, coordinated release per repo
CI scaling Requires path-based/affected-package filtering Naturally scoped to one repo’s build
Tooling Bazel, Nx, Turborepo, etc. Standard per-repo CI
Versioning Often unified or simplified Independent per repo
Typical failure mode CI rebuilds everything on every commit Slow, uncoordinated multi-service changes

Command / Configuration Reference

git checkout -b feature/x develop     # Git Flow: branch a feature off develop
git checkout -b release/2.4.0 develop # Git Flow: cut a release branch for stabilization
git checkout -b hotfix/2.4.1 main     # Git Flow: branch a hotfix off the production tag
git checkout -b feature/y main         # GitHub Flow / trunk-based: short-lived branch off main
gh pr create --base main               # open a PR for review before merging to main
git tag v2.4.0 && git push origin v2.4.0  # tag a release per semver
# Example: path-filtered CI trigger in a monorepo (conceptual)
trigger:
  paths:
    include:
      - services/payments/**
jobs:
  - job: build_payments
    condition: eq(variables['changed_path'], 'services/payments')

Common Pitfalls

  • Pitfall: Adopting Git Flow for a continuously-deployed product. Why: the release/develop branch layers assume infrequent, versioned releases that don’t exist in a continuous-deployment cadence. Fix: use GitHub Flow or trunk-based development for products shipping multiple times a day; reserve Git Flow for genuinely versioned software.
  • Pitfall: Adopting trunk-based development without feature flags. Why: without a flag, every commit to main is immediately live, including unfinished work. Fix: pair trunk-based development with feature flagging from the start.
  • Pitfall: A monorepo’s CI rebuilding every package on every commit. Why: the pipeline has no mechanism to detect which package actually changed. Fix: adopt path-based/affected-package build filtering (Bazel, Nx, Turborepo, or custom path triggers).
  • Pitfall: Shipping a breaking change as a patch version bump. Why: mislabels the change’s impact, so consumers upgrade expecting backward compatibility and get broken. Fix: follow semver strictly — any breaking change requires a major version bump.
  • Pitfall: Long-lived PRs under GitHub Flow causing recurring merge conflicts. Why: slow review, not the workflow itself, is the root cause of branch staleness. Fix: enforce review SLAs and keep PRs small rather than switching workflows.

Interview Questions

  • “A team deploying 10 times a day is using Git Flow and struggling. What do you recommend, and why?” — Tests whether the deploy-cadence-to-workflow mismatch is correctly diagnosed.
  • “Explain why trunk-based development and feature flags are almost always adopted together.” — Tests understanding of the dependency between the two practices.
  • “A monorepo’s CI takes 40 minutes on every commit regardless of what changed. What’s your fix, and why not just split the repo?” — Tests whether path-based build filtering is considered before a larger repo-architecture change.
  • “What’s the real trade-off between a monorepo and a polyrepo?” — Tests whether the candidate can articulate both directions of the trade-off, not just one side’s benefits.
  • “A library bumps from 2.4.1 to 3.0.0. What should consumers assume without reading the changelog?” — Tests semver comprehension.
🔒 Locked
Sign in and unlock the full syllabus to keep reading.
Sign in to continue
Knowledge check
6 scenario questions on this topic
Take the quiz →
Related in 02. Version Control
4. Git Fundamentals
EXPERT