Remote Work Tools

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:

Risks for remote teams:

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:

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:

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'

Built by theluckystrike — More at zovo.one