Remote teams need a branching strategy that works asynchronously — no one standing next to you to resolve a conflict or explain why a branch is two weeks old. The right strategy depends on your team size, release cadence, and how often you deploy.
This guide covers the three dominant strategies (trunk-based development, GitHub Flow, and Gitflow), when each applies, and how to enforce consistency with tooling.
The Core Problem Git Branching Solves
Without a defined strategy, remote teams develop inconsistently: some developers push directly to main, others have branches that live for weeks, and merge conflicts accumulate. A branching strategy is a shared contract about how code moves from idea to production.
Trunk-Based Development
Trunk-based development (TBD) is the simplest strategy: developers commit directly to main (the “trunk”) or use very short-lived feature branches (under 1 day). CI runs on every commit.
# Trunk-based: typical workflow
git checkout main
git pull
# Make a change (small, under a day's work)
git add src/feature.js
git commit -m "feat: add pagination to user list"
git push origin main
For larger features, use feature flags to merge incomplete code safely:
// Feature flag pattern — code ships, but feature is off
if (featureFlags.isEnabled('new-pagination')) {
return <PaginatedList items={items} />;
}
return <OriginalList items={items} />;
# Short-lived feature branch (max 1-2 days)
git checkout -b feat/pagination
# work...
git push origin feat/pagination
# Open PR → merge same day
# Feature branch deleted after merge
Best for:
- Teams deploying multiple times per day
- Teams with strong CI/CD and feature flag infrastructure
- Experienced teams where everyone writes tests
Risks for remote teams:
- Requires discipline: no long-lived branches
- Feature flags add code complexity
- Not ideal if you have infrequent deployments (deploy monthly = TBD is wrong)
GitHub Flow
GitHub Flow uses main as the always-deployable branch and feature branches for all work. Every change goes through a pull request.
# GitHub Flow: step by step
# 1. Create a branch from main
git checkout main && git pull
git checkout -b feature/user-authentication
# 2. Make commits (multiple, as many as needed)
git add src/auth/
git commit -m "feat: add JWT token generation"
git commit -m "test: add auth middleware tests"
git commit -m "fix: handle expired token edge case"
# 3. Push and open a PR
git push origin feature/user-authentication
# Open PR on GitHub with description and review request
# 4. Address review feedback
git add src/auth/middleware.js
git commit -m "fix: address code review feedback on token expiry"
git push
# 5. Merge after approval
# On GitHub: "Squash and merge" (keeps main history clean)
# 6. Delete branch
git push origin --delete feature/user-authentication
Branch naming conventions:
# Prefix by type
feature/user-authentication
fix/checkout-timeout-error
chore/update-dependencies
docs/api-reference-update
release/v2.1.0
# With ticket reference
feature/PROJ-123-user-authentication
fix/BUG-456-checkout-timeout
Best for:
- Teams deploying frequently (daily or weekly)
- Remote teams where async PR review is the primary review mechanism
- Most startup and mid-size product teams
Gitflow
Gitflow uses permanent branches for main, develop, feature branches, release branches, and hotfix branches. It was designed for teams with scheduled releases.
main → production code only, always deployable
develop → integration branch, all features merge here
feature/* → individual features, branch from develop
release/* → pre-release stabilization, no new features
hotfix/* → urgent production fixes, branch from main
# Gitflow: feature development
git checkout develop && git pull
git checkout -b feature/payment-integration
# ... work ...
git checkout develop
git merge --no-ff feature/payment-integration
git push origin develop
git branch -d feature/payment-integration
# Gitflow: creating a release
git checkout develop
git checkout -b release/v2.1.0
# Only bug fixes go on release branch
git commit -m "fix: correct payment validation edge case"
# Release complete — merge to both main and develop
git checkout main && git merge --no-ff release/v2.1.0
git tag -a v2.1.0 -m "Release v2.1.0"
git checkout develop && git merge --no-ff release/v2.1.0
git branch -d release/v2.1.0
# Install git-flow CLI for workflow shortcuts
brew install git-flow-avh
# Initialize in a repo
git flow init -d # use defaults
# Start a feature
git flow feature start user-authentication
# Finish a feature (merges to develop)
git flow feature finish user-authentication
# Start a release
git flow release start v2.1.0
# Finish a release (merges to main + develop, tags)
git flow release finish v2.1.0
Best for:
- Teams with scheduled release cycles (monthly, quarterly)
- Software that requires parallel maintenance of multiple versions
- Teams where QA has a dedicated stabilization period before release
Branch Protection Rules
Whatever strategy you use, enforce it with branch protection:
# GitHub CLI — set branch protection for main
gh api repos/:owner/:repo/branches/main/protection \
--method PUT \
--field required_status_checks='{"strict":true,"contexts":["ci/test"]}' \
--field enforce_admins=false \
--field required_pull_request_reviews='{"required_approving_review_count":1,"dismiss_stale_reviews":true}' \
--field restrictions=null \
--field allow_force_pushes=false \
--field allow_deletions=false
# .github/branch-protection.yml (via GitHub CLI script)
branches:
main:
protection:
required_status_checks:
strict: true
contexts:
- "CI / test"
- "CI / lint"
required_pull_request_reviews:
required_approving_review_count: 1
dismiss_stale_reviews: true
require_code_owner_reviews: true
enforce_admins: false
restrictions: null
allow_force_pushes: false
allow_deletions: false
Commit Message Convention
Consistent commit messages make git log, changelogs, and release notes automatic.
# Conventional Commits format
# https://www.conventionalcommits.org
# Format: type(scope): description
feat(auth): add JWT token refresh endpoint
fix(checkout): handle expired session gracefully
docs(api): update authentication endpoint docs
chore(deps): upgrade axios to 1.7.2
test(auth): add edge case tests for token expiry
refactor(db): extract connection pooling to module
# Enforce with commitlint
npm install --save-dev @commitlint/cli @commitlint/config-conventional
# commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', ['feat','fix','docs','chore','test','refactor','ci','perf']],
'subject-max-length': [2, 'always', 72],
}
};
# Install husky to run commitlint on every commit
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}'
Stale Branch Cleanup
Long-lived branches accumulate in remote repos. Automate cleanup:
# List branches merged into main more than 30 days ago
git branch -r --merged origin/main | grep -v "main\|develop" | while read branch; do
last_commit=$(git log -1 --format="%ci" "$branch")
echo "$last_commit $branch"
done | awk '$1 < "'$(date -d '30 days ago' '+%Y-%m-%d')'"' | sort
# Delete merged remote branches (run periodically)
git fetch --prune # removes references to deleted remote branches locally
# GitHub CLI: list stale PRs
gh pr list --state closed --limit 50 --json headRefName,closedAt \
| jq '.[] | select(.closedAt < "'$(date -d '30 days ago' -I)'") | .headRefName'
Related Reading
- CI/CD Pipeline for Solo Developers: GitHub Actions
- Async Code Review Process Without Zoom Calls
- Remote Code Review Tools Comparison 2026
Built by theluckystrike — More at zovo.one