skip to content

questions

5

In Git, what is a three-way merge and which three commits does it use?

level: juniorimportance: must knowfreq 68%

answer

  1. Two versions cannot show who changed what
  2. A reference point from before the split
  3. git merge-base names it
  4. Compare each side against that point

basics

~20 s

A three-way merge combines two branch tips using their best common ancestor, the merge base, as a reference point. Comparing each tip against that base tells Git which side changed what, so it can combine changes instead of guessing.

solid answer

~40 s

When two branches have diverged, comparing them to each other is not enough: if a line differs, Git cannot tell which side changed it. So it uses three commits — your tip, their tip, and the **merge base**, the best common ancestor of the two, found with `git merge-base`. Git compares base→ours and base→theirs. Where only one side changed a region, that side's version wins with no interaction from you. Where both changed the same region differently, Git reports a conflict, because the base proves both are edits rather than one being the original. This is why merging a branch that is simply behind can be a fast-forward with nothing to compute, and why merging two long-diverged branches produces conflicts only in the places both teams touched.

code

bash · 5 lines
bash
git merge-base main topic
# 9f2b6c1  <- the common ancestor the merge will use

git diff 9f2b6c1 main     # what our side changed since the base
git diff 9f2b6c1 topic    # what their side changed since the base

go deeper

for a junior

Be able to name the three inputs — your tip, their tip, and the common ancestor — and say that the ancestor is what lets Git tell which side made a change.

for a middle

Explain the decision table region by region: one side changed means take that side, both changed differently means conflict, and describe git merge-base as the command that finds the reference point.

for a senior

Bring in the shapes that matter in practice: base equal to one tip, absent base on unrelated histories, per-region rather than per-file granularity, and semantic conflicts that merge cleanly but break the build.

for a principal

Frame the consequence for integration policy — how long branches live determines how far the base drifts, and how much of the merge is reconsidered each time — and set expectations that merges are tested, not trusted.

## Why two versions are not enough Suppose two files differ on line 40. With only those two versions, no tool can tell you whether A added the line, B deleted it, or both rewrote it. A two-way diff describes *difference*; a merge needs *direction of change*. The missing ingredient is the version both sides started from. ## The three inputs 1. **Ours** — the tip of the branch you are on (`HEAD`). 2. **Theirs** — the tip of the branch you are merging in. 3. **The base** — the best common ancestor of those two commits. With the base in hand, every region of every file has a clear story: | base → ours | base → theirs | result | |---|---|---| | unchanged | unchanged | keep base content | | changed | unchanged | take ours | | unchanged | changed | take theirs | | changed identically | changed identically | take it once | | changed differently | changed differently | **conflict** | That table *is* the three-way merge. Everything else — strategies, rename detection, conflict presentation — is machinery around it. ## Finding the base Git computes the base by walking the commit graph from both tips and finding the best common ancestor: a commit reachable from both, with no other common ancestor that is a descendant of it. You can ask for it directly with `git merge-base main topic`. This is also what the `...` notation in `git diff main...topic` uses — it diffs the base against `topic`, which is "what topic added", as opposed to `git diff main..topic`, which is the plain two-endpoint difference. ## Special shapes **One branch is an ancestor of the other.** Then the base *is* the older tip. There is nothing on your side that the other does not already contain, so no content merge is needed at all — Git can simply move your branch pointer forward. The three-way computation degenerates to nothing. **No common ancestor at all.** Two unrelated histories, for example after grafting a second project into a repository, have no merge base. Modern Git refuses such a merge unless you pass `--allow-unrelated-histories`, precisely because a merge without a base cannot distinguish additions from deletions and everything collides. **More than one common ancestor.** Histories that cross-merge can have several equally good ancestors, and Git has to reduce them to one reference point before the table above can be applied. ## Merging is per-region, not per-file A frequent misconception is that a file changed on both sides always conflicts. It does not: the merge works region by region. Two people editing opposite ends of a 500-line file merge cleanly. Conflict requires *overlapping* — or adjacent enough that the algorithm cannot separate them — changes on both sides. Equally, clean does not mean correct. Git compares text, not meaning. One side renaming a function and the other side adding a new call to the old name merges without conflict and breaks the build. Semantic conflicts are exactly why merges get tested rather than trusted. ## What the merge produces When a real three-way merge runs to completion, the result is a commit with **two parents** — ours first, theirs second — whose tree is the combined content. The two-parent shape is what records that these histories were joined, and it is what lets a future merge of the same branches find a much more recent base: the previous merge is now a common ancestor, so Git only reconsiders work done since. ## How to say it in an interview "Git merges by comparing both sides against their common ancestor. Where only one side changed, that change is taken; where both changed the same place differently, that is a conflict. Finding the common ancestor is `git merge-base`." That sentence, plus the observation that it works per region rather than per file, covers what the question is actually testing.

  • What happens when the merge base is the tip of one of the two branches?
    Then one branch already contains everything the other has, and there is nothing to compute. Git can advance the lagging branch pointer straight to the other tip, which is why merging a branch that never diverged is trivial and produces no merge commit by default.
  • What does git merge-base main topic actually return when several ancestors qualify?
    It prints one best common ancestor: a commit reachable from both tips with no other common ancestor descended from it. When several are equally good, `git merge-base --all` lists them all, and the merge itself has to reduce them to a single reference point before merging content.
  • Does a clean merge mean the result is correct?
    No. The merge compares text regions, not meaning. One side renaming a symbol and the other adding a call to the old name merges silently and fails to compile. Clean means no textual disagreement, which is why merge results still get built and tested.
  • Why does merging two branches with no common ancestor fail by default?
    Without a base, every region looks like it was added by both sides, so nothing can be auto-resolved and Git cannot tell additions from deletions. Modern Git refuses outright and requires `--allow-unrelated-histories` to acknowledge you really mean to join two independent histories.

Two people edit copies of the same contract. To combine them you need the original: only against it can you tell an insertion from a deletion, and only then does an edit both made in the same clause stand out as a real disagreement.

saying these in an interview costs you the question

  • Says Git merges by diffing the two branch tips against each other
  • Thinks any file touched on both sides conflicts
  • Believes a clean merge guarantees working code
  • Cannot name the common ancestor as the third input
  • Says the merge base is always the branch point you remember creating

context

open as a page

In a Git merge, when is a change applied automatically and when does it become a conflict?

level: middleimportance: must knowfreq 70%

basics

~20 s

Git compares both sides against the merge base region by region. A region changed on only one side, or changed identically on both, is applied automatically; a region both sides changed differently is a conflict Git leaves for you.

open as a page

What does git merge-base --is-ancestor report, and how do scripts use its exit code?

level: middleimportance: should knowfreq 35%

basics

~20 s

git merge-base --is-ancestor A B prints nothing and exits 0 when commit A is reachable from commit B, and exits 1 when it is not. Scripts branch on that status to test containment without parsing any output.

open as a page

What is a criss-cross history in Git, and how does a merge choose a base then?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A criss-cross history arises when two branches merge into each other in both directions, leaving several equally good common ancestors. Git merges those bases together into a single virtual merge base and runs the ordinary three-way merge against it.

open as a page

What does git merge-base --fork-point do that a plain git merge-base does not?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

It consults the reflog of the reference branch to find where your branch actually forked, even if that commit is no longer reachable from the branch's current tip. Plain git merge-base only looks at commits still in the graph.

open as a page