# git merge-base --fork-point and --is-ancestor in CI

> git merge-base finds the common ancestor of two branches; --fork-point and --is-ancestor power PR diffs and gating. Reference for the flags and shallow-clone errors.

Source: https://latchkey.dev/learn/command-reference/git-merge-base-fork-point  
Updated: 2026-06-30

git merge-base A B prints the best common ancestor commit; --is-ancestor tests a relationship via exit code, and --fork-point uses the reflog to find where a branch diverged.

PR pipelines diff a branch against the point it forked from, not the current tip of main. merge-base computes that point. --is-ancestor gives a clean exit code to gate on, which is friendlier in scripts than parsing output.

## What it does

git merge-base finds the commit where two histories last shared a common ancestor, the natural base for a three-dot diff. --is-ancestor A B exits 0 if A is an ancestor of B and 1 otherwise. --fork-point consults the target branch reflog to find the divergence point even after the upstream moved.

## Common usage

```Terminal
BASE=$(git merge-base origin/main HEAD)
git diff "$BASE" HEAD --name-only
# gate: is this commit already on main?
git merge-base --is-ancestor HEAD origin/main && echo "already merged"
# divergence point accounting for rebased upstream
git merge-base --fork-point origin/main feature
```

## Options

| Flag | What it does |
| --- | --- |
| (default) A B | Print the best common ancestor SHA |
| --is-ancestor A B | Exit 0 if A is an ancestor of B, else 1 |
| --fork-point <ref> [<branch>] | Find divergence using the reflog |
| --octopus | Find a common ancestor of more than two commits |
| --all | Print all merge bases when several exist |

## In CI

This is the single biggest shallow-clone trap: with fetch-depth: 1 the common ancestor is below the clone boundary and merge-base returns nothing or the wrong commit, so PR diffs misfire. Use fetch-depth: 0 or deepen until the base is present. --is-ancestor is exit-code based; use it directly in if/&& rather than capturing stdout.

## Common errors in CI

Empty output (and exit 1) from merge-base means no common ancestor was found locally, the classic shallow-clone symptom; --unshallow or deepen. "fatal: Not a valid commit name origin/main" means the remote-tracking ref was not fetched. --fork-point printing nothing means the reflog lacks the needed entries (fresh clone).

## Using this in CI

CI checkouts are shallow and detached by default, which changes the answer this command gives you. Commands that read history, branch names, or tags need the checkout configured for it.

```.github/workflows/ci.yml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0   # history, tags, and git describe all need this

- run: |
    git rev-parse --is-shallow-repository   # expect false
    git rev-parse --abbrev-ref HEAD          # prints HEAD when detached
```

> `git rev-parse --abbrev-ref HEAD` returns the literal string `HEAD` on a detached checkout rather than a branch name. On GitHub Actions read `github.ref_name` instead; the git command cannot know what it was checked out for.

## FAQ

### git merge-base --fork-point and --is-ancestor in CI?

PR pipelines diff a branch against the point it forked from, not the current tip of main. merge-base computes that point. --is-ancestor gives a clean exit code to gate on, which is friendlier in scripts than parsing output.

### What it does?

git merge-base finds the commit where two histories last shared a common ancestor, the natural base for a three-dot diff. --is-ancestor A B exits 0 if A is an ancestor of B and 1 otherwise. --fork-point consults the target branch reflog to find the divergence point even after the upstream moved.

### In CI?

This is the single biggest shallow-clone trap: with fetch-depth: 1 the common ancestor is below the clone boundary and merge-base returns nothing or the wrong commit, so PR diffs misfire. Use fetch-depth: 0 or deepen until the base is present. --is-ancestor is exit-code based; use it directly in if/&& rather than capturing stdout.

### Common errors in CI?

Empty output (and exit 1) from merge-base means no common ancestor was found locally, the classic shallow-clone symptom; --unshallow or deepen. "fatal: Not a valid commit name origin/main" means the remote-tracking ref was not fetched. --fork-point printing nothing means the reflog lacks the needed entries (fresh clone).

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
