{"record":{"id":"040bf2a14ba37711","repo":"gitbutlerapp/gitbutler","slug":"validated-non-empty-squash-commit-list","errorCode":null,"errorMessage":"validated non-empty squash commit list","messagePattern":"validated non-empty squash commit list","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"info","filePath":"crates/but-workspace/src/branch/integrate_branch_upstream/plan.rs","lineNumber":285,"sourceCode":"                .into_iter()\n                .map(|selector| selector.expect(\"checked all selectors are present\"))\n                .collect::<Vec<_>>(),\n        )?;\n        ordered_selectors\n            .iter()\n            .map(|selector| {\n                editor\n                    .find_selectable_commit(*selector)\n                    .map(|(_, commit)| commit.id)\n            })\n            .collect::<Result<Vec<_>>>()?\n    } else {\n        commit_ids.to_vec()\n    };\n\n    let target_commit_id = *ordered_commit_ids\n        .first()\n        .expect(\"validated non-empty squash commit list\");\n    let merge_subject_ids = commit_ids\n        .iter()\n        .copied()\n        .filter(|commit_id| *commit_id != target_commit_id)\n        .collect::<Vec<_>>();\n    let merge_outcome = editor.merge_commit_changes_to_tree(\n        target_commit_id,\n        merge_subject_ids,\n        editor.repo().merge_options_force_ours()?,\n    )?;\n    let squashed_parent = editor\n        .repo()\n        .merge_base_octopus(ordered_commit_ids.iter().copied())\n        .context(\"failed to compute squash merge-base\")?\n        .detach();\n\n    let tip_commit_id = *ordered_commit_ids\n        .last()","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/branch/integrate_branch_upstream/plan.rs#L267-L303","documentation":"Panic from `expect(\"validated non-empty squash commit list\")` on `ordered_commit_ids.first()` in squash-step planning (plan.rs:285). The function bails at the top when `commit_ids.len() < 2`, and `ordered_commit_ids` is either the mapped selectors (same count) or `commit_ids.to_vec()`, so it has at least two elements and `first()` always yields the squash target. Unreachable by construction.","triggerScenarios":"Building a squash step from an interactive-integration plan; could only fire if the length guard is removed or `ordered_commit_ids` gains a filtering step that can empty it.","commonSituations":"Maintainers editing squash planning (e.g. dropping already-applied commits from the ordered list); none for end users.","solutions":["No caller-side action — the `len() < 2` bail above guarantees non-emptiness","If editing, use `let Some(target) = ordered_commit_ids.first() else { bail!(\"squash produced no commits\") };` so future edits cannot turn a logic slip into a panic","Keep a unit test for the single-commit rejection path"],"exampleFix":"// before\nlet target_commit_id = *ordered_commit_ids.first().expect(\"validated non-empty squash commit list\");\n\n// after\nlet Some(target_commit_id) = ordered_commit_ids.first() else {\n    bail!(\"squash step must have at least two commits\");\n};","handlingStrategy":"validation","validationCode":"// Reject squash plans with fewer than two commits before planning:\nfn valid_squash_step(commit_ids: &[ObjectId]) -> bool { commit_ids.len() >= 2 }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Plan validation already rejects len < 2 with a clear error","Maintainers: replace expect with let-else + bail when editing this function"],"tags":["rust","panic","expect","squash-plan","non-empty-guard","unreachable","gitbutler"],"backgroundTag":"internal-invariant-panic","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}