Skip to content

Restack fixes#48

Merged
Pajn merged 1 commit into
mainfrom
restack-fixes
May 22, 2026
Merged

Restack fixes#48
Pajn merged 1 commit into
mainfrom
restack-fixes

Conversation

@Pajn
Copy link
Copy Markdown
Owner

@Pajn Pajn commented Mar 28, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved floating-branch detection and validation to handle orphaned rebase/fork cases, duplicate candidate scenarios, and continue matching after invalid candidates.
    • Better handling of zero-private-lineage branches to avoid false matches; more robust parent-chain alignment during detection.
  • Tests

    • Added integration tests for zero-private-lineage behavior, recovery after duplicate patch-id candidates, successful non-interactive pick flow, and multi-commit rebase correctness.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4a2dd0f8-78c8-4ae1-862a-a0e26d63b651

📥 Commits

Reviewing files that changed from the base of the PR and between cf9e1a0 and a57f083.

📒 Files selected for processing (2)
  • src/stack.rs
  • tests/restack_tests.rs

📝 Walkthrough

Walkthrough

Refactors floating-base detection in src/stack.rs to return validated matches with candidate indices, truncates target candidate lists by a patch-id boundary, validates matches by aligning first-parent chains, and adds integration tests covering zero-private-lineage, duplicate patch-id candidates, --pick behavior, and multi-commit rebases.

Changes

Floating-base detection with match validation

Layer / File(s) Summary
Floating-base match context enrichment
src/stack.rs
Introduce FloatingBaseMatch { branch_id, target_index }, add candidate_positions to FloatingTargetContext, and change metadata_matches_target_candidate to return Result<Option<usize>> to propagate candidate index information.
Target candidate construction refactoring
src/stack.rs
build_floating_target_context materializes all_candidates, derives private_len from floating_patch_id_boundary, truncates to that prefix, and builds candidates, candidate_ids, and candidate_positions from the truncated slice.
Floating-base detection refactoring
src/stack.rs
find_floating_base delegates to find_floating_match. All match paths (candidate-id fast path, tree-hash, metadata, orphaned rebase-fork, patch-id loop) compute target_index and gate acceptance via validate_floating_match.
Match validation framework
src/stack.rs
Add validate_floating_match and helpers to align branch first-parent chains with the target candidate chain (merge-base-first-parent walk with root fallback), perform multi-commit validation, and allow fork-point fallback acceptance.
Integration tests
tests/restack_tests.rs
Add tests: test_find_floating_base_preserves_zero_private_lineage, test_restack_continues_after_invalid_duplicate_patch_id_candidate, test_restack_pick_reports_no_candidates_for_repaired_top_stack, and test_restack_rebases_multiple_commits_after_single_fork_match.

Sequence Diagram

sequenceDiagram
  participant Branch
  participant FindFloatingMatch
  participant TargetContext
  participant MatchValidator

  Branch->>FindFloatingMatch: branch_id, target_oid
  FindFloatingMatch->>TargetContext: lookup candidate_positions
  alt candidate-id fast path
    FindFloatingMatch->>MatchValidator: FloatingBaseMatch(branch_id, target_index)
  else other match paths (tree/metadata/patch-id)
    FindFloatingMatch->>FindFloatingMatch: compute target_index
    FindFloatingMatch->>MatchValidator: FloatingBaseMatch(branch_id, target_index)
  end
  MatchValidator->>MatchValidator: align first-parent chain with target candidates
  alt validation succeeds
    MatchValidator-->>FindFloatingMatch: Ok(match)
    FindFloatingMatch-->>Branch: Some(oid)
  else validation fails
    MatchValidator-->>FindFloatingMatch: rejected
    FindFloatingMatch-->>Branch: None
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Pajn/Kindra#40: Both PRs modify src/stack.rs’s floating-base detection/matching flow in find_floating_base—tightening tree/patch-id candidate acceptance by requiring parent-context/validation alignment.
  • Pajn/Kindra#37: Both PRs modify src/stack.rs’s floating-base detection—specifically the find_floating_base match/acceptance logic—by changing when a candidate is considered valid based on ancestry/tree/metadata checks.

Poem

🐇 I hop through commits, small and spry,
Matching branches under the sky,
I count the parents, check the chain,
Replay the floats with care again—
A rabbit's nod when tests pass by.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Restack fixes' is vague and generic; it describes bug fixes but lacks specificity about what was actually fixed or improved in the changeset. Revise the title to be more specific about the main change, such as 'Refactor floating-base detection with match validation' or 'Fix floating-base detection and add restack validation tests'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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: 3

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

Inline comments:
In `@src/commands/pr.rs`:
- Around line 1391-1395: Add documentation and an inline comment clarifying that
the KIN_TEST_PR_EDIT_TITLE environment variable used in the non-interactive
branch (the code that checks !std::io::stdin().is_terminal() and returns
test_title) is strictly for tests; annotate that block (near the read of
KIN_TEST_PR_EDIT_TITLE) with a comment like "test-only: override PR edit title
in CI/non-interactive environments" and add a short note to the repository
testing guide or README describing the variable's purpose and usage to avoid
accidental use in production.

In `@src/commands/split.rs`:
- Around line 210-216: The apply_split function mutates refs (see apply_split
and the variables next_branches, new_branch_map, initial_branches, allowed_ids)
but currently writes HEAD last via set_head without a persisted recovery state;
add a durable state file (e.g., .kin/split-state) that records the planned ref
changes and metadata immediately after validation succeeds and before performing
any ref deletions/creations/moves, ensure every mutation step checks/updates or
at least leaves the state intact, and only remove/clear the .kin/split-state
after the final set_head (and all other operations) succeed; also add an
integration test that injects failures at each mutation step to verify that the
presence of .kin/split-state enables a continue/abort recovery path that reads
and replays or rolls back the recorded operations.

In `@tests/split_tests.rs`:
- Around line 268-273: The test currently only verifies branch creation and that
HEAD is not detached; update it to assert that HEAD points to the new branch and
still peels to the original commit id: call repo.head() (not just
repo.head_detached()) and assert its reference name / shorthand corresponds to
"new-feat" (the same branch found by repo.find_branch("new-feat", ...)), then
verify the peeled target of that HEAD reference equals head_id (similar to the
existing branch.get().target() check) so the test fails if HEAD is attached to
the wrong branch.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 087aa07d-1ac5-4a3c-814b-b714ce1fc3f9

📥 Commits

Reviewing files that changed from the base of the PR and between 04b5caa and 0a518ec.

📒 Files selected for processing (10)
  • README.md
  • docs/cli_reference.md
  • src/commands/mod.rs
  • src/commands/pr.rs
  • src/commands/split.rs
  • src/main.rs
  • src/stack.rs
  • tests/pr_tests.rs
  • tests/restack_tests.rs
  • tests/split_tests.rs

Comment thread src/commands/pr.rs
Comment on lines 1391 to +1395
if !std::io::stdin().is_terminal() {
if let Ok(test_title) = std::env::var("KIN_TEST_PR_EDIT_TITLE") {
println!(" [non-interactive] Using title override: {}", test_title);
return Ok(test_title);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider documenting this test-only environment variable.

The KIN_TEST_PR_EDIT_TITLE environment variable is added for non-interactive testing, which is a reasonable approach. Consider adding a comment noting this is for testing purposes only, or documenting it in a testing guide.

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

In `@src/commands/pr.rs` around lines 1391 - 1395, Add documentation and an inline
comment clarifying that the KIN_TEST_PR_EDIT_TITLE environment variable used in
the non-interactive branch (the code that checks !std::io::stdin().is_terminal()
and returns test_title) is strictly for tests; annotate that block (near the
read of KIN_TEST_PR_EDIT_TITLE) with a comment like "test-only: override PR edit
title in CI/non-interactive environments" and add a short note to the repository
testing guide or README describing the variable's purpose and usage to avoid
accidental use in production.

Comment thread src/commands/split.rs
Comment on lines 210 to 216
fn apply_split(
repo: &Repository,
next_branches: HashMap<String, String>,
new_branch_map: Vec<(String, String)>,
initial_branches: Vec<String>,
allowed_ids: HashSet<String>,
) -> Result<()> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Persist recovery state before the final HEAD rewrite.

apply_split mutates multiple refs (branch deletions, creations, and moves) without persisting a recovery state. This new set_head operation adds a late failure point after destructive ref updates may have already succeeded. If any error occurs during the final HEAD adjustment or afterward, kin split is left in a partially applied state with no continue/abort recovery path. Implement a persisted state file (e.g., .kin/split-state) that records the planned ref changes before any mutations occur. Write this state immediately after validation succeeds but before the first deletion. Only clear the state file after all operations—including the final HEAD rewrite—complete successfully. This also requires an integration test that simulates failure at each mutation step and verifies that the incomplete state allows recovery.

Also applies to: 329-338

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

In `@src/commands/split.rs` around lines 210 - 216, The apply_split function
mutates refs (see apply_split and the variables next_branches, new_branch_map,
initial_branches, allowed_ids) but currently writes HEAD last via set_head
without a persisted recovery state; add a durable state file (e.g.,
.kin/split-state) that records the planned ref changes and metadata immediately
after validation succeeds and before performing any ref
deletions/creations/moves, ensure every mutation step checks/updates or at least
leaves the state intact, and only remove/clear the .kin/split-state after the
final set_head (and all other operations) succeed; also add an integration test
that injects failures at each mutation step to verify that the presence of
.kin/split-state enables a continue/abort recovery path that reads and replays
or rolls back the recorded operations.

Comment thread tests/split_tests.rs
Comment on lines +268 to +273
assert!(!repo.head_detached().unwrap());
let branch = repo
.find_branch("new-feat", git2::BranchType::Local)
.unwrap();
assert_eq!(branch.get().target().unwrap(), head_id);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert the checked-out ref, not just branch creation.

These assertions still pass if kin split attaches HEAD to the wrong branch, as long as new-feat exists and HEAD stops being detached. Please also assert that HEAD is on new-feat (and ideally still peels to head_id) so this regression actually proves the checkout behavior.

💡 Tighten the assertion
     assert!(!repo.head_detached().unwrap());
+    assert_eq!(repo.head().unwrap().shorthand(), Some("new-feat"));
+    assert_eq!(repo.head().unwrap().peel_to_commit().unwrap().id(), head_id);
     let branch = repo
         .find_branch("new-feat", git2::BranchType::Local)
         .unwrap();
     assert_eq!(branch.get().target().unwrap(), head_id);

As per coding guidelines, "Any identified bug or edge case (e.g., panics, incorrect state) MUST be reproduced with a permanent test case before the fix is applied".

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

In `@tests/split_tests.rs` around lines 268 - 273, The test currently only
verifies branch creation and that HEAD is not detached; update it to assert that
HEAD points to the new branch and still peels to the original commit id: call
repo.head() (not just repo.head_detached()) and assert its reference name /
shorthand corresponds to "new-feat" (the same branch found by
repo.find_branch("new-feat", ...)), then verify the peeled target of that HEAD
reference equals head_id (similar to the existing branch.get().target() check)
so the test fails if HEAD is attached to the wrong branch.

@Pajn Pajn changed the base branch from main to optimize-sync March 30, 2026 16:42
@Pajn Pajn force-pushed the optimize-sync branch 4 times, most recently from 00e0d8d to ade4767 Compare March 30, 2026 20:31
Base automatically changed from optimize-sync to main March 30, 2026 20:45
@Pajn Pajn force-pushed the restack-fixes branch from 14169b5 to cca29fa Compare May 14, 2026 14:17
@Pajn Pajn force-pushed the restack-fixes branch from cca29fa to cf9e1a0 Compare May 21, 2026 16:11
@Pajn
Copy link
Copy Markdown
Owner Author

Pajn commented May 22, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/stack.rs (1)

932-956: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't stop at the first tree/patch candidate.

Both paths pick the first locally-matching target_index and only then call validate_floating_match. In histories with duplicate trees or repeated patch-ids, the first candidate can fail validation while an older candidate would pass, so this returns a false negative. Keep scanning until one candidate both matches locally and survives validation.

As per coding guidelines, tests/**/*.rs: Any identified bug or edge case (e.g., panics, incorrect state) MUST be reproduced with a permanent test case before the fix is applied.

Also applies to: 1059-1087

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stack.rs` around lines 932 - 956, The loop currently captures the first
locally-matching target_index then validates it, causing false negatives when
that candidate fails validation; change the logic in the loop over
target.candidates so that for each candidate where candidate.tree_id ==
commit.tree_id() and floating_parent_matches_candidate(...) returns true you
construct a FloatingBaseMatch (using branch_id=oid and the current target_index)
and call validate_floating_match(repo, branch_tip, matching, target,
patch_id_cache), returning Ok(Some(matching)) only if validation succeeds,
otherwise continue scanning remaining candidates; apply the same change to the
analogous block around lines 1059-1087; add a permanent test under tests/**/*.rs
that reproduces the duplicate-tree/duplicate-patch-id false-negative before the
fix and verifies the corrected behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/stack.rs`:
- Around line 811-823: The code resets private_len to commit_ids.len() when it
computes to 0, which re-includes the full history and breaks the private-lineage
filter; instead preserve a zero private_len so target.candidates can be empty
and ensure find_floating_match returns Ok(None) when target.candidates is empty;
specifically, stop the fallback that sets private_len = commit_ids.len()
(originating from floating_patch_id_boundary and the private_len calculation),
update find_floating_match to handle an empty target.candidates by returning
Ok(None) rather than erroring, and add a permanent test under tests/**/*.rs that
reproduces the zero-private-lineage case so the fix is covered.

---

Outside diff comments:
In `@src/stack.rs`:
- Around line 932-956: The loop currently captures the first locally-matching
target_index then validates it, causing false negatives when that candidate
fails validation; change the logic in the loop over target.candidates so that
for each candidate where candidate.tree_id == commit.tree_id() and
floating_parent_matches_candidate(...) returns true you construct a
FloatingBaseMatch (using branch_id=oid and the current target_index) and call
validate_floating_match(repo, branch_tip, matching, target, patch_id_cache),
returning Ok(Some(matching)) only if validation succeeds, otherwise continue
scanning remaining candidates; apply the same change to the analogous block
around lines 1059-1087; add a permanent test under tests/**/*.rs that reproduces
the duplicate-tree/duplicate-patch-id false-negative before the fix and verifies
the corrected behavior.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: a8ecb675-c2ce-4857-81dc-ca23ed30337d

📥 Commits

Reviewing files that changed from the base of the PR and between 0a518ec and cf9e1a0.

📒 Files selected for processing (2)
  • src/stack.rs
  • tests/restack_tests.rs

Comment thread src/stack.rs Outdated
@Pajn Pajn force-pushed the restack-fixes branch from cf9e1a0 to a57f083 Compare May 22, 2026 05:07
@Pajn
Copy link
Copy Markdown
Owner Author

Pajn commented May 22, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Pajn Pajn merged commit eae8e3d into main May 22, 2026
2 checks passed
@Pajn Pajn deleted the restack-fixes branch May 22, 2026 06:01
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