SIGN IN SIGN UP

fix(read): fail when --from and --to share no merge-base #4555 (#4754)

* test: add tests for missing coverage

When `--from` is used, there's no check that the history between
`--from` and `--to` (implicitly HEAD if not given) is actually walkeable
in the git-log (for example a shallow-clone.)

Add tests that check that `read` throws an error when the history
between `from` and `to` is not wakleable (no merge-base.)

Fix follows in subsequent commit.

* fix(read): fail when --from and --to share no merge-base

When linting a range with `--from A --to B`, we hand the refs to
git-raw-commits, which runs `git log A..B`. If A and B share no
common ancestor — typically because we're in a shallow clone where
the merge-base wasn't fetched — `git log` doesn't fail; it silently
emits whatever subset of the range happens to exist locally. We
validate that subset and report success, even though we never saw
the rest of the range.

This bites hardest in CI. `actions/checkout@v4` clones with
`fetch-depth: 1` by default, which is fine for validating `HEAD` but
inadequate for any comparison against `origin/HEAD`. A branch with
six commits ahead of master surfaces only `HEAD`; if `HEAD`'s message is
well-formed, the lint passes regardless of what the unfetched commits
look like.

Verify the two refs share a merge-base before walking the range, and
fail with a clear message when they don't. The check runs only when
`from` is set — `--last`, `--edit`, and running without
`--from`/`--to` all bypass it, since none of them depend on a
two-ended range. When `--to` is omitted, mirror what git-raw-commits
does and treat it as `HEAD`, so `--from A` alone is covered too.

The accompanying tests use `git checkout --orphan` to create two
unrelated histories. That produces the exact condition the check
enforces — no merge-base — without coupling the test to the specific
shallow-clone trigger.

A related case, `--from-last-tag` in a shallow clone, can fail the
same way: `git describe` falls back to a hash, we turn that into a
no-op range, and zero commits get validated. The merge-base check
doesn't catch that path (the no-op sets `from = HEAD-hash`, which
trivially merge-bases with `HEAD`), and distinguishing "no tag exists
upstream" from "tag isn't fetched" requires shallow-clone probing
that's better treated separately. Left for follow-up.

Refs https://github.com/conventional-changelog/commitlint/issues/4555

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Erik Cervin-Edin <erik.cervin-edin@einride.tech>

---------

Signed-off-by: Erik Cervin-Edin <erik.cervin-edin@einride.tech>
Co-authored-by: Claude <noreply@anthropic.com>
E
Erik Cervin Edin committed
e4595eb79e67a3c51960e95b1dcc696754312cdb
Parent: 1af3138
Committed by GitHub <noreply@github.com> on 5/14/2026, 11:58:38 AM