subuilds.dev
Git Branching Strategy: A Practical Case

Git Branching Strategy: A Practical Case

· 10 min read
gitlab-for-your-team

Broken code in production on a Friday night. Merge conflicts that should’ve been caught days ago. Familiar?

Most Git workflow guides target massive teams with versioned software. For modern web and SaaS — small team or large — you need something simpler.

This guide covers GitHub Flow / trunk-based development: one long-lived branch (main), short-lived feature/* branches tested on preview deployments, and hotfix/* for production emergencies. The same pattern that scales from 3 devs to 300 at Google, Shopify, GitHub, and Netflix.

Why Bother With Git Strategy?

No structure means people push straight to main, merge conflicts pile up, and nobody knows what’s deployed where.

A simple workflow fixes that:

  • No Friday disasters — branch protection catches mistakes before prod
  • Code gets reviewed — required, not optional
  • Bugs caught before production — preview deployments per PR
  • New devs onboard fast — they know where to push on day one
The Real Goal

A good Git strategy isn’t about following rules perfectly — it’s about making it harder to accidentally break production while keeping deployment fast.

The Branching Model

One long-lived branch, two temporary ones:

BranchPurposeLifetimeProtected
mainProduction. Always deployable.Permanent
feature/*New features and changesTemporary
hotfix/*Emergency production fixesTemporary

The key idea: every PR gets a preview deployment. Testing happens on the feature branch’s preview URL, not on a separate long-lived “staging” branch. When the preview passes, the feature merges to main and ships.

Diagram

Walkthrough: Two Devs, Parallel Features

Sarah builds auth. Mike builds the dashboard. Two features, no drama.

Phase 1: Project Setup (one-time)

git init
git commit -m "chore: initial commit"
git push -u origin main

Protect main in GitHub/GitLab settings (covered below).

Phase 2: Sarah Builds Auth

git checkout main && git pull
git checkout -b feature/user-auth

# Work, commit small, push often
git add src/auth/
git commit -m "feat(auth): add login form + JWT"
git push -u origin feature/user-auth

Sarah opens a PR: feature/user-auth → main. CI runs tests and deploys a preview URL (Vercel, Cloudflare Pages, Netlify, ArgoCD, or whatever your stack provides). Mike reviews the code; QA or Sarah herself clicks through the preview to verify the auth flow works. When everything passes:

# Squash-merge via the PR UI, then tag from main:
git checkout main && git pull
git tag -a v1.1.0 -m "Release v1.1.0: auth"
git push origin v1.1.0

Auto-deploy to production picks up the tag (or the merge to main, depending on your CI). Branch deleted.

Phase 3: Mike Builds Dashboard (in parallel)

git checkout main && git pull   # picks up Sarah's auth
git checkout -b feature/dashboard

# ...work, commit, push, PR → main

Mike develops against the latest main (with Sarah’s auth already shipped). His PR gets its own preview URL. Same flow: review, preview-test, squash-merge, tag.

Phase 4: Production Bug at 11pm

A bug in payment processing is breaking checkouts. You can’t wait for a normal release.

git checkout main && git pull
git checkout -b hotfix/payment-timeout

# Fix, commit, push
git commit -m "fix(payment): increase API timeout to 30s"
git push -u origin hotfix/payment-timeout

# PR: hotfix/payment-timeout → main (fast-track review)
# After merge, tag the patch:
git checkout main && git pull
git tag -a v1.1.1 -m "Hotfix: payment timeout"
git push origin v1.1.1

That’s it. No second branch to sync back into. The hotfix is on main, and main is the only source of truth.

Branch Protection

Set this once in GitHub/GitLab. One protected branch:

main

  • 1 approval required
  • CI must pass (build + tests + security scan)
  • Preview deployment must succeed (status check)
  • No force push

Skip signed commits, conversation resolution, branch deletion protection. Noise at this scale.

Accidentally Committed to Main?

If you forgot to create a feature branch and committed straight to main (we’ve all done it), see how to fix accidental commits to main — it walks you through moving your commits to a feature branch without losing work.

Naming Conventions

Branches

<type>/<short-description> — kebab-case, 2–4 words.

✅ Good❌ Bad
feature/user-authfix-stuff
feature/payment-integrationjohns-branch
bugfix/login-redirect-looptemp
hotfix/payment-timeoutwip

Rules: lowercase, kebab-case, no special chars except / and -. Include an issue number when you have one (feature/123-user-dashboard).

Commits

Three types cover 95% of what you’ll write:

feat: add JWT refresh mechanism
fix: resolve timeout in user API
chore: update dependencies

That’s it. docs, refactor, test, ci exist for when you need them — but don’t make the team memorize the full list on day one.

Tags

Semver on main: vMAJOR.MINOR.PATCH.

  • v1.0.0 — initial release
  • v1.1.0 — new feature (backward compatible)
  • v1.1.1 — bug fix
  • v2.0.0 — breaking change
git tag -a v1.2.0 -m "Release v1.2.0: dashboard"
git push origin v1.2.0

Pull Requests

Title

<type>: <what you did>, 50–72 characters.

feat: add user notification systemfix: resolve login redirect loop on expired tokensUpdate stuff / WIP / Final fix v2

Description (keep it short)

Three things:

  • What changed — one paragraph
  • Why — link the issue or explain the user-facing impact
  • How to test — concrete steps the reviewer can run on the preview URL

Skip the 7-section corporate template. If the PR is small enough that those three things fit in five sentences, it’s a good PR.

Reviewer checklist

  • Follows team conventions
  • No obvious bugs or security issues
  • Tests cover the change
  • Preview deployment verified
  • No debug code or commented-out blocks left

Merge strategy

Squash and merge for everything. One PR = one commit on main. Clean linear history, easy revert, easy git bisect.

Handling Merge Conflicts

git checkout feature/user-profile
git merge main
# CONFLICT (content): Merge conflict in src/components/Profile.tsx

Open the file, resolve the conflict markers:

<<<<<<< HEAD
your changes
=======
changes from main
>>>>>>> main

Then:

git add src/components/Profile.tsx
git commit -m "merge: resolve conflicts with main"
git push

If the conflict is in code you didn’t write, pair with whoever did. Don’t guess.

Automating Quality Checks with Git Hooks

Git hooks catch bad code before it reaches the repo: failing tests, bad commit messages, hardcoded secrets.

Complete Git Hooks Guide

For setup with Husky and CI/CD integration:

Git Hooks for Automation: Catch Mistakes Before They Embarrass You → — theory and patterns

Implementing Git Hooks with Husky: A Real-World Example → — practical setup

5 minutes to set up. Saves hours of debugging.

Branch Lifecycle

How a feature flows from creation to deletion:

Diagram
Branch typeLifespan
feature/*1–7 days
bugfix/*1–3 days
hotfix/*2–4 hours
mainpermanent

If a feature branch is over a week old, expect painful reviews and stale conflicts. Over two weeks, break the feature down.

Troubleshooting

”I committed to wrong branch"

# You're on main but should be on a feature branch.
git checkout -b feature/my-feature   # carries your commits
git checkout main
git reset --hard origin/main         # rewinds main to remote
git checkout feature/my-feature      # your work is here, safe

"I need to undo my last commit"

git reset --soft HEAD~1   # undo commit, keep changes staged
git reset --hard HEAD~1   # undo commit, discard changes
git revert HEAD           # if already pushed — creates a new "undo" commit

"My feature branch is behind main”

git checkout feature/my-feature
git fetch origin
git merge origin/main

Cheat Sheet

# Start a feature
git checkout main && git pull
git checkout -b feature/name

# Sync with main mid-flight
git checkout main && git pull && git checkout - && git merge main

# Finish (after PR is squash-merged)
git checkout main && git pull
git branch -d feature/name

# Hotfix
git checkout main && git pull
git checkout -b hotfix/name
# ...fix, push, PR to main, merge, tag
BranchApprovalsAuto-deploys to
main1Production
feature/*Preview URL (per PR)
hotfix/*1 (fast-track)Production (after merge)

Scaling This Workflow

This same pattern runs from 3 devs to 300. Two enablers you add as the team grows:

  1. Preview deployments per PR — every PR gets its own ephemeral URL. Preview testing becomes the gate for merging to main. Tools: Vercel, Cloudflare Pages, Netlify, AWS Amplify, ArgoCD preview environments, GitLab Review Apps.

  2. Feature flags — merge code behind a flag, ship to production immediately, flip the flag when the feature is ready for users. This decouples deployment (continuous) from release (controlled). It’s how big orgs deploy hundreds of times a day without breaking anything. Tools: LaunchDarkly, Unleash, GrowthBook, Flagsmith, or a homegrown if (flags.X) table.

You don’t need either on day one. Add them when:

  • You’re tired of waiting on a shared dev environment → add preview-per-PR
  • You want to merge half-built features without exposing them → add feature flags

Branch protection, squash merges, and a clean linear main history work at every scale. The infrastructure around them is what changes.

The Bottom Line

This workflow gives you:

  • No Friday disasters — branch protection blocks accidents
  • Code reviews that happen — required, not optional
  • Bugs caught before production — preview-per-PR is your safety net
  • A linear main historygit bisect, revert, and audit are trivial
  • A workflow that scales — same pattern at 3 devs and 300

Protect main this week. Add preview deployments next sprint. Add feature flags when you outgrow the dev environment. Don’t bolt on develop and staging branches — they’ll create more friction than they solve.

There Is No Fixed Git Workflow

This isn’t a contract. Every team hits friction the docs didn’t predict — slow reviews, accidental conflicts, a deploy step that should’ve been automated. When that happens, raise it: in standup, in a quick async note, in a retro. Then change the workflow. The goal is shipping safely with the least ceremony, not following these rules to the letter.

Additional Resources