What is a criss-cross history in Git, and how does a merge choose a base then?
answer
- Two branches merged into each other
- More than one best common ancestor now
- Git will not pick one arbitrarily
- It merges the candidates instead
- The result is never committed
basics
~20 sA 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.
solid answer
~50 sNormally two branches have one best common ancestor. Once they have exchanged merges in both directions — `main` merged into `topic` and `topic` merged into `main`, then both continue — there can be two or more common ancestors, none of which is an ancestor of the other. `git merge-base --all main topic` prints them. Picking one arbitrarily would misreport which side changed what, so the recursive family of strategies instead merges the candidate bases with each other, recursively, producing a **virtual merge base** that exists only for the duration of the merge and is never committed. The real three-way merge then runs against that. The practical effects: a criss-cross merge can conflict inside the base computation itself, the conflicts you are shown may reference content from no single real commit, and the whole thing is slower than a merge with one clean base.
code
bash · 6 linesgit merge-base --all main topic
# 4c1d0ab
# 9f2b6c1 <- two best ancestors: criss-cross
git merge-base main topic
# 9f2b6c1 <- the plain form still prints just onego deeper
It is enough to know that two branches usually have one common ancestor, and that merging branches into each other repeatedly can create more than one.
Explain how the shape arises from merges in both directions, and that git merge-base --all reveals multiple candidates where the plain form shows only one.
Describe the resolution: the candidate bases are merged with each other recursively into a virtual merge base that is never committed, and connect that to what you observe — slower merges and conflicts whose base content matches no real commit.
Own the integration direction. Decide which branch pairs may exchange merges both ways and for how long, since criss-cross histories are a direct product of long-lived bidirectional integration rather than anything Git does wrong.
## How the shape arises Start with `main` and `topic` diverging from commit `A`. Then: 1. Someone merges `main` into `topic` to pick up the latest mainline — call the result `M1`, reachable from `topic`. 2. Someone else merges `topic` into `main` at roughly the same time — call it `M2`, reachable from `main`. 3. Both branches keep committing. Now ask for the common ancestors of the two tips. `M1` is reachable from `topic` and, through the merge in step 2, from `main` as well. `M2` is likewise reachable from both. Neither is an ancestor of the other. Two best common ancestors — the graph crosses over itself, hence "criss-cross". Running `git merge-base --all main topic` and seeing more than one line is the diagnostic. ## Why picking one is wrong The three-way merge derives "who changed this" from the base. If two valid bases disagree about a region, choosing either one arbitrarily makes changes look like they came from a side that did not make them. In practice this produces two bad outcomes: conflicts in places both sides actually agreed on, and — worse — silent resurrection of a change that one side deliberately reverted, because the chosen base did not know about the revert. ## The virtual merge base The recursive family of strategies solves it by **merging the bases**. If there are two candidate bases, Git merges them with each other — recursively, since those two may themselves have multiple bases — and uses the resulting tree as the base for the real merge. That synthetic tree is the virtual merge base. It exists in memory (and, where needed, as objects that are never referenced by any branch), it is not committed, and it does not appear in your history. The strategy name reflects the algorithm: the classic implementation is called `recursive` precisely because of this recursion over merge bases, and the modern default reimplementation inherits the behavior. (Which named strategy runs, and how you select one, is a separate matter — the base computation described here is common to them.) ## What you actually observe - **Conflicts during base construction.** Merging the two bases can itself conflict. Git resolves those internally as best it can, and the leftovers propagate into the base content you are then asked to merge against. This is why a criss-cross merge sometimes shows conflict content that does not match any commit you can find in the log — the "original" side of the conflict came from a synthesized tree. - **Slower merges.** Recursion over multiple bases costs real work on large histories. - **Surprising conflicts in untouched code.** When the bases disagree, regions neither current side edited can still surface. ## Diagnosing it `git merge-base --all main topic` printing more than one line means a criss-cross, and `git log --graph --oneline main topic` shows the two crossing merges that created it. If a merge behaves strangely and `--all` prints multiple hashes, you have your explanation. From there, the useful questions are: which two merges crossed, and can the branch be integrated in one direction only from now on? ## How teams get here Almost always by merging in both directions. Repeatedly merging `main` into a long-lived branch *and* periodically merging that branch back into `main` guarantees the shape. Release branches that receive backports and also get merged forward are another reliable source. The shape is not an error and Git handles it — but it is a sign that two lines of development are being exchanged in both directions over a long period, which is what makes bases multiply. ## Reducing it - Integrate in **one** direction per branch pair where you can: bring mainline into the topic branch, then merge the topic branch once and delete it. - Keep branches short-lived, so there is no time for two crossing merges to accumulate. - Complete the exchange promptly: a branch that is merged back and then kept alive for another month is the raw material for the next criss-cross. ## What to say in an interview "Two branches that have merged into each other in both directions can have several best common ancestors. Git does not pick one — the recursive strategy merges the candidate bases with each other, recursively, into a virtual merge base and merges against that. `git merge-base --all` shows you when there is more than one." Then the observable consequence: conflicts can reference content from a synthesized tree rather than any real commit.
- Is the virtual merge base written into history?No. It is a synthesized tree used only for the duration of the merge. Your merge commit records the two real parents you merged; nothing references the virtual base, and it never appears in `git log`. Any objects created while building it are unreferenced and eventually collected.
- Why can a criss-cross merge conflict in code neither branch touched recently?Because the conflict can originate in the base computation. When the two candidate bases disagree about a region, merging them may leave unresolved content in the synthesized base, and that propagates into the real merge — even though neither current tip changed that region since.
- What workflow habit most reliably produces criss-cross histories?Merging in both directions between the same pair of long-lived branches: repeatedly pulling mainline into a branch while also merging that branch back. Each crossing pair adds candidate ancestors. Integrating in one direction and keeping branches short-lived avoids it almost entirely.
- How do you confirm a strange merge was caused by multiple bases?Run `git merge-base --all <ours> <theirs>`. More than one hash means the merge had to synthesize a base, which explains conflicts whose "original" side matches no commit in the log. `git log --graph` then shows the two merges that crossed.
Two editors each incorporated the other's draft once, so there is no single agreed original. Rather than picking one of the two rival originals, you first reconcile them into a working reference copy, then use that to compare both current drafts.
saying these in an interview costs you the question
- Says Git just picks the newest of several merge bases
- Thinks the virtual merge base becomes a commit in history
- Believes multiple merge bases mean the repository is corrupt
- Claims criss-cross merges are impossible if you always pull first
- Cannot name a command that reveals more than one base