gitbutlerapp/gitbutler · info
checked all selectors are present
Error message
checked all selectors are present
What it means
Panic from `expect("checked all selectors are present")` in squash-step planning (but-workspace integrate_branch_upstream/plan.rs:268). The code maps commit ids to `try_select_commit` options, takes the `all(Option::is_some)` branch, and then unwraps each selector inside `map`. Because the branch is guarded by the `all()` check, every `expect` is provably Some; the assertion just records that coupling.
Source
Thrown at crates/but-workspace/src/branch/integrate_branch_upstream/plan.rs:268
/// before later integration graph mutations can rewire step-graph ancestry.
fn prepare_squash_step_for_editor<M: RefMetadata>(
editor: &Editor<'_, '_, M>,
commit_ids: &[gix::ObjectId],
message: Option<&str>,
) -> Result<gix::ObjectId> {
if commit_ids.len() < 2 {
bail!("Squash step must have at least two commits");
}
let maybe_selectors = commit_ids
.iter()
.map(|commit_id| editor.try_select_commit(*commit_id))
.collect::<Vec<_>>();
let ordered_commit_ids = if maybe_selectors.iter().all(Option::is_some) {
let ordered_selectors = editor.order_commit_selectors_by_parentage(
maybe_selectors
.into_iter()
.map(|selector| selector.expect("checked all selectors are present"))
.collect::<Vec<_>>(),
)?;
ordered_selectors
.iter()
.map(|selector| {
editor
.find_selectable_commit(*selector)
.map(|(_, commit)| commit.id)
})
.collect::<Result<Vec<_>>>()?
} else {
commit_ids.to_vec()
};
let target_commit_id = *ordered_commit_ids
.first()
.expect("validated non-empty squash commit list");
let merge_subject_ids = commit_idsView on GitHub (pinned to caf1f223d3)
Solutions
- No caller-side action
- If refactoring, replace option-juggling with `collect::<Option<Vec<_>>>()` so absence propagates as None instead of relying on the paired expect
- Add a plan-level test with one unselectable commit to pin the fallback branch
Example fix
// before
let maybe_selectors = commit_ids.iter().map(|id| editor.try_select_commit(*id)).collect::<Vec<_>>();
let ordered_commit_ids = if maybe_selectors.iter().all(Option::is_some) {
let ordered = editor.order_commit_selectors_by_parentage(
maybe_selectors.into_iter().map(|s| s.expect("checked all selectors are present")).collect()
)?;
...
// after
let maybe_selectors = commit_ids.iter().map(|id| editor.try_select_commit(*id)).collect::<Option<Vec<_>>>();
let ordered_commit_ids = if let Some(selectors) = maybe_selectors {
let ordered = editor.order_commit_selectors_by_parentage(selectors)?;
... Defensive patterns
Strategy: validation
Validate before calling
// If you pre-validate the plan, ensure every squash commit id is selectable:
let all_selectable = commit_ids.iter()
.all(|id| editor.try_select_commit(*id).is_some()); Prevention
- Unreachable in shipped code; the all(Option::is_some) guard precedes every unwrap
- Maintainers: use collect::<Option<Vec<_>>>() when refactoring so absence propagates naturally
When it happens
Trigger: Planning a squash step whose commit ids are all selectable in the editor; the panic could only fire if the `all()` guard and the later unwrap are separated by an edit (e.g. filtering selectors in between) — not reachable in the shipped code.
Common situations: Maintainers refactoring plan.rs (e.g. partial-selector fallback handling); none for end users.
Related errors
- validated non-empty squash commit list
- anchor is always present in the order at this point
- AtSegment anchor always has oldest commit resolved
- a committed transaction always materializes a workspace
- if it was found before it will be found as id
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/73f933c4a8b9e56b.
Report an issue: GitHub.