Free lesson · Git Mastery
Merging Overview
Merging Overview
A note from Nikhil Joshi, Senior Software Engineer at Google Cloud Spanner.
What Merging Is
Module 6 established that branches are independent lines of work - cheap pointers to commits that let multiple efforts proceed in parallel. The natural follow-on question is what happens when those efforts need to be combined. That's merging.
A merge takes the work from one branch and integrates it into another. The mechanics involve identifying what each branch contributed since they last shared a common ancestor, combining those contributions, and recording the result as a commit (sometimes - fast-forward merges skip even that). The conceptual question is simpler than the mechanics: how do you take two different sets of changes that have evolved in parallel and produce a single coherent state that contains both?
When I moved from SVN at Persistent Systems to Git, the merging model was the biggest conceptual shift. SVN's merges were tied to file paths and revision ranges; you essentially told SVN "take these specific changes from over there and apply them here." Git's merges are based on the commit graph - Git figures out what changed by comparing the branch tips to their common ancestor. The branch is the unit of merge, not a manually-specified range. That changed everything about how I thought about parallel work.
This topic covers what merging is at a high level. The rest of Module 7 covers the specific operations: fast-forward merges (Topic 7.2), three-way merges (Topic 7.3), merge strategies (Topic 7.4), conflicts (Topics 7.5 and 7.6), commit messages (Topic 7.7), and undoing (Topic 7.8).
The Two Kinds of Merge
Git has two fundamentally different merge situations, and the right mental model depends on which one applies:
flowchart TD
A[Two branches that need to be combined] --> B{Is one branch's tip<br/>a direct descendant<br/>of the other?}
B -->|Yes| C[Fast-forward merge<br/>No new commit<br/>Just move the pointer]
B -->|No| D[Three-way merge<br/>Combine changes<br/>Create a merge commit]
The diagram shows the decision Git makes when merging. If one branch's tip is reachable from the other - that is, one branch is a direct descendant of the other in the commit graph - Git can fast-forward. The branch pointer just moves to the new commit; nothing else changes. If the two branches have diverged - each has commits the other doesn't - Git does a three-way merge, combining the changes and creating a merge commit that has both branches' tips as parents. Topic 7.2 covers fast-forward; Topic 7.3 covers three-way.
The two scenarios feel different in practice. Fast-forward merges are silent and instant - main moved a few commits ahead, you merge your branch which had no commits, your branch pointer simply moves to match main. Three-way merges are where the interesting work happens - Git has to combine two histories, sometimes successfully and sometimes with conflicts.
When Merging Happens in Practice
A merge is the routine outcome of feature work. The typical sequence:
- Engineer branches from main
- Engineer works on the branch, makes commits
- Engineer pushes the branch to the remote
- A pull request opens for review
- The PR is approved and merged into main
- The feature branch is deleted
Step 5 is a merge. On most platforms (GitHub, GitLab, Bitbucket) the merge happens through the web UI when someone clicks "merge." Under the hood, the platform runs git merge against the target branch. The result is either a fast-forward (if no other work has landed on main since the branch began) or a three-way merge (if main has moved since).
The same operation happens locally when you git merge from one branch to another in your own clone. The mechanics are identical; the web UI is just a wrapper around the underlying Git command.
The Basic Command
The simplest invocation:
git switch main # be on the destination branch
git merge feature/payment-flow # merge the source branch into the current branch
Two commands: switch to the branch you want to merge into, then merge the source branch into it. The merge command takes the source branch as an argument; the current branch is the destination. After this, the current branch contains the source branch's changes - either as a fast-forward update of the pointer or as a merge commit.
Note that the order of arguments matters. You're always on the destination and you merge the source into it. To merge feature/payment-flow into main, you switch to main first, then merge feature/payment-flow. Doing it the other way around would integrate main into the feature branch, which is a different operation with different semantics.
What a Merge Commit Looks Like
When Git creates a merge commit (the three-way case), the commit object has two parents instead of one. Topic 14.4 covers commit objects in depth; for now, the relevant fact is that the merge commit's structure is what records "this commit combines work from two branches."
git log --oneline --graph
This command shows the commit history with a visual graph of branch structure. Merge commits appear with multiple incoming lines - one from each parent. The asterisks and lines form a small ASCII visualization that makes the branching and merging structure visible at a glance.
Sample output:
* e1f4a2b (HEAD -> main) Merge branch 'feature/payment-flow'
|\
| * b9e3f81 (feature/payment-flow) Add validation
| * c47b2a3 Update payment model
* | d8f3e1c Update README
|/
* a3f2c1d Initial commit
The merge commit e1f4a2b has two parents - d8f3e1c from main and b9e3f81 from the feature branch. The history clearly shows the structure: two parallel lines of development, then a merge that joins them. This visualization is what git log --oneline --graph --all shows for any repository's branching structure.
A Conceptual Difference From SVN
In SVN, a merge required telling the tool exactly which revisions to merge. The "branch" concept existed but was less central; merges were essentially specifying a range of changes to apply. The result was that merges between long-lived branches were dreaded - keeping track of which revisions had already been merged was manual, and conflicts were common.
In Git, the branches themselves carry the information. Git knows what's been merged because it can walk the commit graph. The common ancestor between two branches is computable from the graph; the changes each branch made since that ancestor are computable; the merge applies both sets of changes to the ancestor and resolves any overlaps.
This means merges between long-lived branches are routine rather than fearsome. The team at Spanner merges feature branches into main dozens of times a day; the team at Razorpay did the same. The mechanics are reliable; the conflicts are tractable.
A Pattern at Spanner
The typical daily workflow on the Spanner team:
# Start of day: get the latest main
git switch main
git pull --rebase origin main
# Continue work on my feature branch
git switch feature/my-work
# Periodically: bring in latest main
git merge main
# (or git rebase main, depending on team preference - Topic 8.1 covers the choice)
# End of work: push and open PR
git push origin feature/my-work
This is the pattern that keeps feature branches synchronized with main as the team's work progresses. Periodic merges from main into the feature branch resolve conflicts incrementally rather than letting them accumulate. When the feature branch is ready to merge back into main, the merge is usually clean because incremental sync has already absorbed any conflicting changes.
The discipline of merging main into feature branches periodically - sometimes daily - is what keeps the final merge-back painless. Letting a feature branch drift for weeks without syncing produces a conflict-heavy merge at the end; the incremental approach distributes the work.
Topic 8.1 will introduce rebase as an alternative to this pattern. The choice between "merge main into feature" and "rebase feature onto main" is a long-running debate in many teams. Both work; the choice is mostly stylistic.
When Merges Are Wrong
A few situations where the right operation isn't a merge:
You want a linear history without merge commits. Rebase (Topic 8.1) is the alternative. The feature branch's commits are replayed on top of main, producing a straight line rather than a branched-and-merged graph.
You want only some commits from the source branch. Cherry-pick (Topic 13.2) takes one or a few commits from another branch and applies them to the current one. Useful for picking fixes out of a feature branch.
You want to combine multiple commits into one. Squashing (Topic 8.7) collapses multiple commits into a single one. Sometimes done before merging to clean up a feature branch's history.
The "merge" is just bringing in changes that haven't diverged. That's a fast-forward (Topic 7.2). Mechanically a merge but with very different semantics.
Knowing which operation fits the situation is half the skill. The other half is executing the operation cleanly.
What Comes Next
Topic 7.2 covers fast-forward merges - the simpler case. Topic 7.3 covers three-way merges. Topic 7.4 covers merge strategies (the algorithms Git uses to combine changes). Topics 7.5 and 7.6 cover conflicts. Topic 7.7 covers merge commit messages. Topic 7.8 covers aborting and undoing merges.
After Module 7, Module 8 covers rebasing as the alternative integration pattern.
What Goes Wrong
Warning: Common merge-mental-model confusions:
Thinking of merge as "bring code over." It's more precise: bring all of another branch's history into this branch. The result includes commits, not just changes.
Reversing the source and destination. You're always on the destination and merge the source. Switching to feature and merging main integrates main into feature, which is rarely what you want during PR-style workflow.
Forgetting that merges create commits. A three-way merge produces a new commit on the destination branch. That commit appears in history.
Avoiding merges of long-lived branches. Letting branches diverge for weeks produces painful merges. Sync regularly.
Treating the platform's "merge" button as different from
git merge. It isn't. The platform runs the same command server-side.Expecting all merges to be automatic. They aren't. Three-way merges with conflicting changes require manual resolution (Topic 7.6).
Try It
Exercise: Build the mental model:
- In a practice repository, create a branch, make some commits, then merge it back into main. Use
git log --oneline --graph --allto see the structure.- Create a branch from main, make a commit, then make a commit on main too (so the branches diverge). Merge - note that this is a three-way merge that creates a merge commit.
- Create a branch from main and make a commit on the branch. Don't make any commits on main. Merge - note this is a fast-forward (no merge commit, just pointer movement).
- Practice the sync pattern: while on a feature branch, periodically
git merge mainto absorb main's changes. Notice how the feature branch's history grows.- Read a real merge commit's
git show <merge-commit>output. Note the dual-parent structure shown in the header.- Compare the same set of work merged versus rebased (Module 8). Note the difference in resulting history structure.