Git Workflows: From Solo to Team (2026)
Git workflow is about more than commands — it's about how you organize your work. Here are the workflows that scale from solo to team.
The Solo Developer Workflow
# When it's just you, keep it simple:
main branch = your production code
feature branches = work in progress
# Typical day:
git checkout main
git pull origin main # Get latest
git checkout -b new-feature # Start working
# ... make commits ...
git checkout main
git merge new-feature # Merge back
git push origin main # Ship it
git branch -d new-feature # Clean up
# Commit often, push when ready:
git add -A && git commit -m "WIP: login form styling"
git add -A && git commit -m "WIP: validation logic"
git add -A && git commit -m "feat: complete login flow"
# Push only the final version (or squash merge)
Feature Branch Workflow (Small Teams)
Main principle: main branch should always be deployable.
Never commit directly to main. Always work on branches.
Branch naming convention:
- feature/user-authentication
- bugfix/login-crash-on-safari
- hotfix/payment-expiry-check
- refactor/users-api-typescript
The flow:
1. Create branch from main
2. Work on your feature (commit as much as you want)
3. Push your branch to remote
4. Open a Pull Request (PR) / Merge Request (MR)
5. Code review + CI checks pass
6. Merge to main
7. Delete the branch
# Step 1: Start from updated main
git checkout main
git pull origin main
git checkout -b feature/oauth-login
# Step 2: Work and commit
git add .
git commit -m "feat: add OAuth2 Google login"
# Step 3: Push branch to remote
git push -u origin feature/oauth-login
# Step 4: Open PR (on GitHub/GitLab/Bitbucket)
# Now teammates review your code
# Step 5: After review, address feedback
git add . && git commit -m "fix: address review comments"
git push origin feature/oauth-login
# Step 6: Maintainer merges PR (or you merge if team allows)
# Step 7: Clean up locally
git checkout main
git pull origin main
git branch -d feature/oauth-login # Delete local branch
git push origin --delete feature/oauth-login # Delete remote branch
GitFlow (Release-Based Projects)
For projects with scheduled releases (not continuous deployment):
main (production-ready)
/ \
/ \
develop (integration branch for features)
/ | \
feat feat feat (feature branches)
\ | /
release (release branches)
|
hotfix (emergency fixes to main)
Rules:
- develop = current development version
- main = released versions (tagged)
- Features branch from develop, merge back to develop
- Releases branch from main (or develop), merge back to both main + develop
- Hotfixes branch from main, merge back to both main + develop
# Release workflow:
git checkout main
git checkout -b release/v2.1.0
# Bump version numbers, update changelog
npm version minor --no-git-tag-version
git add -A && git commit -m "chore: prepare release v2.1.0"
# Merge to main and tag
git checkout main
git merge release/v2.1.0 --no-ff
git tag -a v2.1.0 -m "Release version 2.1.0"
git push origin main --tags
# Merge back to develop
git checkout develop
git merge release/v2.1.0 --no-ff
git push origin develop
# Cleanup
git branch -d release/v2.1.0
# Hotfix workflow:
git checkout main
git checkout -b hotfix/critical-security-fix
# ... fix the issue ...
git checkout main
git merge hotfix/critical-security-fix --no-ff
git tag -a v2.1.1 -m "Security patch v2.1.1"
git push origin main --tags
# Also merge to develop (so fix isn't lost in next dev cycle)
git checkout develop
git merge hotfix/critical-security-fix --no-ff
git push origin develop
Trunk-Based Development (Fast Teams)
For teams doing continuous deployment:
- Everyone commits directly to main (trunk)
- No long-lived branches
- Features hidden behind feature flags
- CI/CD must be fast and reliable
- Each commit is potentially deployable
When to use:
✅ Small team (< 10 devs)
✅ Fast CI (< 10 min)
✅ Comprehensive test suite
✅ Feature flag system in place
❌ Large teams without discipline
❌ Slow or manual testing
❌ No feature flags
# Trunk-based daily workflow:
git checkout main
git pull origin main
# ... work on feature (hidden behind flag) ...
git add -A && git commit -m "feat(payment): add Stripe integration behind flag"
git push origin main
# CI runs → auto-deploys to staging
# QA tests on staging
# Flip feature flag for production rollout
# If something breaks:
git revert <commit-hash> # Instantly revert bad commit
git push origin main # Deployed immediately
// Fix properly later, then revert the revert
Practical Branch Hygiene
# Before starting work ALWAYS update main:
git checkout main && git pull origin main
# Keep branches short-lived (merge or abandon within 1-2 days):
# Check how old your branches are:
git for-each-ref --sort=-committerdate --format='%(refname:short) %(committerdate:short)' refs/heads/
# Find merged branches that can be deleted:
git branch --merged | grep -v '\*' | grep -v 'main'
# Abandon a branch (save work but don't merge):
git stash -u # Save changes
git checkout main # Switch away
# Later: come back
git checkout abandoned-feature
git stash pop # Restore work
# Sync fork with upstream (contributing to open source):
git remote add upstream https://github.com/original/repo.git
git fetch upstream
git checkout main
git merge upstream/main # Integrate upstream changes
git push origin main # Update your fork
# Clean up local branches tracking deleted remotes:
git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -d
# Rebase vs merge (the eternal debate):
# MERGE: Creates merge commit, preserves exact history
# REBASE: Linear history, rewrites commits as if they were written on top of main
# When to use each:
# Use MERGE for:
# - Shared branches (team collaboration)
# - Public branches (PRs from others)
# - Important branches where history matters
# Use REBASE for:
# - Your own local feature branch before pushing
# - Keeping history clean and linear
# - "I want my branch to look like I wrote it on top of latest main"
# Safe rebase workflow (BEFORE pushing to shared branch):
git checkout feature-branch
git rebase main # Replay your commits on top of latest main
git push origin feature-branch --force-with-lease # Safer force push
Commit Message Convention
# A good convention makes git log readable and enables automation:
# Format:
# <type>(<scope>): <subject>
#
# <body> (optional)
#
# <footer> (optional)
# Types:
# feat: New feature
# fix: Bug fix
# docs: Documentation changes
# style: Formatting, missing semicolons, etc. (no code change)
# refactor: Code change that neither fixes a bug nor adds a feature
# perf: Performance improvement
# test: Adding or updating tests
# chore: Build process, tooling, dependencies, config
# ci: CI/CD changes
# Examples:
feat(auth): add OAuth2 Google login flow
Implement Google OAuth2 using passport-google-oauth20.
Users can now sign in with their Google account.
Redirects to profile setup for new users.
Closes #123
fix(db): handle connection timeout gracefully
DB queries were crashing the app when connection pool was exhausted.
Now retries with exponential backoff up to 3 times before returning error.
Fixes #456
# Why this matters:
# - `git log` becomes a readable changelog
# - Can auto-generate CHANGELOG.md from messages
# - Can determine semantic version bumps automatically
# - Easy to find what changed and why
# Install commit message linter:
# npm install -D @commitlint/cli @commitlint/config-conventional
# npx husky-init (adds git hooks)
# Add .commitlintrc.json with conventional config
Which workflow do you use? What's your branching strategy?
Follow @armorbreak for more practical developer guides.
Top comments (0)