Find the Right Rebase Point with git merge-base

Rebase against the wrong commit and you replay changes that are already merged, or miss a rewrite that happened upstream. git merge-base finds the actual shared ancestor so you do not have to guess, and it answers a narrower question scripts ask constantly: is this commit already contained in that one. The examples use Git 2.43.0 from the Debian package git-man 1:2.43.0-1ubuntu7.3. Allow about fifteen minutes and work in a repository with at least two branches.

Checkpoint: This command only reads repository history and reflogs. It does not merge, rebase, reset, or change a branch. No elevated privileges are needed.

1. Find the shared ancestor of two commits

Confirm the command and version first:

$ command -v git
/usr/bin/git
$ git --version
git version 2.43.0

Ask for the merge base of any two names: a local branch, a remote-tracking branch, a tag, or a full commit ID.

$ git merge-base main topic
<40-character commit ID>

The result is one commit reachable from both tips, picked as a best common ancestor. In a normal branch layout that is the point where the branches diverged. Only the object name is printed, followed by a newline. Save it if you need to reuse the point in another command:

$ base=$(git merge-base main topic)
$ git show --no-patch --oneline "$base"
<commit ID> <common ancestor subject>

After a criss-cross merge, Git can have more than one equally good merge base. Without --all, the manual leaves which one is picked unspecified. If your decision hinges on every candidate, ask for them explicitly (step 3).

2. Ask the direct ancestry question

For scripts, never compare the printed merge-base text against a branch name by eye. Use --is-ancestor when the question is a plain yes or no: is the first commit already contained in the second.

$ git merge-base --is-ancestor main origin/main
$ status=$?
$ printf 'status=%s\n' "$status"
status=0

Status 0 means main is an ancestor of origin/main. Status 1 means it is not. Any other non-zero status is an error, such as an invalid revision, so do not collapse every failure into a simple "not an ancestor" result.

A fast-forward check usually asks whether the current release tip is already contained in the proposed new tip:

if git merge-base --is-ancestor release candidate; then
    printf '%s\n' 'candidate can fast-forward release'
else
    status=$?
    if [ "$status" -eq 1 ]; then
        printf '%s\n' 'candidate does not contain release'
    else
        printf 'ancestry check failed: status %s\n' "$status" >&2
        exit "$status"
    fi
fi

This checks history only. It does not update either branch and it proves nothing about whether the candidate builds or passes tests.

3. Compare several tips correctly

With three or more plain arguments, the default behaviour is easy to misread. git merge-base A B C compares A against a hypothetical merge of B and C, it is not simply the ancestor common to all three. Use this mode only when that hypothetical merge is genuinely what you mean, and label the arguments clearly in any script that uses it.

To find the best ancestor shared by every supplied tip for an n-way merge, use --octopus:

$ git merge-base --octopus main topic maintenance
<commit ID shared by all three tips>

To see every best result for two tips, add --all:

$ git merge-base --all main topic
<one commit ID per line>

Both modes report commit IDs, not verdicts. Inspect any result with git show --no-patch before treating it as a release or rebase boundary.

4. Use fork-point mode before replaying topic commits

Ordinary ancestry can give the wrong rebase boundary when a remote-tracking branch was rewound and rebuilt. Say topic was branched from an older tip of origin/main, and that remote branch has since discarded that tip. The old tip may still sit in the reflog.

Ask Git to consider those earlier reference tips:

$ fork_point=$(git merge-base --fork-point origin/main topic)
$ printf '%s\n' "$fork_point"
<commit ID where topic was based>

The first argument is the reference whose reflog gets examined. The optional second argument is the history being tested; if you leave it out, Git uses HEAD. This mode reads reflog evidence on top of the graph, so it is not a drop-in replacement for git merge-base origin/main topic.

Review the selected commit and the commits that would be replayed:

$ git show --no-patch --oneline "$fork_point"
<commit ID> <old origin/main subject>
$ git log --oneline "$fork_point"..topic
<topic commits, newest first>

Warning: Rebasing rewrites commit IDs and can require a force update once the branch is published. Review the range and coordinate with anyone using the branch before running it. The documented pattern, once you are sure, is:

$ git rebase --onto origin/main "$fork_point" topic

If the rebase stops, resolve each conflict, run git add for the resolved paths, then use git rebase --continue. To abandon the in-progress rebase and return to its starting state, run git rebase --abort. Fork-point mode is not guaranteed to find the old tip: reflog entries expire, and it only recognises tips that were recorded for the reference you name.

5. Diagnose surprising or empty results

Check that every name actually resolves in the repository you intended:

$ git rev-parse --verify main
<commit ID>
$ git rev-parse --verify topic
<commit ID>

If revision verification fails, fix the branch or tag name rather than reading the merge-base error as a history result. If the branches genuinely have no common ancestor, ordinary merge-base mode exits unsuccessfully and prints no commit ID.

If --fork-point fails while ordinary merge-base succeeds, inspect the reference reflog without changing it:

$ git reflog show --date=iso origin/main
<recent origin/main tips and their dates>

The old starting tip may have expired, or topic may have been created from a commit that was never actually the tip of origin/main. In that case, stop and choose the boundary manually after reviewing the graph. Do not substitute an unverified parent just to make a rebase command run.

Done means