Skip to content

Strip squash-merged ancestor commits during rebase --onto#193

Merged
subsetpark merged 12 commits intomainfrom
zax--rebase-ancestor-subject-filter
Apr 17, 2026
Merged

Strip squash-merged ancestor commits during rebase --onto#193
subsetpark merged 12 commits intomainfrom
zax--rebase-ancestor-subject-filter

Conversation

@subsetpark
Copy link
Copy Markdown
Collaborator

@subsetpark subsetpark commented Apr 17, 2026

Summary

  • Worktree.find_old_base now reads each candidate commit's subject alongside its SHA (via git log --format='%H %s') and filters out commits matching [<project>] Patch N: for any transitive ancestor N, in addition to the existing git rev-list --cherry-pick patch-id filter.
  • The subject-based fallback is load-bearing against squash-merging trunks: a squash collapses a PR's commits into a single new commit with a fresh patch-id, so cherry-pick can't match the ancestor's original commit on a descendant feature branch. We saw this on trigger-only-execution/patch-7 — the auto --onto main replayed merged Patch 1 and Patch 2 back onto main because their feature-branch patch-ids had drifted from the squashes.
  • Graph.transitive_ancestors exposes the ancestor set; both rebase call sites in bin/main.ml (the Rebase op and merge-conflict rebase) thread it through execute_worktree_planWorktree.rebase_onto ~project_name ~ancestor_ids.

Test plan

  • dune build (fatal-warnings clean)
  • dune runtest --force — full suite passes, including:
    • 9 new pure tests for is_ancestor_patch_subject and oldest_non_ancestor_commit (covers squash (#42) suffix, wrong-project tag, ad-hoc agent subjects, missing-colon form, empty-ancestor passthrough)
    • 3 new property tests for Graph.transitive_ancestors (self-exclusion, direct-deps coverage, deps-closure)
    • New integration test 10 in test_rebase_onto.ml: reproduces the patch-7 drift — an ancestor commit with the [proj] Patch 1: subject whose content was touched (so its patch-id no longer matches main's squash) is correctly stripped when ancestor_ids=[1]; only the current patch's commit sits on top of the squash
  • Existing 9 rebase_onto integration tests keep passing by threading ~project_name:"" and ~ancestor_ids:[] (no-op filter preserves pre-change behavior)

🤖 Generated with Claude Code


Summary by cubic

Stops rebase --onto from replaying squash-merged ancestor commits by reading git log subjects and skipping [<project>] Patch N: for transitive ancestors. Picks the correct old base on squash-merging trunks and hardens log parsing.

  • Bug Fixes

    • Worktree.find_old_base now uses git log --cherry-pick --format='%H %s' --no-show-signature --no-merges, returns the oldest SHA after dropping lines whose subjects match [<project>] Patch N: for any ancestor_ids; strips trailing \r, skips blank/malformed/empty-SHA lines, and supports opt-out via project_name="" or ancestor_ids=[]. Docs clarify why --no-merges and --no-show-signature are required.
    • Added Graph.transitive_ancestors (cycle-safe, ascending order) and threaded its result into Worktree.rebase_onto ~project_name ~ancestor_ids at both rebase call sites; tests include a line-count sanity check to prove the subject-filter path.
  • Refactors

    • Replaced oldest_unique_commit with oldest_non_ancestor_commit, which subsumes the old behavior.

Written for commit 32b2a31. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Rebase now computes transitive ancestor info and recognizes project-tagged commit subjects to avoid replaying ancestor patches.
  • Bug Fixes

    • More robust base selection for rebases: skips ancestor-tagged commits, preserves commit-subject separators, and handles empty/whitespace and CRLF edge cases.
  • Tests

    • Added property and integration tests covering transitive-ancestor discovery, cycle safety, and ancestor-aware rebase/subject-parsing behavior.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
onton Ready Ready Preview, Comment Apr 17, 2026 8:52pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Compute transitive ancestor patch IDs from the graph and propagate them into worktree rebase logic. Worktree base selection now filters git-log candidates by commit subjects that match ancestor patch IDs; main orchestration computes ancestor_ids from the runtime snapshot and passes them into rebase and merge-conflict execution.

Changes

Cohort / File(s) Summary
Graph traversal
lib/graph.ml, lib/graph.mli
Add transitive_ancestors : t -> Patch_id.t -> Patch_id.t list to collect transitively reachable dependency patch IDs (excluding the start), cycle-safe traversal and sorted, duplicate-free result.
Main orchestration
bin/main.ml
Extend execute_worktree_plan signature with ~ancestor_ids; compute ancestor_ids at runtime via Graph.transitive_ancestors from the current snapshot and pass into both normal rebase and merge-conflict execution paths.
Worktree rebase logic
lib/worktree.ml, lib/worktree.mli
Replace oldest_unique_commit/rev-list approach with subject-aware parsing: add is_ancestor_patch_subject and oldest_non_ancestor_commit; change find_old_base and rebase_onto signatures to accept ~project_name and ~ancestor_ids and filter git log candidates by subject matching.
Tests (properties & rebase)
test/test_properties.ml, test/test_rebase_onto.ml
Add QCheck2 properties for Graph.transitive_ancestors (including a cyclic graph case). Replace and extend Worktree unit tests to validate is_ancestor_patch_subject and oldest_non_ancestor_commit; update integration tests to pass project_name/ancestor_ids and add a squash-merge ancestor scenario.

Sequence Diagram

sequenceDiagram
    participant Orchestrator as Orchestrator/Main
    participant Graph as Graph
    participant Worktree as Worktree
    participant Git as Git

    Orchestrator->>Graph: transitive_ancestors(patch_id)
    Graph-->>Orchestrator: ancestor_ids
    Orchestrator->>Worktree: execute_worktree_plan(..., ancestor_ids)
    Worktree->>Git: git log --cherry-pick --right-only --format=%H %s
    Git-->>Worktree: (sha, subject) list
    Worktree->>Worktree: filter out subjects matching ancestor_ids
    Worktree->>Git: resolve chosen_sha^ (parent)
    Git-->>Worktree: parent SHA
    Worktree->>Git: git rebase --onto parent target
    Git-->>Worktree: rebase result
    Worktree-->>Orchestrator: result / fail label
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I chased the graph through branch and vine,
Pulled ancestors out by careful line,
Skipped kin whose subjects wore the tag,
Rebased clean without a snag,
A hop, a sniff, a tidy sign.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: filtering out squash-merged ancestor commits during rebase operations using subject-pattern matching, which directly addresses the core problem solved in this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch zax--rebase-ancestor-subject-filter

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 4 comments

Comment thread lib/worktree.ml Outdated
Comment thread lib/worktree.ml
Comment thread lib/worktree.ml Outdated
Comment thread lib/graph.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

The core logic — using git log --format=%H %s --cherry-pick --right-only plus subject-based ancestor filtering to handle squash-merged deps — is sound and well-tested. The regression test (test 10) faithfully reproduces the described patch-7 drift scenario. transitive_ancestors in graph.ml is correctly implemented with a visited-set DFS, and the property tests cover self-exclusion, direct-dep coverage, and transitivity closure. Two quality issues and two notes:

  1. Should-fix: oldest_unique_commit is now dead production code — its call site was replaced by oldest_non_ancestor_commit, but the old function remains publicly exported in worktree.mli. Should be removed.

  2. Should-fix: The None arm of lsplit2 in oldest_non_ancestor_commit silently promotes an unparseable line to a bare SHA, which would produce a confusing rev-parse failure downstream if ever reached. Should return None instead.

  3. Note: The doc comment on is_ancestor_patch_subject understates the delimiter set (says : and ; code accepts any non-alphanum/-/_).

  4. Note: ancestor_ids is a list scanned with List.mem; fine at current scale, worth noting if ancestor sets grow.
    | Severity | Count |
    |----------|-------|
    | Must-fix | 0 |
    | Should-fix | 2 |
    | Note | 2 |


4 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 857728 in / 7069 out · Cache: 7959 created / 52685 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 21 · Tool mix: read_file=14, search_codebase=7

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/graph.ml`:
- Around line 41-46: transitive_ancestors currently seeds seen as empty so
cycles can re-add the root patch_id; fix by seeding seen with the root to
prevent it being returned: in transitive_ancestors change the initial visit call
to start with Set.singleton (module Patch_id) patch_id (or equivalently Set.add
(Set.empty (module Patch_id)) patch_id) so visit will never include the original
patch_id in its result; keep deps and visit logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a0695352-09fe-4769-81f1-abc4385f0354

📥 Commits

Reviewing files that changed from the base of the PR and between 4bfde5e and 8d3b0ef.

📒 Files selected for processing (7)
  • bin/main.ml
  • lib/graph.ml
  • lib/graph.mli
  • lib/worktree.ml
  • lib/worktree.mli
  • test/test_properties.ml
  • test/test_rebase_onto.ml

Comment thread lib/graph.ml Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Comment thread lib/worktree.ml
Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 3 comments

Comment thread lib/worktree.ml
Comment thread lib/worktree.ml
Comment thread lib/graph.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

The PR correctly implements squash-merge ancestor stripping. The core logic in is_ancestor_patch_subject and oldest_non_ancestor_commit is sound, the integration test (test10) faithfully reproduces the regression, and the previously outstanding dead-code issue with oldest_unique_commit is fully resolved — the function no longer exists in .ml or .mli.

Findings:

  1. should-fix (lib/worktree.ml:393): is_ancestor_patch_subject with project_name:"" and non-empty ancestor_ids constructs the prefix "[] Patch ", which could match unusual-but-legal commit subjects. The callers today always supply a real project name, but an explicit early-exit guard (if String.is_empty project_name || List.is_empty ancestor_ids then false) would make the invariant self-documenting and safe for future callers.

  2. should-fix (lib/worktree.ml:393): lsplit2 ~on:' ' → None silently drops space-free lines. The prior review's concern (Turn 1) about that branch is now moot because the new code returns None (skip) instead of Some line (treat line as SHA). The current behaviour is correct; a blank-line pre-filter would add clarity.

  3. note (lib/graph.ml:44): transitive_ancestorsSet.to_listList.mem gives O(n) membership tests per commit line. Fine at current scale; noting for when gameplan sizes grow.
    | Severity | Count |
    |----------|-------|
    | Must-fix | 0 |
    | Should-fix | 2 |
    | Note | 1 |


3 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 844518 in / 6087 out · Cache: 0 created / 60644 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 20 · Forced submit: yes · Tool mix: read_file=13, search_codebase=7

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/worktree.ml`:
- Around line 386-390: The parsing in oldest_non_ancestor_commit loses a final
space because String.strip removes the trailing separator from a "<sha> " line,
breaking String.lsplit2; fix by preserving the final "%H %s" separator instead
of using String.strip on log_output — e.g., replace String.strip with only
trimming newlines (or rstrip just '\n'/'\r') or simply ensure a single trailing
space is present (append " " if missing) before splitting; update the code paths
around oldest_non_ancestor_commit, log_output, String.strip and the subsequent
String.lsplit2 usage so empty subjects don't drop the separator.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3a77eaeb-cd92-49bb-bfb7-5ac74ea3b61c

📥 Commits

Reviewing files that changed from the base of the PR and between 8d3b0ef and 0cd0529.

📒 Files selected for processing (3)
  • lib/worktree.ml
  • lib/worktree.mli
  • test/test_rebase_onto.ml
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/worktree.mli

Comment thread lib/worktree.ml Outdated
Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 2 comments

Comment thread test/test_properties.ml
Comment thread lib/worktree.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

The diff correctly implements the squash-merge ancestor stripping for rebase_onto. The core logic in is_ancestor_patch_subject, oldest_non_ancestor_commit, find_old_base, transitive_ancestors, and both call sites in bin/main.ml is sound. The two previously outstanding should-fix concerns from Turn 1/Turn 2 about the None arm of lsplit2 and the project_name="" guard are fully addressed in this diff.

Two new issues found:

  1. prop_transitive_ancestors_transitive (should-fix): The closure property incorrectly returns false when an ancestor has a dep on the root patch p.id, because p.id is correctly excluded from ancestors but the property doesn't account for that exception. The property is logically wrong even if the current generators happen not to produce this case.

  2. CRLF safety in oldest_non_ancestor_commit (should-fix): The line is stripped only for the blank-line check but passed raw to lsplit2, so a trailing \r on any line would be included in the extracted SHA, causing git rev-parse <sha\r>~1 to fail. On macOS/Linux this is unreachable with standard git config; on Windows or with core.autocrlf it is a real path. rstrip before splitting would harden this at zero correctness cost.
    | Severity | Count |
    |----------|-------|
    | Must-fix | 0 |
    | Should-fix | 2 |
    | Note | 0 |


2 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 910939 in / 5549 out · Cache: 7959 created / 52685 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 21 · Forced submit: yes · Tool mix: read_file=14, search_codebase=6, find_references=1

Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 3 comments

Comment thread lib/worktree.ml
Comment thread test/test_properties.ml
Comment thread lib/worktree.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

The PR correctly fixes the squash-merge drift bug. Previously-outstanding issues from prior turns are fully addressed: oldest_unique_commit is removed, the None arm of lsplit2 skips instead of passing raw text as a SHA, and the project_name="" guard prevents the accidental [] Patch N: match.

Three remaining observations:

  1. note (lib/worktree.ml:404): git log --format=%H %s without --no-signature can emit GPG signature lines (when the user's global log.showSignature = true) that reach lsplit2 and get misinterpreted as SHAs. The downstream rev-parse gpg:~1 call then fails, causing find_old_base to return Error and fall back to plain rebase — recoverable but confusing. Adding --no-signature to the invocation (available since git 2.2.0) removes this path entirely.

  2. should-fix (test/test_properties.ml:117): The transitivity property now has the correct root-exclusion guard, but gen_patch_dag only generates acyclic graphs. The cycle-termination guard inside transitive_ancestors (Set.mem seen) is never exercised by this test suite. A dedicated unit test with a hand-crafted cycle would make the cycle-safety claim verifiable.

  3. note (lib/worktree.ml:375): The doc comment lists only ' ' and ':' as delimiters, but Char.is_whitespace also includes \t, \r, etc. Cosmetic but slightly misleading.
    | Severity | Count |
    |----------|-------|
    | Must-fix | 0 |
    | Should-fix | 1 |
    | Note | 2 |


3 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 910246 in / 6814 out · Cache: 0 created / 60644 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 20 · Forced submit: yes · Tool mix: read_file=16, search_codebase=4

Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 3 comments

Comment thread lib/worktree.mli Outdated
Comment thread test/test_properties.ml Outdated
Comment thread bin/main.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

The core logic is correct and well-tested. The squash-merge ancestor-subject filter, transitive_ancestors, CRLF stripping, --no-signature guard, and the integration test 10 regression are all sound. All previously-outstanding issues from Turns 1–4 (removing oldest_unique_commit, None arm in lsplit2, --no-signature, CRLF stripping, empty project_name guard, and cycle-safety) are fully addressed in this diff.

Two actionable issues remain:

  1. should-fix (lib/worktree.mli:108): The rebase_onto doc comment still says git rev-list --cherry-pick but the implementation was changed to git log --cherry-pick in this PR. The error message inside the impl was updated but the public .mli doc wasn't.

  2. should-fix (test/test_properties.ml:163): The hand-crafted cycle test uses List.equal (order-sensitive) to compare transitive_ancestors results, despite the function documenting "order is unspecified". Currently safe because both sets have exactly one element, but it is a brittle pattern. Should use sorted comparison.

One note on non-atomic double Runtime.read for agent + ancestor_ids in bin/main.ml — harmless today since the graph is immutable post-startup, but worth knowing if the graph ever becomes mutable.

Severity Count
Must-fix 0
Should-fix 2
Note 1

3 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 811080 in / 8364 out · Cache: 0 created / 52512 read · Context: 257 items · Review mode: agentic · Turns: 13 · Tool calls: 17 · Tool mix: read_file=15, search_codebase=2

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/worktree.ml">

<violation number="1" location="lib/worktree.ml:436">
P1: `git log` is called with an invalid flag (`--no-signature`), which can make rebase base-detection fail at runtime.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread lib/worktree.ml Outdated
Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 4 comments

Comment thread lib/worktree.ml
Comment thread test/test_rebase_onto.ml
Comment thread lib/graph.mli
Comment thread lib/worktree.mli
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

The PR correctly fixes the squash-merge ancestor-replay bug and all previously outstanding review concerns (CRLF stripping, --no-signature, empty-project guard, cycle termination, sorted cycle-test assertions, and the .mli rev-listlog doc correction) have been addressed in this diff.

Four new findings:

  1. should-fix (lib/worktree.ml:404): lsplit2 ~on:' ' can return Some ("", subject) for a leading-space line, passing an empty SHA to git rev-parse, which fails with a confusing error. Unreachable from real git log output but a trivial one-line guard (if String.is_empty sha then None) closes the gap defensively.

  2. should-fix (test/test_rebase_onto.ml:759): Test 10 verifies only the final end-state; it does not assert that the cherry-pick filter alone fails to drop the drifted Patch 1 commit (the negative case that proves the subject-filter is load-bearing). If a git version's patch-id normalisation happened to match the amended commit, the test would still pass without the new code being exercised.

  3. note (lib/graph.mli:24): transitive_ancestors doc says "order is unspecified" but the implementation uses Set.to_list which is deterministically sorted by Patch_id.compare.

  4. note (lib/worktree.mli:63): oldest_non_ancestor_commit doc omits --no-merges and --no-signature from the described git command, which are the flags that prevent merge-commit lines and GPG annotations from corrupting SHA extraction.
    | Severity | Count |
    |----------|-------|
    | Must-fix | 0 |
    | Should-fix | 2 |
    | Note | 2 |


4 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 991359 in / 8295 out · Cache: 0 created / 60644 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 20 · Forced submit: yes · Tool mix: read_file=19, search_codebase=1

Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 4 comments

Comment thread lib/worktree.ml
Comment thread test/test_rebase_onto.ml
Comment thread bin/main.ml
Comment thread lib/graph.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

This PR correctly addresses the squash-merge trunk rebase drift bug. The core implementation is sound:

  • is_ancestor_patch_subject has the right early-exit guards (empty project name, empty ancestor list), correct delimiter logic for [project] Patch N:, and handles squash suffixes like (#42).
  • oldest_non_ancestor_commit correctly handles CRLF via rstrip ~drop:(Char.equal '\r'), empty subjects (the "<sha> " trailing-space case), blank-line skipping, and malformed lines.
  • find_old_base uses --no-show-signature which is the correct git log flag (the Turn 4 suggestion of --no-signature is a git format-patch flag and would have been wrong).
  • transitive_ancestors is cycle-safe and the cycle test in test_properties.ml correctly sorts both sides.
  • All previously-flagged must-fix issues are resolved: None arm of lsplit2 no longer treats raw lines as SHAs, CRLF is stripped, GPG signature injection is blocked, and the transitive-ancestors test root-exclusion guard is correct.

One should-fix: prop_trailing_whitespace now tests trailing spaces on the subject column rather than the SHA, making its name slightly misleading. The behavior is still correct — the SHA extraction is not affected — but an explicit test covering the empty-subject line case (which prop_empty_subject_preserved in the second block already covers) would complete the picture in the first block too.

Severity Count
Must-fix 0
Should-fix 1
Note 3

4 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 857125 in / 8513 out · Cache: 0 created / 56578 read · Context: 257 items · Review mode: agentic · Turns: 14 · Tool calls: 15 · Tool mix: read_file=12, search_codebase=3

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/worktree.ml`:
- Around line 411-413: The code currently treats "no kept commits" as an error
by returning Result.Error "no unique commits found" from the block that matches
List.last kept; change that to return a distinct success value signalling
"nothing left to transplant" (for example: change the function's Ok payload from
a raw sha string to an option string and return Result.Ok None when kept is
empty, or introduce a dedicated variant like `Nothing_to_transplant`), and
update callers (notably rebase_onto) to handle that new success case by moving
the branch to target without replaying commits instead of falling back to plain
git rebase; locate the code by the match List.last kept expression and the
rebase_onto caller to implement the changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f09e5e47-3bb0-470b-a351-1d565323050a

📥 Commits

Reviewing files that changed from the base of the PR and between 502478b and 2d7273d.

📒 Files selected for processing (3)
  • lib/worktree.ml
  • lib/worktree.mli
  • test/test_properties.ml
✅ Files skipped from review due to trivial changes (1)
  • lib/worktree.mli
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/test_properties.ml

Comment thread lib/worktree.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

This diff correctly addresses all must-fix and should-fix items from prior review turns. The new is_ancestor_patch_subject, oldest_non_ancestor_commit, and transitive_ancestors implementations are correct. All previously flagged correctness issues are resolved: --no-show-signature flag, empty-SHA guard (Some ("", _) -> None), CRLF rstrip, blank-line gate, early-exit on empty project_name/ancestor_ids, prop_transitive_ancestors_transitive root-exclusion guard, List.equal replaced by sorted comparison, and the test10 sanity check for the cherry-pick-alone path are all present and correct. The graph.mli doc now correctly says "sorted by [Patch_id.compare]".

Three carry-over note-severity observations remain — none are correctness issues:

  1. lib/worktree.mli rebase_onto doc missing opt-out hint (project_name="" or ancestor_ids=[]).
  2. prop_trailing_whitespace name is now misleading (tests subject trailing spaces, not SHA bleeding).
  3. The two-node cycle test could be strengthened with a three-node ring to exercise non-adjacent back-edge termination.
    | Severity | Count |
    |----------|-------|
    | Must-fix | 0 |
    | Should-fix | 0 |
    | Note | 3 |

3 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 1010470 in / 5054 out · Cache: 0 created / 60644 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 21 · Forced submit: yes · Tool mix: read_file=17, search_codebase=4

subsetpark and others added 9 commits April 17, 2026 13:41
find_old_base now switches to `git log --format='%H %s'` so it can see
each candidate commit's subject, and filters out commits whose subject
matches `[<project>] Patch N:` for any transitive ancestor N. The
patch-id fallback from `git rev-list --cherry-pick` alone isn't enough
when upstream squash-merges: the squash carries a fresh patch-id that
cherry-pick can't equate with the feature branch's original commit, so
the ancestor's commit survives the rebase as an empty/no-op or
conflicting replay.

Graph gains a `transitive_ancestors` helper; both rebase call sites in
bin/main.ml pass the computed ancestor set through execute_worktree_plan
to the new `Worktree.rebase_onto ~project_name ~ancestor_ids` signature.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Delete oldest_unique_commit (.ml + .mli); migrate its tests to
  oldest_non_ancestor_commit ~ancestor_ids:[] which subsumes the old
  behaviour for real git log --format=%H %s output.
- In oldest_non_ancestor_commit, treat a line without a space as
  malformed and skip it rather than treating the whole line as a SHA.
  Real git output always has "<sha> <subject>" with a mandatory space;
  the previous fallback would have fed garbage into rev-parse.
- Clarify the is_ancestor_patch_subject doc comment to match the
  actual predicate (any non-[alnum-_] character terminates the id).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Graph.transitive_ancestors now seeds the [seen] set with the root
  patch_id and removes it from the result, so a dependency cycle
  (p -> q -> p) can no longer leak p back into its own ancestors.
- is_ancestor_patch_subject: short-circuit when project_name or
  ancestor_ids is empty, so "[] Patch N:" can never accidentally
  match. Widen the id terminator set to stop only on whitespace or
  ':' — any Patch_id.to_string that doesn't itself contain those
  now round-trips, so the function is robust to non-numeric ids
  (slugs, UUIDs, etc).
- oldest_non_ancestor_commit: drop the [String.strip] on the whole
  log output. An oldest commit with an empty subject emits
  "<sha> " via [%H %s]; stripping would erase the separator and
  silently drop the SHA, picking the wrong old_base. Blank/
  whitespace lines are filtered explicitly.
- Add regressions for the empty-subject and empty-project cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- oldest_non_ancestor_commit now rstrips only trailing [\r] on each
  line before splitting, so CRLF line endings don't leak the [\r]
  into the sha. Explicitly not a full rstrip: the mandatory trailing
  space on empty-subject lines ("<sha> ") must survive to keep
  [lsplit2] parsing it as a separator.
- prop_transitive_ancestors_transitive: a dep graph with a back-edge
  to the root (B depends on root A) would have grand = A, which the
  fixed [transitive_ancestors] correctly excludes from ancestors.
  Allow grand = p.id as an exception to the closure check.
- Add a CRLF regression test for oldest_non_ancestor_commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- find_old_base now passes --no-signature to git log so a
  log.showSignature=true in the user's global gitconfig can't
  inject "gpg: Signature made ..." lines into %H %s output.
  Without this, oldest_non_ancestor_commit would treat the
  signature line as a sha and propagate a confusing rev-parse
  failure into the rebase fallback.
- Sharpen is_ancestor_patch_subject's doc comment: the id
  terminator is any Char.is_whitespace or ':' (not just space
  and colon as the old wording implied).
- Hand-crafted cycle test for Graph.transitive_ancestors:
  gen_patch_dag only produces DAGs, so the Set.mem termination
  guard isn't exercised by the property tests. Build A↔B
  directly, verify the walk terminates and excludes the root.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- worktree.mli: rebase_onto's doc comment now says "git log --cherry-pick"
  to match the actual implementation. Was still referencing the
  old "git rev-list --cherry-pick" invocation that this PR replaced.
- test_properties.ml: sort both sides of the cycle-test equality so
  the assertion doesn't depend on Set.to_list traversal order. With
  only one element per set it's moot today, but the pattern would
  break silently if future cycles had multiple ancestors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
git log's option is --show-signature / --no-show-signature; the
bare --no-signature form is not recognized and fails with exit 128,
which would have made find_old_base fall back to plain rebase on
every call once this landed. Verified locally:
  $ git log --no-signature -n 1
  fatal: unrecognized argument: --no-signature

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- oldest_non_ancestor_commit: skip lines where lsplit2 yields an
  empty SHA (leading whitespace). Real git log --format=%H %s can't
  produce these, but the guard keeps "" from ever reaching
  git rev-parse and producing a confusing fallback.
- test 10: the original trailing-newline drift didn't actually
  change patch-id (git normalizes trailing whitespace), so
  cherry-pick alone already stripped Patch 1 and the subject-filter
  path wasn't exercised. Switch to content-level drift
  ("dep-v1-drift") and add a sanity assertion that confirms
  cherry-pick keeps the drifted commit — so the end-state assertion
  is genuinely proving the subject-filter code path.
- graph.mli: document that transitive_ancestors returns a list
  sorted by Patch_id.compare and is cycle-safe.
- worktree.mli: spell out the full git log invocation
  (--no-merges, --no-show-signature) that oldest_non_ancestor_commit
  expects as input.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- worktree.ml: expand the comment above the per-line stripping in
  oldest_non_ancestor_commit to explain why both rstrip (\r only)
  and the strip-based blank-line gate are needed — one handles CRLF,
  the other handles space-only separator lines, and "<sha> " must
  survive both.
- test_rebase_onto.ml: add prop_single_line_empty_subject to cover
  the narrow "<sha> \n" case directly (existing
  prop_empty_subject_preserved covers the multi-line variant).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- worktree.mli: document that project_name="" or ancestor_ids=[]
  opts out of the subject filter (cherry-pick dedup still applies).
- test_properties.ml: extend the hand-crafted cycle block with a
  three-node A→B→C→A ring, so the [seen]-set termination guard is
  exercised on a non-adjacent back-edge (C→A is reached only after
  A→B→C, not directly from A).
- test_rebase_onto.ml: rename prop_trailing_whitespace to clarify
  that the invariant under test is "subject-side trailing spaces
  don't corrupt the SHA" (the SHA side is always safe because
  lsplit2 splits at the first space).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 4 comments

Comment thread lib/worktree.mli
Comment thread lib/graph.mli Outdated
Comment thread test/test_rebase_onto.ml Outdated
Comment thread test/test_properties.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

This PR correctly implements the ancestor-subject filter for squash-merged trunks and is well-tested. All previously raised must-fix and should-fix issues from prior turns have been addressed. The remaining open items are all note-level carry-overs:

  1. rebase_onto doc missing opt-out hint (worktree.mli:116, Turn 8/9): project_name="" or ancestor_ids=[] skips the filter, but this is not documented.
  2. transitive_ancestors order contract ambiguous (graph.mli:27, Turn 6/8): Implementation always returns ascending Patch_id.compare order via Set.to_list, but doc says "order is unspecified."
  3. prop_trailing_whitespace test name misleading (test_rebase_onto.ml:43, Turn 7): The test now exercises trailing spaces on the subject column, not on the SHA — but the name implies SHA-level stripping.
  4. 3-node cycle not exercised (test_properties.ml:161, Turn 8/9): The hand-crafted cycle test uses a 2-node mutual cycle; a 3-node ring would more thoroughly validate the non-adjacent back-edge guard in transitive_ancestors.
    | Severity | Count |
    |----------|-------|
    | Must-fix | 0 |
    | Should-fix | 0 |
    | Note | 4 |

4 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 1018061 in / 5761 out · Cache: 0 created / 60644 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 23 · Forced submit: yes · Tool mix: read_file=17, search_codebase=6

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/worktree.mli (1)

54-57: Document the actual patch-id terminator rule in the interface.

The implementation intentionally matches subjects like [proj] Patch 6 (HEAD -> main) as well as [proj] Patch 6: ..., but this comment currently reads as though : is required. The .mli should expose the same delimiter contract as the implementation/tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/worktree.mli` around lines 54 - 57, Update the comment for the
commit-subject matcher in lib/worktree.mli to document the exact patch-id
terminator rule: state that the pattern is "[<project_name>] Patch <N>" where
the "<N>" may be followed by either a colon (:) or whitespace and then
additional tokens (e.g. git ref/parentheses like "(HEAD -> main)") or
end-of-line; ensure the comment mentions this behavior alongside the existing
reference to ancestor_ids so it reflects the implementation/tests that accept
both ":" and space/parenthesis-terminated forms.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/worktree.mli`:
- Around line 54-57: Update the comment for the commit-subject matcher in
lib/worktree.mli to document the exact patch-id terminator rule: state that the
pattern is "[<project_name>] Patch <N>" where the "<N>" may be followed by
either a colon (:) or whitespace and then additional tokens (e.g. git
ref/parentheses like "(HEAD -> main)") or end-of-line; ensure the comment
mentions this behavior alongside the existing reference to ancestor_ids so it
reflects the implementation/tests that accept both ":" and
space/parenthesis-terminated forms.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 13d23ce5-e873-400a-ba39-7f12adf28fdc

📥 Commits

Reviewing files that changed from the base of the PR and between 2d7273d and 215ba85.

📒 Files selected for processing (7)
  • bin/main.ml
  • lib/graph.ml
  • lib/graph.mli
  • lib/worktree.ml
  • lib/worktree.mli
  • test/test_properties.ml
  • test/test_rebase_onto.ml
🚧 Files skipped from review as they are similar to previous changes (4)
  • lib/graph.ml
  • test/test_properties.ml
  • bin/main.ml
  • test/test_rebase_onto.ml

Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 2 comments

Comment thread test/test_rebase_onto.ml
Comment thread lib/graph.mli Outdated
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

Overall the PR is in good shape. All prior must-fix and should-fix issues from Turns 1–10 have been addressed: --no-show-signature is used consistently, the rebase_onto doc documents the opt-out contract, the empty-SHA guard is present, the doc for rebase_onto is updated, the three-node cycle test is added, and oldest_unique_commit has been removed.

One new must-fix concern: the test-10 sanity check (verifying that cherry-pick alone keeps the drifted Patch 1) does not guard against the log returning fewer than two entries. If the line-count precondition silently fails, the SHA comparison fires on wrong inputs and the test aborts before exercising the subject-filter path. Adding a line-count assertion before the SHA comparison makes the failure mode explicit.

One carry-over note: the graph.mli doc says "sorted by" rather than "sorted in ascending order by", which is cosmetically imprecise but not wrong.

Severity Count
Must-fix 1
Should-fix 0
Note 1

2 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 1025843 in / 5532 out · Cache: 0 created / 60644 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 22 · Forced submit: yes · Tool mix: read_file=17, search_codebase=5

- test_rebase_onto.ml test 10: assert the cherry-pick log contains
  both commits (Patch 1 + Patch 7) before the SHA comparison, so a
  future git version that patch-id-equates the drift with the squash
  fails as an explicit line-count mismatch rather than a confusing
  SHA mismatch.
- graph.mli: spell out that transitive_ancestors returns the list in
  "ascending order" by Patch_id.compare, removing the (minor)
  direction ambiguity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 3 comments

Comment thread test/test_rebase_onto.ml
Comment thread lib/worktree.mli
Comment thread lib/graph.ml
@flowglad-review-service
Copy link
Copy Markdown

Review Summary

The core implementation — oldest_non_ancestor_commit, is_ancestor_patch_subject, Graph.transitive_ancestors, and the wiring in execute_worktree_plan — is correct and well-tested. All prior must-fix and should-fix concerns from earlier turns have been addressed. The remaining items are lightweight:

  1. should-fix (test_rebase_onto.ml:810): The sanity-check assertion in test 10 would benefit from a clarifying comment explaining the ordering relationship between HEAD~1 (Patch 1) and HEAD (Patch 7) relative to oldest_non_ancestor_commit's last-line return semantics. Not a bug, but likely to confuse future readers.

  2. note (worktree.mli:63): The advisory sentence in the oldest_non_ancestor_commit doc mentions --no-show-signature but omits --no-merges as an equally-important caller requirement. A merge-commit subject won't match any ancestor pattern and could be returned as the oldest SHA.

  3. note (lib/graph.ml:44): List.mem in is_ancestor_patch_subject is O(n) per commit line; harmless at current scale.
    | Severity | Count |
    |----------|-------|
    | Must-fix | 0 |
    | Should-fix | 1 |
    | Note | 2 |


3 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 1031852 in / 5099 out · Cache: 0 created / 60644 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 19 · Forced submit: yes · Tool mix: read_file=13, search_codebase=6

- test_rebase_onto.ml test 10: add a comment at the SHA assertion
  explaining why oldest_non_ancestor_commit returns HEAD~1 (oldest
  = Patch 1, last line of newest-first git log output) and noting
  that the [git] helper already strips its subprocess output.
- worktree.mli: elaborate the oldest_non_ancestor_commit advisory
  to explain why both --no-merges and --no-show-signature are
  load-bearing for callers who build their own git log invocation
  (merge commits give wrong ~1 parent; GPG lines get mistaken for
  SHAs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai bot commented Apr 17, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

@subsetpark subsetpark merged commit ba35f47 into main Apr 17, 2026
6 of 7 checks passed
@subsetpark subsetpark deleted the zax--rebase-ancestor-subject-filter branch April 17, 2026 20:53
Copy link
Copy Markdown

@flowglad-review-service flowglad-review-service bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: 3 comments

Comment thread test/test_rebase_onto.ml
with
| Result.Ok sha ->
(* With [~ancestor_ids:[]] the subject filter is inactive, so both
log lines survive; [oldest_non_ancestor_commit] returns the last
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD FIX] test/test_rebase_onto.ml:823 — Carry-over from Turn 12: The sanity-check now has the line-count assertion (good), but the SHA assertion immediately after lacks a comment explaining why oldest_non_ancestor_commit must return HEAD~1 rather than HEAD. oldest_non_ancestor_commit returns List.last kept — the last (oldest) line of newest-first git log output. With two commits in the log (Patch 7 = HEAD = first line, Patch 1 = HEAD~1 = last line), List.last returns Patch 1's SHA. Without a comment a reader may assume the function returns the newest SHA and wonder why we assert against HEAD~1.

Suggested change
log lines survive; [oldest_non_ancestor_commit] returns the last
```ocaml
(* oldest_non_ancestor_commit returns the *last* (oldest) line of the
newest-first log, so with two commits the result is Patch 1 = HEAD~1,
not Patch 7 = HEAD. *)
let patch1_sha = git ~process_mgr ~dir [ "rev-parse"; "HEAD~1" ] in
assert_eq "test10: cherry-pick alone keeps drifted Patch 1" patch1_sha sha

> **Scope:** Two lines in `test/test_rebase_onto.ml` around line 823; no other files need changing.

Comment thread lib/graph.ml
excluding [patch_id] itself. Missing IDs in [deps_map] act as leaves.
Dependency cycles that point back to [patch_id] do not re-add it. *)
let transitive_ancestors t patch_id =
let rec visit seen pid =
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NOTE] lib/graph.ml:43transitive_ancestors returns a Patch_id.t list and is_ancestor_patch_subject in worktree.ml uses List.mem (O(n) per commit line scanned). For current gameplan sizes (tens of patches) this is harmless. If ancestor sets grow large, passing a Set.M(Patch_id).t directly and replacing List.mem with Set.mem (O(log n)) would improve performance.

Suggested change
let rec visit seen pid =
No change required at current scale. If ancestor counts grow, change the return type to `Set.M(Patch_id).t` and update `is_ancestor_patch_subject` to use `Set.mem`.

Scope: lib/graph.ml, lib/graph.mli, lib/worktree.ml, and both bin/main.ml call sites.

Comment thread bin/main.ml
patch_id)
in
let ancestor_ids =
Runtime.read runtime (fun snap ->
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NOTE] bin/main.ml:2571agent and ancestor_ids are fetched in two separate Runtime.read calls (two mutex acquisitions). The graph is immutable after startup so there is no real race today. The same pattern appears at the merge-conflict rebase path (~line 2906). If the graph ever becomes mutable, combining into a single read would be the safe fix.

Suggested change
Runtime.read runtime (fun snap ->
No change required while the graph is immutable. If it becomes mutable, combine into a single read:
```ocaml
let agent, ancestor_ids =
Runtime.read runtime (fun snap ->
let agent = Orchestrator.agent snap.Runtime.orchestrator patch_id in
let ancestor_ids =
Graph.transitive_ancestors
(Orchestrator.graph snap.Runtime.orchestrator) patch_id
in
(agent, ancestor_ids))
in

> **Scope:** Both rebase call sites in `bin/main.ml`.

@flowglad-review-service
Copy link
Copy Markdown

Review Summary

This PR is in good shape. All must-fix and should-fix issues from prior review turns have been resolved in this diff:

  • oldest_unique_commit dead code removed from both .ml and .mli
  • Empty-SHA guard added (Some ("", _) -> None in oldest_non_ancestor_commit)
  • CRLF trailing-\r stripping correctly implemented with String.rstrip ~drop:(Char.equal '\r')
  • --no-show-signature flag added to the git log invocation
  • rebase_onto opt-out behaviour (empty project_name / empty ancestor_ids) documented in .mli
  • Three-node cycle test added to test_properties.ml
  • Line-count sanity check added in test 10 before the SHA comparison
  • graph.mli doc now says "sorted in ascending order by [Patch_id.compare]"
  • prop_transitive_ancestors_transitive correctly excludes the root via the Types.Patch_id.equal grand p.id || … guard

One remaining should-fix: the SHA-comparison block in test 10 would benefit from a one-line comment explaining that oldest_non_ancestor_commit returns List.last kept (oldest = last line of newest-first git log), so with two commits the result is Patch 1 = HEAD~1, not Patch 7 = HEAD. Without this comment the assertion reads backwards.

Two carry-over notes (O(n) List.mem in ancestor lookup; two separate Runtime.read calls per rebase site) continue to apply but require no immediate action.

Severity Count
Must-fix 0
Should-fix 1
Note 2

3 comments posted · Model: claude-sonnet-4-6 (sonnet) · Tokens: 1051906 in / 7528 out · Cache: 0 created / 60644 read · Context: 257 items · Review mode: agentic · Turns: 15 · Tool calls: 19 · Tool mix: read_file=14, search_codebase=3, find_references=1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant