Skip to content
All posts
Tech7 min read

5 Git Workflows That Will Make Your Team More Productive

J
Jamith Nimantha
March 21, 2026
Terminal showing git log with colorful branch graph
Photo by Unsplash
On this page

Most teams do not need Git Flow. Short-lived branches off main, squash merges, and branch protection cover almost everything, and the elaborate branching models exist for a release cadence that shipping continuously makes irrelevant.

Five practices below, in the order they pay off. None of them require the team to agree on anything philosophical.

1. GitHub Flow (For Most Teams)

One main branch. All work in short-lived feature branches. Merge via pull request.

bash
git checkout -b feature/user-authentication
# ... work ...
git push origin feature/user-authentication
# Open a PR → review → merge → delete branch

Rules:

  • main is always deployable
  • Branches live for days, not weeks
  • Every merge goes through a PR with at least one reviewer
  • Delete branches after merge

[!TIP] Enforce "Require branches to be up to date before merging" in GitHub branch protection settings. This prevents the class of bugs where a feature branch doesn't have recent changes from main.

2. Conventional Commits

Structure your commit messages. Unlocks automated changelogs and semantic versioning.

bash
feat: add QR code WiFi mode support
fix: correct JSON parser crash on empty arrays
docs: update README with environment variables
chore: upgrade next.js to 16.0.10
perf: lazy-load tool components on route change
refactor: extract heading parser to server utility

Format: type(optional-scope): description

Types: feat, fix, docs, style, refactor, perf, test, chore, ci

Why it matters: tools like semantic-release and standard-version read commit messages and automatically bump version numbers and generate CHANGELOG.md entries.

bash
npm install -g commitizen
# Then use 'git cz' instead of 'git commit' for a guided prompt

3. Squash Merging for Clean History

Instead of merging every commit from a feature branch, squash them into one:

bash
# In GitHub: "Squash and merge" button
# On CLI:
git checkout main
git merge --squash feature/my-feature
git commit -m "feat: add email signature templates"

Before squash:

text
* wip
* trying again
* fix typo
* oops
* working now

After squash:

text
* feat: add email signature templates

[!NOTE] Squash merging means you lose granular commit history on the feature branch. Keep branches short so this isn't a problem; if a branch has 40 commits, something went wrong earlier.

4. Interactive Rebase Before PR

Clean up your commits before asking for review:

bash
git rebase -i HEAD~5  # Interactively edit last 5 commits

In the editor:

text
pick a1b2c3 feat: add email template base
squash d4e5f6 fix typo in template
squash g7h8i9 wip
pick j0k1l2 feat: add template preview

squash merges that commit into the previous one. The result: a clean, logical history that's easy to review and bisect.

5. Branch Protection Rules

Set these in GitHub → Settings → Branches → Branch protection rules for main:

text
✅ Require a pull request before merging
✅ Require approvals: 1
✅ Dismiss stale pull request approvals when new commits are pushed
✅ Require status checks to pass before merging
    → Your CI pipeline: lint, typecheck, test
✅ Require branches to be up to date before merging
✅ Restrict who can push to matching branches
✅ Do not allow bypassing the above settings

This prevents accidental direct pushes to main and ensures every change is reviewed and CI-green before landing.

Why not Git Flow

Git Flow gives you main, develop, feature/*, release/* and hotfix/*. It was designed in 2010 for software with versioned releases shipped to users who install them, and for that it works well.

It fits continuous deployment badly. develop becomes a second integration branch that drifts from main, every change merges twice, and release branches spend days accumulating conflicts. The overhead buys you the ability to stage a release, which a team deploying on merge does not need.

Use Git Flow if you ship versioned artefacts: a mobile app going through review, a library with supported major versions, on-premise software. Use GitHub Flow if deploying is a merge.

GitHub FlowGit Flow
Long-lived branchesmainmain and develop
Merges per changeonetwo or more
Suitscontinuous deploymentversioned releases
Release stagingfeature flagsrelease branches
Overheadalmost nonesignificant

The middle path most teams actually land on: GitHub Flow, plus feature flags for anything that needs to merge before it is ready to be seen. That gets you the ability to stage a release without a second branch to maintain.

Trunk-based development, and the branch lifetime that matters

The most valuable variable is not the branching model, it is how long a branch lives. A branch open for two hours conflicts with nothing. A branch open for two weeks conflicts with everything, and the conflict resolution is where bugs get introduced.

Trunk-based development takes this to its conclusion: branches measured in hours, merged behind a flag if incomplete. The practices in this post support that, and the flow chosen matters far less than keeping branches short.

If your team argues about branching models, the productive question is usually not which model, it is why changes take a fortnight to merge.

The recovery commands worth knowing before you need them

Git makes almost everything reversible, and knowing that changes how confidently people work. These four cover the situations that cause panic.

You committed to the wrong branch. Move the commit without losing it:

bash
git reset HEAD~1 --soft     # undo the commit, keep the changes staged
git stash
git switch correct-branch
git stash pop
git commit -m "feat: the thing"

You force-pushed over someone's work, or lost a branch in a rebase. The reflog records where every branch pointer has been, for ninety days by default:

bash
git reflog                  # find the sha the branch used to point at
git reset --hard abc1234

Almost nothing committed is genuinely lost. The exception is uncommitted work destroyed by git checkout . or git reset --hard, which the reflog cannot help with, because it was never committed.

You need one commit from another branch, not the whole thing:

bash
git cherry-pick abc1234

You want to undo a commit that is already pushed. Do not rewrite shared history, add an inverse commit instead:

bash
git revert abc1234

revert is the safe one on any branch other people have pulled. reset is for history that is still yours alone.

Where these practices actually conflict

Worth being honest that the five above are not independent, and two combinations bite.

Squash merging destroys the individual commits, which makes the conventional-commit messages on your feature branch irrelevant. Only the squash message survives, so that is the one that has to follow the convention. Some teams write careful atomic commits and then squash them all away, which is wasted effort.

Interactive rebase after pushing means a force push. Use --force-with-lease rather than --force, because it refuses when someone else has pushed to the branch since you last fetched:

bash
git push --force-with-lease

Plain --force overwrites their work without asking. The longer flag is worth the typing, and worth aliasing.

Putting It Together

A workflow that scales:

bash
# Start work
git checkout main && git pull
git checkout -b feat/new-feature

# Work in small logical commits
git add -p  # Add hunks interactively, not entire files
git commit -m "feat: initial structure for new feature"

# Before opening PR
git rebase -i HEAD~4  # Clean up commits
git push origin feat/new-feature

# After review
# Use "Squash and merge" on GitHub
# Delete the branch

The goal is a main branch history that reads like a table of contents: each entry is a meaningful unit of work, described in plain English, that any team member can understand.

None of this works if reviews sit for days. The single highest-leverage change most teams can make is not a branching model, it is agreeing that reviewing an open pull request comes before starting new work.

Tools in this post

Related Tool

Focus Pomodoro Timer

Boost your productivity with a visual Pomodoro timer and relaxing ambient soundscapes. Keep focused and track work cycles.

Try it free
J

Written by

Jamith Nimantha

Software developer. Builds the DebuggerMe tools and writes about the things he runs into shipping them.

Share this post

Back to all posts

Related Articles

All articles →