feat(walk): rev_list_range/4 — base..head with exclude_merges #47

merged colechristensen cole.christensen@gmail.com wants to merge fix/337-rev-list-range into main

Adds ExGitObjectstore.rev_list_range/4: commits reachable from head but not base (the base..head range), topological order oldest-first, with an :exclude_merges option.

Combined, these linearize a range exactly the way git rebase (without --rebase-merges) replays it: merge commits are dropped while the commits they merged in — reachable via any parent — are kept.

Why

Anvil’s rebase merge strategy currently builds its replay list by walking the head branch first-parent-only and includes merge commits, then cherry-picks each. Cherry-picking a merge commit fails with {:merge_commit_needs_mainline, sha}, so any PR whose head history contains a merge commit 422s on rebase-merge instead of merging or reporting a conflict. This primitive is the plumbing Anvil needs to flatten base..head correctly. See fangorn/anvil#337.

Implementation

  • Bounded two-color limit_list painting (INTERESTING from head, UNINTERESTING from base; :u dominates), stopping once no interesting commit remains in the frontier — touches only the range plus its immediate boundary, not all of base’s history.
  • Topological oldest-first via Kahn’s algorithm with a (committer-date, sha) ready-queue for deterministic ordering.

Tests

  • 8 new tests in walk_test.exs: diamond ordering, exclude_merges keeps both merge sides, empty range, linear range, and the integration-branch regression (an integration branch built by merging feature branches into main replays cleanly onto base with all content, instead of :merge_commit_needs_mainline).
  • Full suite green; mix credo --strict clean on changed files; mix format clean.
Created Jul 10, 2026 at 15:07 UTC | Merged Jul 10, 2026 at 16:26 UTC by colechristensen cole.christensen@gmail.com