{"record":{"id":"f1dfce329971a10f","repo":"gitbutlerapp/gitbutler","slug":"heads-is-length-1","errorCode":null,"errorMessage":"Heads is length 1","messagePattern":"Heads is length 1","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"info","filePath":"crates/but-workspace/src/legacy/head.rs","lineNumber":65,"sourceCode":"pub fn remerged_workspace_tree_v2(\n    repo: &gix::Repository,\n    ws: &but_graph::Workspace,\n) -> Result<(gix::ObjectId, Vec<gix::ObjectId>, gix::ObjectId)> {\n    let target_base_oid = ws\n        .stored_target_commit_id()\n        .context(\"failed to get target base oid\")?;\n    let heads = ws\n        .stacks\n        .iter()\n        .map(|stack| stack.tip_skip_empty().unwrap_or(target_base_oid))\n        .collect::<Vec<_>>();\n    let workspace_tree_id = if heads.is_empty() {\n        but_core::Commit::try_from(repo.find_commit(target_base_oid)?)?\n            .tree_id_or_auto_resolution()?\n            .detach()\n    } else if heads.len() == 1 {\n        let commit = but_core::Commit::try_from(\n            repo.find_commit(*heads.first().expect(\"Heads is length 1\"))?,\n        )?;\n        commit.tree_id_or_auto_resolution()?.detach()\n    } else {\n        let base_tree_id = but_core::Commit::try_from(\n            repo.find_commit(repo.merge_base_octopus(heads.iter().copied())?)?,\n        )?\n        .tree_id_or_auto_resolution()?\n        .detach();\n        let mut workspace_tree_id = base_tree_id;\n\n        let (merge_options_fail_fast, conflict_kind) = repo.merge_options_fail_fast()?;\n        for head in &heads {\n            let stack_head = but_core::Commit::try_from(repo.find_commit(*head)?)?;\n            let branch_tree_id = stack_head.tree_id_or_auto_resolution()?.detach();\n\n            let mut merge = repo.merge_trees(\n                base_tree_id,\n                workspace_tree_id,","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/legacy/head.rs#L47-L83","documentation":"Panic from `expect(\"Heads is length 1\")` in legacy workspace-tree computation (but-workspace legacy/head.rs:65). The code buckets `heads` by count: empty uses the target tree, exactly one reads that commit's tree via `heads.first()`, and multiple does an octopus merge. The `expect` sits inside the `heads.len() == 1` arm, so `first()` is guaranteed Some; it is an unreachable-by-construction assertion.","triggerScenarios":"Computing the legacy workspace tree for a workspace with exactly one non-empty stack tip; the panic becomes reachable only if the `if/else if` chain is reordered so the `len() == 1` arm can see a different count.","commonSituations":"Maintainers touching legacy workspace-tree logic (e.g. adding empty-stack filtering inside the arms); none for end users.","solutions":["No caller-side action — the else-if structure guarantees the length","If refactoring, prefer destructuring: `let [head] = heads.as_slice() else { unreachable!() };` or restructure into a match on `heads.as_slice()` so each arm owns its length proof","Keep snapshot tests for 0/1/N stack workspaces"],"exampleFix":"// before\n} else if heads.len() == 1 {\n    let commit = but_core::Commit::try_from(\n        repo.find_commit(*heads.first().expect(\"Heads is length 1\"))?,\n    )?;\n\n// after: length proof in the pattern\nmatch heads.as_slice() {\n    [] => { /* target tree */ }\n    [head] => {\n        let commit = but_core::Commit::try_from(repo.find_commit(*head)?)?;\n        commit.tree_id_or_auto_resolution()?.detach()\n    }\n    _ => { /* octopus merge */ }\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Unreachable in shipped code; the len()==1 else-if arm owns the length proof","Maintainers: match on heads.as_slice() ([], [head], _) so each arm carries its own evidence"],"tags":["rust","panic","expect","length-guard","legacy","workspace-tree","unreachable"],"backgroundTag":"internal-invariant-panic","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}