AnyLearn
All lessons
Programmingadvanced

Merge and Rebase: Two Answers to Divergence

Every operation so far moved a pointer. Merging is the first that has to decide something: two branches changed the same project and one result must come out. This lesson covers the three-way merge and the merge base it depends on, what a conflict actually is, and why rebase produces different commits rather than moving them.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 8

The merge base is the whole trick

Two branches have diverged. Comparing their tips gives a set of differences and no way to interpret them: a line present in one and absent in the other could be an addition on one side or a deletion on the other, and those need opposite outcomes.

The missing information is the common ancestor, and Git computes it.

$ git merge-base main feature
a9cb607a61d6aaee5145894e92fa5f146cbdca80

That is the most recent commit reachable from both, and it is available for free because every commit records its parents. Finding it is a graph traversal over information already stored.

With three points instead of two, every line has an interpretable history. Compare the base to each side separately, and it becomes clear which side changed what.

This is a three-way merge, and the base is what makes it work. Systems without a reliable common ancestor cannot do this, which is why merging was historically painful and why cheap, accurate merging is often cited as Git's main practical advance.

Full lesson text

All 8 steps on one page, for reading, reference, and search.

Show

1. The merge base is the whole trick

Two branches have diverged. Comparing their tips gives a set of differences and no way to interpret them: a line present in one and absent in the other could be an addition on one side or a deletion on the other, and those need opposite outcomes.

The missing information is the common ancestor, and Git computes it.

$ git merge-base main feature
a9cb607a61d6aaee5145894e92fa5f146cbdca80

That is the most recent commit reachable from both, and it is available for free because every commit records its parents. Finding it is a graph traversal over information already stored.

With three points instead of two, every line has an interpretable history. Compare the base to each side separately, and it becomes clear which side changed what.

This is a three-way merge, and the base is what makes it work. Systems without a reliable common ancestor cannot do this, which is why merging was historically painful and why cheap, accurate merging is often cited as Git's main practical advance.

2. The rule, per region

For each region of each file, the algorithm compares the base against both sides and applies one rule.

Base to oursBase to theirsResult
UnchangedUnchangedKeep the base
ChangedUnchangedTake ours
UnchangedChangedTake theirs
Changed identicallyChanged identicallyTake either
Changed differentlyChanged differentlyConflict

Only the last row needs a human. Everything else has an unambiguous answer, which is why most merges complete without intervention even when both branches touched the same file.

That point is worth emphasising because the intuition is wrong. Two people editing the same file merges cleanly. It is only a problem when they edited the same region in different ways, and the algorithm's granularity is the region rather than the file.

A conflict is therefore not an error. It is the algorithm reporting that it has no basis for choosing, and asking rather than guessing.

That is the correct behaviour, and it is also why a tool that silently resolves conflicts is dangerous: the cases it resolves are precisely the ones with no defensible answer.

3. What a merge commit is

When the result is computed, Git creates a commit like any other, with one difference visible in the object.

$ git cat-file -p HEAD
tree 69dde178328624a81262cf4eb9283b91fbfd8a37
parent c1447f0caf00577fc432a7022b43020f9da72c63
parent cc8e3e195cca577668c7e26ac6524211c422c40f

Two parent lines. That is the entire representation of a merge: an ordinary commit whose tree is the merged result and which records both histories as ancestors.

The ordering is meaningful. The first parent is the branch you were on; the second is what you merged in. That is what --first-parent follows, and it is why a log restricted to first parents shows the mainline without every merged branch's internal commits.

A fast-forward is the case where no merge is needed. If your branch is an ancestor of the other, there is nothing to combine, so Git just moves your ref forward. No commit is created and the history stays linear.

That is why some merges produce a merge commit and others silently do not, which looks inconsistent until you know the rule: a merge commit appears only when the histories actually diverged.

4. Merge against rebase

The right branch is where the misconception lives. Rebase does not move commits. It cannot: a commit's hash is derived from its content including its parent, so a commit with a different parent is a different object with a different name.

What rebase does is take the diff each commit introduced, apply it to the new base, and create a new commit with the same message, the same author, a new committer timestamp and a new hash.

The originals still exist, now unreferenced, findable in the reflog until garbage collection.

So the two operations differ in what they preserve. Merge preserves the graph exactly and adds a node recording that a combination happened. Rebase discards the graph's shape and produces a story that reads as though the work had been done sequentially.

Neither is more correct. They optimise for different things: merge for an accurate record, rebase for a readable one.

flowchart TD
A["Two diverged branches"] --> B["Merge"]
A --> C["Rebase"]
B --> D["New commit with two parents"]
D --> E["Both histories preserved exactly"]
E --> F["Graph shows the divergence"]
C --> G["Replay each commit onto the new base"]
G --> H["New commits, new hashes"]
H --> I["Linear history; originals now unreferenced"]

5. Why rebasing shared branches breaks things

The rule everyone repeats is not to rebase a branch others have pulled. The mechanism is worth knowing, because it turns a superstition into an obvious consequence.

A colleague's repository has your original commits, and their branch points at one of them. After you rebase and force-push, the remote holds different objects with different hashes representing the same work.

When they pull, Git sees their branch and the remote's as diverged, because neither tip is an ancestor of the other. Its correct response is to merge them, which produces a history containing both copies of every rebased commit.

The symptom is duplicated commits and conflicts between a change and itself, and it is confusing precisely because the two sides are semantically identical and structurally unrelated.

So the rule follows from the object model rather than from convention: rebasing replaces objects, and anyone holding the old ones has no way to know the new ones supersede them.

The corollary is the useful part. Rebasing is entirely safe on a branch nobody else has, which is most branches most of the time. "Never rebase" is too strong; "never rebase what others have pulled" is the actual constraint.

6. Resolving a conflict

When the algorithm cannot decide, it writes both versions into the file with markers and stops.

<<<<<<< HEAD
connect(timeout=30)
=======
connect(timeout=60, retries=3)
>>>>>>> feature

The top section is the current branch, the bottom is what is being merged. Resolving means editing the file to what it should be, which is frequently neither side verbatim.

That is the part people get wrong. The markers present a choice between two options, and the correct result is often a combination: the other side's retries with your timeout, or a value that accounts for both intentions. Picking a side because the interface suggests picking a side is how merge resolutions introduce bugs.

The file is then staged to record that it is resolved, and the merge is committed.

Two settings are worth knowing. merge.conflictStyle = diff3 adds a third section showing the base, which converts the question from "which of these two" into "what did each side change and why", and that is usually decisive.

And rerere records how you resolved a given conflict and replays it if the same one appears again, which matters during a long rebase where the same conflict recurs at every commit.

7. What the algorithm cannot see

A clean merge means no textual conflict. It does not mean the result works, and the gap is where merge-related bugs live.

The algorithm compares text. It has no model of the program.

So: one branch renames a function, another adds a call to the old name in a different file. No region overlaps, the merge is clean, and the result does not compile. One branch changes a function's units from seconds to milliseconds, another adds a caller passing seconds. Clean merge, silent bug.

This is a semantic conflict, and no line-based tool can detect it, because detecting it requires understanding what the code means.

The defence is not a better merge algorithm. It is that a merge result is new code that nobody has ever tested, even though every line came from somewhere reviewed. Running the tests after merging, rather than only on each branch beforehand, is the only thing that catches it, which is precisely what continuous integration on the merge result is for.

Worth noting how this connects: the lesson What Undecidability Costs Real Tools explains why a general semantic merge is not available, since determining whether two changes interfere is a semantic property of programs.

8. Choosing between them

MergeRebase
ProducesOne commit, two parentsNew commits, new hashes
HistoryPreserved exactlyRewritten as linear
Records that work was parallelYesNo
Safe on shared branchesYesNo
ConflictsOnce, at the mergePossibly once per commit
Bisect and blameMerge commits add noiseCleaner, linear
Reverting the whole featureOne revert of the mergeRevert each commit

The fourth row decides most cases on its own, and the fifth is the practical annoyance: a rebase over many commits can present the same conflict repeatedly, which is what rerere exists to absorb.

The common convention across teams combines them rather than choosing. Rebase your own feature branch onto the latest mainline while you work, because it is yours and nobody has it. Then merge it into the mainline, because that records honestly that a feature was developed and integrated at a point.

The transferable observation is that the argument is not technical. Both produce a working tree; they differ in what story the history tells, and the right answer depends on whether your history is an audit record or a narrative for future readers.

Teams that argue about it are usually disagreeing about that question rather than about Git.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. Why does a three-way merge need the common ancestor?
    • Without it, a line present on one side only could be an addition or a deletion, and those need opposite outcomes
    • To determine which branch is newer
    • To compute the order in which commits should be replayed
    • To decide which side becomes the first parent
  2. Two developers edit the same file on different branches. What happens on merge?
    • A conflict, since the file was touched twice
    • It merges cleanly unless they changed the same region differently
    • The later commit silently wins
    • Git refuses the merge and requires a rebase
  3. What does rebase actually do to the original commits?
    • Moves them onto the new base, preserving their hashes
    • Deletes them and their objects immediately
    • Rewrites their parent field in place
    • Leaves them in the database, unreferenced, while creating new commits with new hashes
  4. Why does rebasing a branch others have pulled cause duplicate commits?
    • Because the reflog is shared between clones
    • Because force-push corrupts the remote's object database
    • Because their branch and the rewritten remote branch have no ancestry relation, so Git merges both copies
    • Because rebase does not preserve commit messages
  5. What is a semantic conflict?
    • Two branches changing the same region differently
    • Changes that merge cleanly as text but produce broken or wrong code together
    • A conflict Git resolves automatically using rerere
    • A merge that fails because the base cannot be computed

Related lessons

Programming
advanced

Undo: Reset, Revert, Restore, and Getting Work Back

Git's undo commands are notoriously confusing because they are usually memorised as recipes. Read against the object model they separate cleanly: each acts on a different one of the three places content lives. This lesson maps them, covers recovery from the accidents that feel unrecoverable, and states what genuinely cannot be undone.

8 steps·~12 min
Programming
advanced

Refs: A Branch Is a File Containing a Hash

Hashes are unusable by hand, so Git names them. A branch is not a copy of anything, not a directory, and not a container for commits: it is a forty-character file. Once that is clear, checkout, reset, detached HEAD and the reflog stop being separate concepts and become one operation on one kind of file.

8 steps·~12 min
Programming
intermediate

The Object Database: Git Is a Content-Addressed Store

Git is usually taught as a set of commands. Underneath it is a key-value store where the key is a hash of the content, and four object types built on that one idea. This lesson derives the hash by hand, shows what a commit actually contains, and explains why the design makes history tamper-evident.

8 steps·~12 min
Programming
advanced

The Design Philosophy: What Go Left Out

Go is unusual for what it refuses to include. Errors are values, not exceptions. Interfaces are satisfied without declaring it. Formatting is not a choice. This lesson works through the omissions, the reasoning behind each, and the real costs, because every one of them trades expressiveness for something else.

8 steps·~12 min