skip to content

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

level: seniorimportance: nice to knowfreq 24%

answer

  1. The graph forgets where refs used to point
  2. Something local does remember
  3. It matters after an upstream rewrite
  4. Useless in a fresh clone
  5. One rebase mode turns it on by default

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.

solid answer

~50 s

Plain `git merge-base main topic` computes the best common ancestor from the commit graph as it stands now. That answer is wrong when `main` has been rewritten or rewound: the commit you actually branched from may no longer be reachable from `main`, so the computed base sits further back than reality and Git treats commits you never wrote as yours. `git merge-base --fork-point main topic` also reads `main`'s reflog — the record of every value that ref has held locally — and looks for an old position that is an ancestor of `topic`. That recovers the real fork point. Two limits follow directly: it depends on local reflog entries, so it produces nothing useful in a fresh clone or after the reflog expires, and it is therefore a heuristic. `git rebase` applies fork-point mode by default when you give it no explicit upstream argument, and not when you name one.

code

bash · 4 lines
bash
git merge-base main topic             # graph only
git merge-base --fork-point main topic  # also reads main's reflog

git reflog show main                  # the historical positions it consults

go deeper

for a junior

It is enough to know that git merge-base finds a common ancestor and that a variant exists for the case where the upstream branch was rewritten.

for a middle

Explain that this mode also consults the reference branch's reflog, so it can find a fork point that is no longer reachable from the branch's current tip.

for a senior

Show the diagnostic use: recognize a rebase replaying commits you never wrote as a rewritten upstream, check git reflog show for the ref's old positions, and know the mode is on by default only when rebase is given no explicit upstream.

for a principal

Own the reproducibility boundary: this is a local heuristic that depends on what one clone observed, so it must never underpin shared tooling or CI, where a recorded base commit is the durable alternative.

## The problem it exists for The commit graph only remembers where refs point *now*. If `origin/main` was rebased or reset upstream and you fetched the new version, the commit you originally branched from may have vanished from `main`'s ancestry — it was replaced by a rewritten equivalent with a different hash. Ask plain `git merge-base main topic` in that state and it walks the current graph, finds the most recent commit still common to both, and returns something *older* than where you really forked. Everything between that older base and your true fork point is then attributed to your branch. Rebase or a three-dot diff computed from that base will try to replay or display commits you never authored, which typically arrives as a pile of conflicts on changes that were never yours. ## What --fork-point adds `git merge-base --fork-point <ref> [<commit>]` additionally consults the **reflog** of `<ref>`: the local, per-repository log of every value that reference has held. Old positions of `main` are still recorded there even when they are unreachable in the graph. Git looks through those historical positions for one that is an ancestor of your branch, and returns that as the fork point. That is exactly the commit `main` pointed at when you branched, so "what did I add" comes out right even though `main` has since been rewritten. ## The consequences of relying on the reflog The reflog is **local and per-repository**. It records what *your* clone observed. Therefore: - On a fresh clone there are no historical positions to consult, so `--fork-point` has nothing to work with and produces no answer. - Reflog entries expire, so old branches can fall off the back of the mechanism. - Two clones can legitimately disagree, because they observed different sequences of ref updates. - CI, which clones fresh, cannot reproduce a fork point your laptop can. So this is a **heuristic that improves interactive work on a machine that watched the rewrite happen**, not a property of the repository that everyone can recompute. Never build a shared invariant on it. ## Its role in rebase `git rebase` uses fork-point mode by default when you run it with **no** explicit upstream argument — the case where it takes the branch's configured upstream — and does not use it when you name an upstream explicitly. `--fork-point` and `--no-fork-point` let you override either way. The reason is the same problem described above: rebasing onto an upstream that was itself rewritten should replay only *your* commits, and only the reflog knows which ones those are. When a rebase suddenly wants to replay dozens of commits that are not yours, the upstream was rewritten and the fork point was not (or could not be) recovered. Passing `--fork-point`, or naming the correct base explicitly, is the fix; the diagnosis is `git reflog show origin/main` to see what that ref has been. ## Distinguishing it from --is-ancestor and --all The `merge-base` forms answer different questions: - plain — best common ancestor from the current graph. - `--all` — every best common ancestor (multiple means a criss-cross shape). - `--is-ancestor` — a yes/no containment predicate, answered by exit status. - `--fork-point` — where the branch was created, using ref history rather than only the graph. Only the last one reads anything outside the commit graph, which is what makes it both more useful and less reproducible than the others. ## When to reach for it - Your upstream branch gets rewritten (rewound, rebased, or reset) and you have local work on top of an old position. - You want "the commits I added" and the graph-derived base is clearly too old. - A rebase proposes replaying commits you do not recognize. And when not to: in scripts that must produce the same answer on every machine, in CI, or anywhere a fresh clone is involved. There, compute the base explicitly from a commit you record yourself, or use plain `merge-base` and accept its definition.

  • Why does --fork-point return nothing in a fresh clone?
    It depends on the reflog of the reference branch, which is local and only records ref updates this clone observed. A fresh clone has no history of previous positions for that ref, so there is nothing to search and the heuristic has no answer to give.
  • When does git rebase apply fork-point mode by default?
    When you run it without an explicit upstream argument, so it uses the branch's configured upstream. Naming an upstream explicitly turns it off by default. `--fork-point` and `--no-fork-point` override either way.
  • Why should scripts avoid depending on --fork-point?
    Because the answer depends on what a particular clone observed, not on the repository's content. Two machines can legitimately differ, and CI with a fresh clone gets nothing. Anything shared should compute its base from the graph or from a commit hash recorded explicitly.
  • What symptom suggests you needed a fork point and did not get one?
    A rebase or diff that includes commits you never wrote. The graph-derived base landed further back than your real branch point because the upstream was rewritten, so upstream commits are being attributed to your branch and replayed against you.

saying these in an interview costs you the question

  • Thinks --fork-point is just a faster merge-base
  • Assumes it works identically in a fresh clone or CI
  • Believes it reads the remote's reflog over the network
  • Uses it in shared scripts and expects reproducible answers
  • Cannot connect it to upstream branches being rewritten

context