Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

Git Branching & Merging

Branching is Git’s killer feature. Create branches in milliseconds, experiment freely, and merge with confidence.

Why Branching Matters

Git Branching

Parallel Development

Multiple features in progress simultaneously without interference

Safe Experimentation

Try ideas without risking the main codebase

Code Review

Review changes before merging to main

Clean History

Organize work into logical units

Understanding Branches

A branch is just a pointer to a commit. That is it. It is a 41-byte file containing a commit hash. This is why Git branches are so cheap to create — there is no copying of files, no duplicating directories. Creating a branch is like putting a sticky note on a page in a book: the book does not change, you just marked where you are.
# List all branches
git branch

# List with last commit
git branch -v

# List all branches (including remote)
git branch -a

Creating Branches

# Create a new branch
git branch feature/user-auth

# Create and switch to it
git checkout -b feature/user-auth

# Modern syntax (Git 2.23+)
git switch -c feature/user-auth

Switching Branches

# Switch to existing branch
git checkout main
# or
git switch main

# Switch to previous branch
git checkout -
Before switching branches: Commit or stash your changes! Git won’t let you switch if you have uncommitted changes that would be overwritten.

Branch Naming Conventions

# Feature branches
git checkout -b feature/add-login
git checkout -b feature/payment-integration

# Bug fixes
git checkout -b fix/login-timeout
git checkout -b fix/memory-leak

# Hotfixes (urgent production fixes)
git checkout -b hotfix/security-patch

# Release branches
git checkout -b release/v1.2.0

# Experimental
git checkout -b experiment/new-architecture

Merging Branches

Fast-Forward Merge

When there are no new commits on the target branch:
# On main branch
git checkout main

# Merge feature branch
git merge feature/user-auth
Before:
main:    A---B
              \
feature:       C---D

After (fast-forward):
main:    A---B---C---D

Three-Way Merge

When both branches have new commits:
git checkout main
git merge feature/user-auth
Before:
main:    A---B---E
              \
feature:       C---D

After (merge commit):
main:    A---B---E---M
              \     /
feature:       C---D

Resolving Merge Conflicts

Conflicts happen when the same lines are changed in both branches. They are not failures — they are Git honestly telling you, “I found changes in both branches that touch the same code, and I do not want to guess which one you intended.” Conflicts are normal, expected, and a sign that your team is actively working on the same codebase.

Example Conflict

git merge feature/redesign
# Auto-merging index.html
# CONFLICT (content): Merge conflict in index.html
# Automatic merge failed; fix conflicts and then commit the result.

Conflict Markers

<<<<<<< HEAD
<h1>Welcome to My Site</h1>
=======
<h1>Welcome to Our Platform</h1>
>>>>>>> feature/redesign
  • <<<<<<< HEAD: Your current branch’s version
  • =======: Separator
  • >>>>>>> feature/redesign: Incoming branch’s version

Resolving

1

Open the conflicted file

# See which files have conflicts
git status
2

Edit the file

Remove conflict markers and choose the correct version:
<h1>Welcome to Our Platform</h1>
3

Stage the resolved file

git add index.html
4

Complete the merge

git commit  # Message pre-filled

Conflict Resolution Tools

# Use a visual merge tool
git mergetool

# Abort the merge
git merge --abort

# See conflicts
git diff --name-only --diff-filter=U

Deleting Branches

# Delete a merged branch
git branch -d feature/user-auth

# Force delete (even if not merged)
git branch -D experiment/failed-idea

# Delete remote branch
git push origin --delete feature/user-auth

Branch Management

Viewing Branches

# Branches merged into current branch
git branch --merged

# Branches not yet merged
git branch --no-merged

# Branches with last commit info
git branch -vv

Renaming Branches

# Rename current branch
git branch -m new-name

# Rename another branch
git branch -m old-name new-name

Common Branching Workflows

Feature Branch Workflow

# 1. Create feature branch from main
git checkout main
git pull origin main
git checkout -b feature/add-comments

# 2. Work on feature
# ... make changes ...
git add .
git commit -m "feat: add comment system"

# 3. Keep feature branch updated
git checkout main
git pull origin main
git checkout feature/add-comments
git merge main  # Or rebase

# 4. Push and create pull request
git push origin feature/add-comments
# Create PR on GitHub/GitLab

# 5. After PR is merged, clean up
git checkout main
git pull origin main
git branch -d feature/add-comments

Gitflow Workflow

Gitflow is designed for software with scheduled release cycles — mobile apps, desktop software, libraries with semantic versioning. It adds ceremony (more branches, more rules), so only use it when you genuinely need release branches.
# Main branches: main (production), develop (integration)

# Start a feature
git checkout develop
git checkout -b feature/new-feature

# Finish feature -- always use --no-ff (no fast-forward)
# --no-ff forces a merge commit even when fast-forward is possible.
# Why? Because the merge commit preserves the fact that a feature branch
# existed. Without it, the feature commits blend into develop's history
# and you lose the "this group of commits was one feature" context.
git checkout develop
git merge --no-ff feature/new-feature
git branch -d feature/new-feature

# Start a release -- branch off develop when it has enough features for a release
git checkout -b release/1.2.0 develop
# ... version bumps, last-minute bug fixes, changelog updates ...
# Only bug fixes allowed here -- no new features. This is the "stabilization" phase.
git checkout main
git merge --no-ff release/1.2.0
git tag -a v1.2.0 -m "Release version 1.2.0"
# Merge back to develop so bug fixes from the release branch are not lost
git checkout develop
git merge --no-ff release/1.2.0
git branch -d release/1.2.0

# Hotfix -- branch off main for urgent production fixes
git checkout -b hotfix/1.2.1 main
# ... apply the fix ...
git checkout main
git merge --no-ff hotfix/1.2.1
git tag -a v1.2.1 -m "Hotfix: critical security patch"
# Merge back to develop so the fix is included in future releases
git checkout develop
git merge --no-ff hotfix/1.2.1
git branch -d hotfix/1.2.1
Production gotcha: The most common Gitflow mistake is forgetting to merge hotfixes back to develop. This means the next release re-introduces the bug you just fixed in production. Automate this with CI or use a checklist.

Trunk-Based Development

Everyone commits to main (the “trunk”) or uses extremely short-lived branches (hours, not days). This is how Google, Meta, and most high-velocity SaaS teams operate. The key insight: merging frequently with small changes is far less risky than merging infrequently with large changes.
# Short-lived branch (merged same day, ideally within hours)
git checkout main
git pull origin main
git checkout -b quick-fix
# ... make changes (keep them small -- under 200 lines ideally) ...
git commit -am "fix: resolve login issue"
git push origin quick-fix
# Immediate merge after CI passes -- no week-long review cycles

# Incomplete features use feature flags, not long-lived branches.
# The code ships to production but is hidden until the flag is enabled.
# if (featureFlags.isEnabled('new-checkout-flow')) { ... }
Prerequisite: Trunk-based development requires a robust CI pipeline and strong test coverage. Without automated tests catching regressions, merging to main multiple times per day means breaking main multiple times per day. Start with Feature Branch workflow and graduate to trunk-based when your CI and testing culture are mature.

Merge Strategies

Merge (Default)

git merge feature/new-ui
# Creates a merge commit
Pros: Preserves complete history
Cons: Can create a messy history with many merge commits

Squash Merge

# Squash takes ALL commits from the feature branch and stages them
# as a single set of changes -- you then write one clean commit message.
git merge --squash feature/new-ui
git commit -m "feat: add new UI design"
Pros: Clean main branch history — each feature is one commit
Cons: Loses individual commit history (the 47 “WIP” and “fix typo” commits disappear, which is usually a feature, not a bug)
When to squash: Squash merge is the most popular strategy for merging PRs at companies like GitHub, Shopify, and Stripe. The reasoning is simple — the main branch should tell the story of what shipped, not the messy journey of how it got built. Individual commit history is preserved in the PR itself if anyone needs it.

Rebase (Advanced)

git checkout feature/new-ui
git rebase main
# Replays feature commits on top of main
Pros: Linear history
Cons: Rewrites history (don’t rebase public branches!)

Practical Examples

Example 1: Feature Development

# Start feature
git checkout -b feature/dark-mode

# Make commits
echo "dark theme CSS" > dark.css
git add dark.css
git commit -m "feat: add dark mode styles"

echo "toggle button" > toggle.js
git add toggle.js
git commit -m "feat: add dark mode toggle"

# Merge to main
git checkout main
git merge feature/dark-mode

# Clean up
git branch -d feature/dark-mode

Example 2: Handling Conflicts

# Create conflict scenario
git checkout -b branch-a
echo "Version A" > file.txt
git add file.txt
git commit -m "Add version A"

git checkout main
git checkout -b branch-b
echo "Version B" > file.txt
git add file.txt
git commit -m "Add version B"

# Try to merge
git checkout main
git merge branch-a  # Success
git merge branch-b  # CONFLICT!

# Resolve
# Edit file.txt, choose version
git add file.txt
git commit

# Clean up
git branch -d branch-a branch-b

Best Practices

  • Merge within days, not weeks
  • Reduces merge conflicts
  • Easier code review
git pull origin main
# Resolve any conflicts
git push origin feature/my-feature
# Local
git branch -d feature/completed

# Remote
git push origin --delete feature/completed
feature/user-authentication
fix/login-timeout
my-branch
test

Troubleshooting

”Cannot switch branches - uncommitted changes”

This happens because Git refuses to overwrite your uncommitted work when switching branches — it is protecting you, not blocking you.
# Option 1: Commit changes (if the work is meaningful, even if incomplete)
git add .
git commit -m "WIP: work in progress"

# Option 2: Stash changes (preferred for quick context switches)
# Stash shelves your changes temporarily so you can come back later
git stash save "WIP: halfway through payment form"
git checkout other-branch
# ... do your work on the other branch ...
git checkout original-branch
git stash pop  # Restore your shelved changes

”Merge conflict in binary file”

Binary files (images, fonts, compiled assets) cannot be auto-merged because there is no meaningful “line-by-line diff.” You must choose one version or the other.
# Keep their version (the branch you are merging in)
git checkout --theirs file.png
git add file.png

# Keep our version (the branch you are currently on)
git checkout --ours file.png
git add file.png
Production gotcha: Binary conflicts are common in repos with designers committing image assets. The best prevention is Git LFS (Large File Storage), which stores binaries outside the repo and reduces conflict frequency. Additionally, agree on a convention: “the branch being merged in wins for assets” — this eliminates the decision paralysis.

”Accidentally committed to wrong branch”

This happens more often than anyone admits. The fix is straightforward: cherry-pick the commit to the correct branch, then remove it from the wrong one.
# First, note the commit hash from git log
git log --oneline -3

# Move commit to the correct branch
git checkout correct-branch
git cherry-pick abc123  # SHA of the commit -- creates a copy on this branch

# Remove from wrong branch
git checkout wrong-branch
git reset --hard HEAD~1  # WARNING: this discards the commit from this branch
Be careful: git reset --hard is destructive — it permanently removes the commit from the branch. Make sure you have successfully cherry-picked it to the correct branch first. If you are nervous, use git reset --soft HEAD~1 instead, which un-commits but keeps the changes staged.

Key Takeaways

  • Branches are lightweight pointers
  • Create branches freely for features, fixes, experiments
  • Merge conflicts are normal—learn to resolve them
  • Delete branches after merging
  • Choose a workflow that fits your team

Interview Deep-Dive

Strong Answer:
  • Squash merges collapse all commits from a feature branch into a single commit on main. The main branch tells the story of “what shipped” — one commit per feature/fix. This makes git log main clean, git bisect on main fast (fewer commits to search), and reverts trivial (git revert <single-sha>). Companies like GitHub, Shopify, and Stripe use this model. The individual commit history is preserved in the pull request UI if anyone needs it.
  • Regular merge commits preserve full branch history and add an explicit merge commit that records when and how branches were integrated. The benefit is complete traceability: you can see every intermediate step, every WIP commit, every fix-after-code-review commit. This matters in regulated environments where auditors want to see the exact sequence of changes. It also means git blame shows the original author of each line, not “the person who clicked merge.”
  • The trade-off comes down to: do you optimize for reading history (squash) or writing history (merge)? Squash merges require discipline in writing a good squash commit message (the default auto-generated message is usually garbage). Merge commits require discipline in keeping individual commits clean (otherwise main history is polluted with “WIP” and “fix typo”).
  • My recommendation depends on team culture. If the team writes clean, atomic commits with good messages, regular merges work well. If commits tend to be messy (most teams), squash merges produce a cleaner main branch. A third option — rebase-and-merge — replays the individual commits linearly on main without a merge commit, giving you clean history if the individual commits are clean.
Follow-up: With squash merges, git blame on main shows the person who merged the PR, not the original author. How do you deal with that?GitHub and GitLab preserve the original author as the commit author and set the merger as the committer in squash merges, but many tools only display the author. For teams that care about accurate attribution, the PR link in the commit message (e.g., (#456)) serves as the source of truth. I also recommend using the Co-authored-by trailer in squash commit messages to credit all contributors. For deep-dive blame, git log --follow -p file.txt and the PR history are more reliable than git blame alone.
Strong Answer:
  • First, I understand the three-way merge. There are three versions of the file: the common ancestor (base), our version (HEAD), and their version (the branch being merged). I view all three explicitly: git show :1:file.txt (base), git show :2:file.txt (ours), git show :3:file.txt (theirs). This gives me the full context that conflict markers alone do not.
  • Second, I examine the intent of each change. I run git log --oneline main..feature -- file.txt to see what commits from the feature branch touched this file, and git log --oneline feature..main -- file.txt for changes on main. Reading the commit messages tells me what each side was trying to accomplish.
  • Third, I use a visual merge tool. git mergetool opens a three-pane view showing base, ours, and theirs side by side. VS Code, IntelliJ, and Beyond Compare all support this. For a 100-line conflict, visual diffing is essential — raw conflict markers are unreadable at that scale.
  • Fourth, if the conflict is in code (not prose), I resolve it and immediately run the test suite. Tests catch logical errors in my resolution that I might miss visually. If tests pass, I am confident in the resolution.
  • Fifth, I make the resolution its own atomic step. I do not combine conflict resolution with other changes. The merge commit should contain only the resolution so that future reviewers can audit exactly what decisions were made during the merge.
Follow-up: After resolving, tests pass locally but the merge introduced a subtle logic bug that only appears under load. How could this happen?This happens when both branches changed the same logic independently in compatible but semantically conflicting ways. For example, branch A changes a function’s error handling, branch B changes the same function’s retry logic. Git sees no textual conflict (they modified different lines), so it auto-merges. But the combined behavior is wrong — the new error handling skips the new retry logic. This is called a “semantic merge conflict” and no tool can detect it automatically. The defense is thorough code review of merge commits, integration tests that exercise combined behavior, and in critical systems, requiring manual review of all auto-merged regions (not just conflicted ones) in the merge diff.
Strong Answer:
  • Feature Branch (the default for most teams): every feature gets a branch off main, merged via PR after review. Failure mode: long-lived feature branches that diverge significantly from main, leading to painful merge conflicts. Prevention: merge main into feature branches daily, keep branches under a week.
  • Gitflow: adds develop, release/*, and hotfix/* branches on top of feature branches. Best for software with versioned releases (mobile apps, desktop software, libraries). Failure mode: excessive ceremony. Teams spend more time managing branches than writing code. I have seen teams with 5 developers maintaining 4 concurrent release branches. Prevention: only adopt Gitflow if you genuinely ship versioned releases to different customers simultaneously.
  • Trunk-Based: everyone commits to main (or very short-lived branches that merge within hours). Used by Google, Meta, and most high-velocity SaaS companies. Failure mode: broken main. If the CI pipeline is slow or flaky, developers merge untested code. If there are no feature flags, half-built features ship to users. Prevention: fast CI (under 10 minutes), comprehensive test coverage, feature flags for in-progress work.
  • The pattern I see: most teams start with Feature Branch workflow. As the team grows and the deployment cadence increases, they either evolve toward trunk-based (SaaS products) or toward Gitflow (versioned products). The most common mistake is adopting a workflow that is too complex for the team’s maturity — a 3-person startup using full Gitflow is adding process overhead with no benefit.
Follow-up: Your team currently uses Gitflow and wants to migrate to trunk-based development. What prerequisites must be in place before the switch?Three prerequisites are non-negotiable. First, a CI pipeline that runs in under 10 minutes and is not flaky — if CI takes 30 minutes, developers will merge without waiting. Second, a feature flag system (LaunchDarkly, Unleash, or even a simple config file) so incomplete features can be deployed behind flags without exposing them to users. Third, a team culture of small, frequent commits — if developers are used to 2-week branches with 50 commits, they need to learn to break work into shippable increments. I would run both workflows in parallel during the transition: new features use trunk-based, existing long-lived branches finish in Gitflow, and after 2-3 months the team is fully migrated.

Next: Git Collaboration →