Prune integrated commits at or below the target from all stacks#13627
Prune integrated commits at or below the target from all stacks#13627
Conversation
f3ed8c2 to
85c2204
Compare
48e31b8 to
2dbc25f
Compare
|
@Caleb-T-Owens your pr #13632 makes the tests here green. 🙏 |
There was a problem hiding this comment.
Pull request overview
This PR updates the workspace projection’s “integrated commit pruning” so pruning applies to all stacks (including metadata-tracked stacks) and attempts to prune only integrated commits that are at-or-below a target boundary, with corresponding snapshot updates in but-graph tests.
Changes:
- Reworked
prune_integrated_segments()/truncate_at_fork_point()to prune an integrated tail using a computed “target commit” boundary. - Removed the metadata-tracked stack skip so pruning is applied uniformly across stacks.
- Updated
instasnapshots to reflect the new pruning/truncation behavior and simplified segment cleanup.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
crates/but-graph/src/projection/workspace/init.rs |
Rewrites integrated pruning logic to use a target-boundary and truncates stack segments accordingly. |
crates/but-graph/tests/graph/init/with_workspace.rs |
Updates workspace graph snapshots to match the new pruning behavior. |
2dbc25f to
f6ca178
Compare
f6ca178 to
a1fbed3
Compare
a1fbed3 to
75c6781
Compare
| for &(graph_sidx, ofs) in seg.commits_by_segment.iter().rev() { | ||
| for (i, commit) in graph[graph_sidx].commits.iter().enumerate().rev() { | ||
| let offset = ofs + i; | ||
| let above_target = seg_idx < target_seg_idx | ||
| || (seg_idx == target_seg_idx && offset < target_offset); | ||
| if above_target { | ||
| break 'outer; | ||
| } |
There was a problem hiding this comment.
We absolutely can not do comparisons on indices to determine this stuff.
We provide no guarantees in the order that the but graph is created. Even the order of edges can't be relied on.
I would encourage you to take an approach where you get a HashSet of all the commits beneath your target_commit, then loop over the segments, dropping commits or full segments if they're fully included in the HashSet
There was a problem hiding this comment.
If you just look at commits, empty refs will be ambiguous, but your traversal of the graph (visit_all_segments_including_start_until) will also let you gather the relevant reference names too
There was a problem hiding this comment.
Thanks, I've pushed a new version!
dfa1e84 to
ccb515e
Compare
ccb515e to
c0389f1
Compare
c0389f1 to
893b3fb
Compare
f56d615 to
e256a70
Compare
Previously, `prune_integrated_segments` skipped metadata-tracked stacks entirely and walked top-down looking for the first integrated commit. This had two problems: - Integrated commits *above* the target were incorrectly pruned, even though the user hasn't advanced the target via upstream integration. - The `is_metadata_tracked` guard hid all integrated state from those stacks, preventing `integrate_upstream` from detecting them. Replace the approach with HashSet-based graph reachability: 1. Walk the graph from the target segment outward (`visit_all_segments_excluding_start_until`) to collect all commit IDs that are ancestors of the target. 2. Slice the target segment itself from the target commit's actual position downward — this avoids the assumption that the target is always the first commit in its segment. 3. Walk each stack bottom-up, pruning the contiguous integrated tail only where commits are in the `at_or_below_target` set. When a segment is fully emptied by pruning, remove it entirely. 4. Preserve metadata-tracked empty branches that were already empty (e.g. a branch just added at the fork point with no commits yet), but remove everything else that ends up empty after pruning. Add a regression test (`integrated_commits_above_target_are_kept`) that verifies integrated commits above the target survive pruning. Preserve user-added empty branches at fork point When truncating stacks to the fork point we previously removed any empty segments, which also discarded branches that the user had just created at the fork point but that had no commits yet. This change defers removal of empty segments to a new helper that keeps segments if they are referenced in workspace metadata (i.e. a branch the user added at the fork point). It also correctly handles trimming a segment that is completely pruned by removing the segment rather than leaving an empty one.
e256a70 to
f56c6ab
Compare
|
Here's some context on each changed snapshot! Snapshot 1:
|
| Emoji | Meaning |
|---|---|
| 📕 | Workspace segment (SegmentMetadata::Workspace) |
| 📙 | Branch segment (SegmentMetadata::Branch) — metadata-tracked |
| (none) | Anonymous segment — no metadata association |
Commit kind (push status)
| Emoji | Meaning |
|---|---|
| ❄️ | ReachableByMatchingRemote — pushed to own remote (force-push req) |
| ❄ | ReachableByRemote — pushed to some other remote |
| 🟣 | RemoteOnly — exists only on remote |
| · | Local only — not pushed |
Commit flags (in parentheses)
| Symbol | Flag | Meaning |
|---|---|---|
| 🏘️ | InWorkspace |
Reachable from a workspace tip |
| ✓ | Integrated |
Reachable from the target branch (e.g. origin/main) |
Stack & ref symbols
| Symbol | Meaning |
|---|---|
| ≡ | Stack header prefix |
| [🌳] | Branch is checked out in the main worktree |
| ► | Extra refs at the same commit (e.g. ►four, ►three) |
| ⇡N | N local commits ahead of remote tracking branch |
on <sha> |
Stack base commit |
{N} |
Stack ID from workspace metadata |
Summary
Rewrites integrated-commit pruning so that all stacks are pruned equally, but only removes integrated commits at or below the workspace's
target_commit_id.What changed
is_metadata_trackedskip — metadata-tracked stacks are no longer exempt from pruningtruncate_at_fork_point— determines "at or below target" via graph reachability (walking outward from the target segment to collect aHashSetof commit IDs), not index comparisons. When a segment is fully emptied by pruning, it is removed entirely.is_metadata_trackedandfind_commit_in_stackhelpers — no longer neededintegrated_commits_above_target_are_keptverifies both that integrated commits above the target survive, and that they are pruned once the target advancesWhy
Two problems with the old behavior:
Integratedflag could remove branches whose work was merged upstream before the user ran integrate, preventingintegrate_upstreamfrom detecting and processing themHow it works
The pruning boundary is the workspace's
target_commit_id— the SHA stored in metadata that stays fixed until the user integrates upstream.To determine which commits are "at or below" the target, we:
Direction::Outgoing) from the target segment, collecting all reachable commit IDs into aHashSetThen for each stack, the contiguous integrated tail whose commits are in the set is removed:
When a branch is merged upstream but the user hasn't integrated yet, the target hasn't moved — so the branch and its commits stay visible. Once the user integrates and the target advances, they are pruned automatically.
Fully-integrated stacks are cleaned up entirely. Empty branches that existed before pruning (e.g. a newly added branch at the fork point) are preserved if they appear in workspace metadata.
Fallback
When
target_commitmetadata is not set (e.g. in tests or older workspaces), the target segment is resolved fromtarget_ref. This keeps pruning working in fixtures that don't settarget_commit_id.