Compute behind count from octopus merge-base and update upstream display#13689
Compute behind count from octopus merge-base and update upstream display#13689
Conversation
Caleb-T-Owens
left a comment
There was a problem hiding this comment.
Lgtm.
Kind of confusing that the target ref commit is called target_commit_id given that terms is now synonyms with the target sha, but it's fine I guess
There was a problem hiding this comment.
Pull request overview
Adjusts how BaseBranch.behind / upstream_commits are computed so the UI “sync” state reflects the farthest-behind applied stack, using an octopus merge-base across workspace stack tips and the target tip.
Changes:
- Updates
target_to_base_branch()to compute an octopus merge-base across workspace parents + target tip, then counts first-parent commits from target tip to that base. - Updates existing
set_base_branchtests to match the newbehindsemantics. - Adds a new fixture + regression test covering multiple stacks with different fork points.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| crates/gitbutler-branch-actions/src/base.rs | Recomputes upstream_commits/behind using octopus merge-base of workspace stack tips and target tip. |
| crates/gitbutler-branch-actions/tests/branch-actions/virtual_branches/set_base_branch.rs | Updates assertions for new behind behavior; adds regression test for “farthest-behind stack”. |
| crates/gitbutler-branch-actions/tests/fixtures/scenario/three-stacks-different-bases.sh | New git fixture creating two stacks with different bases to validate behind-count logic. |
@Caleb-T-Owens I think you'll find about 150 mentions of this term in the code base, but I agree, I'd prefer |
| # Two stacks that fork from different points on master, with origin/master | ||
| # advanced past both. This creates a scenario where each stack has | ||
| # a different number of "behind" commits relative to the target. |
| .map(|id| id.detach()) | ||
| .collect(); | ||
| if heads.is_empty() { | ||
| None |
Change how `upstream_commits` and `behind` are computed on `BaseBranch`: instead of counting commits between the target tip and the stored `target.sha`, compute the octopus merge-base across all workspace stack tips (from the workspace ref) and the target tip, then walk first-parent commits from the target tip down to that merge-base. This makes the behind count reflect the farthest-behind stack in the workspace, which is what the sync button needs to show.
| let heads: Vec<_> = repo | ||
| .find_reference(WORKSPACE_REF_NAME)? | ||
| .peel_to_commit()? | ||
| .parent_ids() | ||
| .map(|id| id.detach()) | ||
| .collect(); |
| let base = repo | ||
| .merge_base_octopus([heads.as_slice(), &[target_commit_id]].concat()) | ||
| .context("failed to compute octopus merge-base for workspace stacks")?; | ||
| Some(base.object()?.id().detach()) | ||
| } |
| Some(target.sha) | ||
| } else { | ||
| let base = repo | ||
| .merge_base_octopus([heads.as_slice(), &[target_commit_id]].concat()) | ||
| .context("failed to compute octopus merge-base for workspace stacks")?; | ||
| Some(base.object()?.id().detach()) | ||
| } | ||
| }; | ||
|
|
||
| // Commits on the target branch between its tip and the lowest merge base. | ||
| let upstream_commit_ids = lowest_merge_base | ||
| .map(|lb| first_parent_commit_ids_until(repo, target_commit_id, lb)) | ||
| .transpose() | ||
| .context("failed to get upstream commits")? | ||
| .unwrap_or_default(); |
Summary
Behind count now reflects the farthest-behind stack
behindandupstream_commitsonBaseBranchare now computed using theoctopus merge-base across all workspace stack tips and the target tip, instead
of counting from the stored
target.sha. This means the behind count showsthe worst case across all stacks in the workspace.
Updated CLI display
Before:
After:
(merge base)with commit count between the two.(common base)— the deepest sharedancestor across all stacks. Previously showed
[origin/main], which wasmisleading since this commit isn't where the target ref points.
--upstream, the individual commits are listed between the two lines.Desktop app button
N behindwith a single stack,≤N behindwith multiple stacks.Robustness improvements (from copilot review)
try_find_referencefor the workspace ref with fallback totarget.sha.merge_base_octopuscall.lowest_merge_basefromOption<ObjectId>to plainObjectId.Test plan
cargo test --workspace— all tests passbehind_reflects_farthest_behind_stackasserts the behind countmatches the farthest-behind stack (3 behind, not 1)
but statusandbut status --upstreamdisplayN behind/≤N behind🤖 Generated with Claude Code