Feature Branch Workflow
The feature branch workflow is the most widely-used Git collaboration model in professional teams. Its core idea is simple: every piece of work — a feature, a bug fix, a refactor, an experiment — gets its own dedicated branch. No one commits directly to main. Code lands on main only after it has been reviewed, tested, and deliberately merged. This single constraint produces enormous improvements in code quality, team coordination, and release confidence.
Core idea
Feature branches isolate work in progress
main: A ── B ─────────────── M1 ─────────── M2
│ ↑ ↑
│ (merge PR#1) (merge PR#2)
feature/auth: └── C ── D ── E |
(reviewed, CI passed)
feature/search: └── F ── G ── H ──────────────────┘
main is always in a releasable state.
Work-in-progress lives safely on its own branch.Branch naming conventions
Consistent branch names make it easy to understand the purpose of a branch at a glance, filter branches, and automate CI/CD pipelines.
Pattern | Example | Used for |
|---|---|---|
|
| New features |
|
| Bug fixes |
|
| Critical production fixes |
|
| Maintenance, upgrades |
|
| Code restructuring, no new features |
|
| Documentation updates |
|
| Adding or improving tests |
|
| Release preparation (Git Flow style) |
Full lifecycle: from idea to merged code
Update
mainto the latest state.Create a new branch from
mainfor the feature.Write code, commit in small logical increments.
Push the branch to the remote regularly.
When ready, open a Pull Request from your branch to
main.Team reviews the PR — leave comments, request changes, or approve.
CI/CD runs automated tests on the branch.
Once approved and tests pass, merge the PR into
main.Delete the feature branch (local and remote).
Pull the updated
mainlocally.
Real command walkthrough — feature/user-search
# 1. Start from a fresh main git switch main git pull origin main # Already up to date. # 2. Create the feature branch git switch -c feature/user-search # Switched to a new branch 'feature/user-search' # 3. Write code and commit incrementally # (first commit) git add src/components/SearchBar.tsx git commit -m "feat: add SearchBar component skeleton" # (second commit) git add src/hooks/useSearch.ts git commit -m "feat: implement useSearch hook with debouncing" # (third commit) git add src/pages/SearchResults.tsx tests/search.test.ts git commit -m "feat: add SearchResults page with unit tests" # 4. Push the branch to remote git push -u origin feature/user-search # Branch 'feature/user-search' set up to track remote branch. # 5. Open a PR on GitHub/GitLab # (done in the web UI — or via CLI) gh pr create --title "Add user search feature" --body "Implements full-text user search with debouncing and pagination." # 6. (After review and approval) # 7. Merge the PR (via web UI, or:) git switch main git merge --no-ff feature/user-search git push origin main # 8. Delete the branch git branch -d feature/user-search git push origin --delete feature/user-search # 9. Update local main git pull origin main
Benefits over centralized workflow
Concern | Centralized | Feature Branch |
|---|---|---|
Code review | None — code goes directly to main | Mandatory — PRs require approval before merge |
Main branch stability | Fragile — any commit can break it | Protected — only reviewed, passing code merges |
Parallel work | Risky — easy to interfere | Safe — each feature is isolated |
Incomplete features | Visible in main immediately | Hidden on their own branch until ready |
Deployment | Any commit might go to prod | Only merged, reviewed code goes to prod |
CI/CD | Runs on main after commit | Runs on feature branch before merge — catches issues early |
Blame and bisect | Fine | Better — logical feature-per-merge grouping |
Keeping feature branches up to date with main
If your feature branch lives for more than a day or two, main will have moved forward. You need to bring those changes into your branch to avoid a painful merge conflict at PR time.
Option 1: Merge main into your feature branch
# Bring main's latest commits into your feature branch via merge git switch feature/user-search git fetch origin git merge origin/main # This creates a "merge commit" — your history shows a fork and re-join # Good for: seeing when you synced # Bad for: noisy history with many merge commits
Option 2: Rebase your branch onto main (cleaner history)
git switch feature/user-search git fetch origin git rebase origin/main # If conflicts occur during rebase: # Fix the conflict in each file, then: git add <resolved-file> git rebase --continue # Result: your commits are replayed on top of the latest main # History looks like you started the branch today, not 2 weeks ago. # After rebase, force-push is required (history was rewritten): git push --force-with-lease origin feature/user-search
Handling long-lived feature branches
Aim for feature branches shorter than 1–2 days. Long branches are harder to review and produce more conflicts.
Break large features into smaller sub-tasks, each on its own short-lived branch.
Use feature flags to merge incomplete code to
mainearly, keeping the flag off in production until ready.Schedule periodic rebases (or merges from main) so your branch does not drift too far.
Set a PR "work in progress" (WIP/Draft) status to signal that the branch is not ready for review yet.
Common errors
Error: forgot to branch from main
# You realize you have been committing directly to main git log --oneline # e5f6a7b (HEAD -> main, origin/main) my feature work ← oops # Fix: move those commits to a new branch, then reset main git branch feature/my-work # create branch pointing to current HEAD git reset --hard origin/main # reset main to remote state git switch feature/my-work # switch to the new branch # Your commits are now on feature/my-work, not main
Error: branch has diverged from remote after rebase
git push origin feature/user-search # ! [rejected] feature/user-search -> feature/user-search (non-fast-forward) # This is expected after a rebase — use force-with-lease: git push --force-with-lease origin feature/user-search # (safe: only force-pushes if no one else has pushed to the branch)