Git — Branching & Merging
Branching is Git's most powerful feature. A branch is a lightweight, movable pointer to a specific commit. Creating, switching, and merging branches is nearly instantaneous because Git does not copy files — it only creates a 41-byte reference file.
This section covers branch management, merging strategies, conflict resolution, rebasing, stashing, and popular branching models like Git Flow and trunk-based development.
A branch in Git is simply a named reference to a commit. The default branch is main (or historically master). When you make a new commit, the current branch pointer moves forward automatically.
| 1 | # List all local branches |
| 2 | git branch |
| 3 | |
| 4 | # List all branches (including remote) |
| 5 | git branch -a |
| 6 | |
| 7 | # Create a new branch (stays on current branch) |
| 8 | git branch feature-login |
| 9 | |
| 10 | # Create and switch to new branch |
| 11 | git checkout -b feature-login |
| 12 | |
| 13 | # Modern way (Git 2.23+) |
| 14 | git switch -c feature-login |
| 15 | |
| 16 | # Switch to an existing branch |
| 17 | git checkout main |
| 18 | git switch main |
| 19 | |
| 20 | # Rename a branch |
| 21 | git branch -m old-name new-name |
| 22 | |
| 23 | # Delete a fully merged branch |
| 24 | git branch -d feature-login |
| 25 | |
| 26 | # Force delete (even if not merged) |
| 27 | git branch -D feature-login |
info
Merging combines the history of two branches. Git creates a merge commit that has two parent commits, preserving the complete history of both branches.
| 1 | # Merge feature into main |
| 2 | git switch main |
| 3 | git merge feature-login |
| 4 | |
| 5 | # This creates a merge commit if there are no conflicts. |
| 6 | # Output: |
| 7 | # Merge made by the 'ort' strategy. |
| 8 | # 5 files changed, 120 insertions(+), 30 deletions(-) |
| 9 | |
| 10 | # Fast-forward merge (no merge commit needed) |
| 11 | # Happens when main has not diverged since feature was created |
| 12 | git merge feature-login |
| 13 | # Updating a1b2c3d..e4f5g6h |
| 14 | # Fast-forward |
| 15 | |
| 16 | # Force a merge commit even with fast-forward |
| 17 | git merge --no-ff feature-login |
| 18 | |
| 19 | # Squash merge (combine all feature commits into one) |
| 20 | git merge --squash feature-login |
| 21 | git commit -m "Add login feature" |
| Merge Type | Behavior | Use Case |
|---|---|---|
| Fast-forward | Linear history, no merge commit | Simple, no divergence |
| 3-way merge | Creates merge commit with two parents | Divergent branches |
| --no-ff | Forces merge commit even when fast-forward possible | Preserve feature branch history |
| --squash | Combines all commits into one | Clean up messy history |
best practice
Conflicts occur when two branches modify the same part of the same file. Git cannot automatically determine which change to keep and requires manual resolution.
| 1 | # When a merge has conflicts, Git shows: |
| 2 | Auto-merging src/app.ts |
| 3 | CONFLICT (content): Merge conflict in src/app.ts |
| 4 | Automatic merge failed; fix conflicts and then commit the result. |
| 5 | |
| 6 | # The conflicted file contains conflict markers: |
| 7 | const TIMEOUT = 5000; |
| 8 | |
| 9 | # Steps to resolve: |
| 10 | # 1. Open the file and edit the conflicted region |
| 11 | # 2. Remove the conflict markers |
| 12 | # 3. Keep the correct code |
| 13 | # 4. Stage the resolved file |
| 14 | git add src/app.ts |
| 15 | |
| 16 | # 5. Complete the merge |
| 17 | git commit |
| 18 | |
| 19 | # Abort the merge entirely |
| 20 | git merge --abort |
| 21 | |
| 22 | # Use a merge tool |
| 23 | git mergetool |
pro tip
Rebasing rewrites commit history by moving a branch's commits to a new base. Unlike merging (which creates a merge commit), rebasing produces a linear history. Rebase is useful for keeping feature branches up to date with main.
| 1 | # Rebase current branch onto main |
| 2 | git switch feature-login |
| 3 | git rebase main |
| 4 | |
| 5 | # Interactive rebase (edit, squash, reorder commits) |
| 6 | git rebase -i HEAD~3 |
| 7 | |
| 8 | # This opens an editor with: |
| 9 | pick a1b2c3d Add login form |
| 10 | pick e4f5g6h Add validation |
| 11 | pick h7i8j9k Add API integration |
| 12 | |
| 13 | # Commands available in interactive rebase: |
| 14 | # pick - use commit |
| 15 | # reword - change commit message |
| 16 | # edit - stop to amend |
| 17 | # squash - combine with previous commit |
| 18 | # fixup - combine, discard message |
| 19 | # drop - remove commit |
| 20 | |
| 21 | # After resolving rebase conflicts: |
| 22 | git add resolved-file.ts |
| 23 | git rebase --continue |
| 24 | |
| 25 | # Skip a problematic commit |
| 26 | git rebase --skip |
| 27 | |
| 28 | # Abort the rebase entirely |
| 29 | git rebase --abort |
warning
Cherry-picking applies a specific commit from one branch onto another. It is useful for selectively porting bug fixes without merging an entire branch.
| 1 | # Apply a single commit to the current branch |
| 2 | git cherry-pick abc123 |
| 3 | |
| 4 | # Cherry-pick a range of commits |
| 5 | git cherry-pick abc123..def456 |
| 6 | |
| 7 | # Cherry-pick with no automatic commit (edit first) |
| 8 | git cherry-pick -n abc123 |
| 9 | |
| 10 | # Cherry-pick multiple commits |
| 11 | git cherry-pick abc123 def456 ghi789 |
| 12 | |
| 13 | # Cherry-pick with a custom message |
| 14 | git cherry-pick -x abc123 |
| 15 | # -x adds "cherry picked from commit ..." to the message |
info
Git worktrees allow you to check out multiple branches simultaneously in separate directories. This avoids the overhead of stashing or committing WIP code when you need to context-switch.
| 1 | # Create a new worktree for a branch |
| 2 | git worktree add ../project-hotfix hotfix |
| 3 | # This creates ../project-hotfix/ with the hotfix branch checked out |
| 4 | |
| 5 | # Create a worktree with a new branch |
| 6 | git worktree add -b new-feature ../project-feature main |
| 7 | |
| 8 | # List all worktrees |
| 9 | git worktree list |
| 10 | |
| 11 | # Remove a worktree |
| 12 | git worktree remove ../project-hotfix |
| 13 | |
| 14 | # Prune stale worktree references |
| 15 | git worktree prune |
pro tip
Branching strategies define how teams organize branches around releases, features, and bug fixes. The right strategy depends on your team size, release cadence, and CI/CD pipeline.
Git Flow
A comprehensive branching model with dedicated branches for features, releases, and hotfixes. Well-suited for projects with scheduled releases.
| 1 | # Main branches (infinite lifetime): |
| 2 | main # Production-ready code |
| 3 | develop # Integration branch for features |
| 4 | |
| 5 | # Supporting branches (finite lifetime): |
| 6 | feature/* # New features (branch off develop) |
| 7 | release/* # Release preparation (branch off develop) |
| 8 | hotfix/* # Urgent fixes (branch off main) |
| 9 | |
| 10 | # Workflow: |
| 11 | git checkout -b feature/login develop |
| 12 | # ... work on feature ... |
| 13 | git checkout develop |
| 14 | git merge --no-ff feature/login |
| 15 | |
| 16 | # For releases: |
| 17 | git checkout -b release/1.2 develop |
| 18 | # ... final tweaks ... |
| 19 | git checkout main |
| 20 | git merge --no-ff release/1.2 |
| 21 | git tag -a v1.2 |
info
Trunk-Based Development
A simpler model where developers make short-lived feature branches (1-2 days) off main and merge frequently. Integrates well with CI/CD and continuous deployment.
| 1 | # Principles: |
| 2 | # - Short-lived branches (hours, not days) |
| 3 | # - Frequent merges to main (multiple times daily) |
| 4 | # - Feature flags for incomplete work |
| 5 | # - Automated testing on every push |
| 6 | |
| 7 | # Workflow: |
| 8 | git checkout -b add-search |
| 9 | # ... work for a few hours ... |
| 10 | git add . && git commit -m "Add search component" |
| 11 | git push -u origin add-search |
| 12 | # Create PR, get review, merge to main |
| 13 | git checkout main |
| 14 | git pull |
| 15 | git branch -d add-search |
| 16 | |
| 17 | # Key practices: |
| 18 | # - Keep branches under 1 day old |
| 19 | # - Use feature flags (not branches) for incomplete features |
| 20 | # - Run CI on every branch push |
| 21 | # - Rebase daily to reduce merge conflicts |
best practice
GitHub Flow
A simpler variant of trunk-based development popularized by GitHub. Everything branches from main, and all merges happen via pull requests.
| 1 | # Rules: |
| 2 | # 1. Anything in main is deployable |
| 3 | # 2. Create descriptive branches off main |
| 4 | # 3. Push to named branches frequently |
| 5 | # 4. Open a PR early (even as draft) |
| 6 | # 5. Get review, run CI checks |
| 7 | # 6. Merge to main and deploy immediately |
| 8 | |
| 9 | # Branch naming convention: |
| 10 | feat/add-user-auth |
| 11 | fix/login-validation |
| 12 | chore/update-deps |
| 13 | docs/api-readme |
| 14 | refactor/db-layer |
Tags mark specific points in history, typically used for releases (v1.0, v2.3.1). Unlike branches, tags do not move when new commits are added.
| 1 | # Create a lightweight tag |
| 2 | git tag v1.0.0 |
| 3 | |
| 4 | # Create an annotated tag (recommended) |
| 5 | git tag -a v1.0.0 -m "Release version 1.0.0" |
| 6 | |
| 7 | # Tag a specific commit |
| 8 | git tag -a v1.0.0 abc123 -m "Release 1.0.0" |
| 9 | |
| 10 | # List all tags |
| 11 | git tag |
| 12 | |
| 13 | # Search for tags matching a pattern |
| 14 | git tag -l "v2.*" |
| 15 | |
| 16 | # Show tag details |
| 17 | git show v1.0.0 |
| 18 | |
| 19 | # Push tags to remote |
| 20 | git push origin v1.0.0 |
| 21 | |
| 22 | # Push all tags |
| 23 | git push --tags |
| 24 | |
| 25 | # Delete a local tag |
| 26 | git tag -d v1.0.0 |
| 27 | |
| 28 | # Delete a remote tag |
| 29 | git push origin --delete v1.0.0 |
pro tip
These commands handle edge cases in branch management, from detached HEAD states to complex ref log operations.
| 1 | # Detached HEAD state — investigate old commits |
| 2 | git checkout abc123 |
| 3 | # Warning: you are in 'detached HEAD' state. |
| 4 | # Create a branch if you want to keep changes: |
| 5 | git switch -c investigation-branch |
| 6 | |
| 7 | # Find branches containing a specific commit |
| 8 | git branch --contains abc123 |
| 9 | |
| 10 | # List merged branches (safe to delete) |
| 11 | git branch --merged main |
| 12 | |
| 13 | # List unmerged branches |
| 14 | git branch --no-merged |
| 15 | |
| 16 | # Show upstream tracking info |
| 17 | git branch -vv |
| 18 | |
| 19 | # Compare branches (commits in A but not B) |
| 20 | git log main..feature-login --oneline |
| 21 | git log feature-login..main --oneline |
| 22 | |
| 23 | # Recover a deleted branch (if you have the hash) |
| 24 | git reflog |
| 25 | # Output: abc123 HEAD@{0}: checkout: moving from main to feature-x |
| 26 | git checkout -b recovered-branch abc123 |
warning
✗ Rebasing public branches
git rebase main (on shared branch)Rewriting pushed commits causes duplicate commits for other developers. Only rebase local, unpublished work.
✗ Merge commit pollution
git pull (without --rebase)git pull creates a merge commit by default. Use git pull --rebase to keep a linear history, or set git config --global pull.rebase true.
✗ Long-lived feature branches
git checkout -b feature-huge (2 weeks ago)Branches that live for weeks accumulate massive merge conflicts. Merge or rebase daily to stay in sync. Use feature flags instead of long-lived branches.