{"record":{"id":"ff3e18668cf4ea52","repo":"gitbutlerapp/gitbutler","slug":"merge-strategy-must-only-be-used-on-stacks-with-on","errorCode":null,"errorMessage":"Merge strategy must only be used on stacks with one head and one bottom commit","messagePattern":"Merge strategy must only be used on stacks with one head and one bottom commit","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/upstream_integration.rs","lineNumber":300,"sourceCode":"        review_hints,\n    )?;\n\n    // Validate described updates and find commits to rebase\n    for stack in &mut stacks {\n        let relevant_updates = updates_with_selectors\n            .iter()\n            .filter(|(s, _)| stack.bottoms.contains(s))\n            .collect::<Vec<_>>();\n\n        if relevant_updates\n            .iter()\n            .any(|(_, kind)| *kind == BottomUpdateKind::Merge)\n        {\n            if relevant_updates.len() > 1 {\n                bail!(\"Found multiple updates for a stack using the merge strategy\");\n            }\n            if stack.heads.len() != 1 || stack.bottoms.len() != 1 {\n                bail!(\n                    \"Merge strategy must only be used on stacks with one head and one bottom commit\"\n                );\n            }\n\n            stack.to_merge = true\n        } else {\n            // currently the only other kind is rebase.\n            let mut tips = relevant_updates.iter().map(|(s, _)| *s).collect::<Vec<_>>();\n            let mut seen = tips.iter().cloned().collect::<HashSet<_>>();\n\n            while let Some(tip) = tips.pop() {\n                for c in editor\n                    .direct_children(tip)?\n                    .iter()\n                    .filter_map(|(c, _)| stack.nodes.contains_key(c).then_some(*c))\n                {\n                    if seen.insert(c) {\n                        tips.push(c);","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-workspace/src/upstream_integration.rs#L282-L318","documentation":"but-workspace's upstream integration supports a 'merge' bottom-update strategy for integrating upstream changes, but it can only be applied to a stack that resolves to exactly one head commit and one bottom commit. When a stack targeted with the Merge strategy has multiple heads or multiple bottoms, the library refuses rather than attempt an ambiguous merge integration.","triggerScenarios":"Calling integrate_upstream (or integrate_upstream_with_hints) where the stack's relevant updates include BottomUpdateKind::Merge while stack.heads.len() != 1 or stack.bottoms.len() != 1. Note a sibling check first rejects multiple updates for the same stack, so this fires on the single-update-but-multi-head/multi-bottom-stack shape.","commonSituations":"Running integration (e.g. 'but pull'/workspace integrate) on a stack that has diverged into multiple branches/heads, or whose base was split so it has several bottom commits, while a merge-based update from the remote is pending.","solutions":["Flatten the stack to a single head (merge or rebase its extra branches together) before integrating upstream","Ensure the stack has one bottom commit by splitting off extra bases into separate stacks","Use an integration strategy other than Merge (e.g. rebase-based updates) for multi-head stacks","Inspect stack.heads and stack.bottoms via the workspace API to confirm the shape before triggering integration"],"exampleFix":"// before: integrating a multi-head stack with a merge update\nintegrate_upstream(&repo, &workspace, /* hints with Merge */ hints)?;\n// after: guard the stack shape first\nanyhow::ensure!(stack.heads.len() == 1 && stack.bottoms.len() == 1, \"flatten stack before merge-integration\");\nintegrate_upstream(&repo, &workspace, hints)?;","handlingStrategy":"validation","validationCode":"if stack.heads.len() != 1 || stack.bottoms.len() != 1 {\n    anyhow::bail!(\"flatten stack to one head/bottom before merge-strategy integration\");\n}","typeGuard":"fn is_merge_integratable(stack: &Stack) -> bool {\n    stack.heads.len() == 1 && stack.bottoms.len() == 1\n}","tryCatchPattern":"match integrate_upstream(&repo, &workspace, hints) {\n    Err(e) if e.to_string().contains(\"one head and one bottom\") => flatten_stack_then_retry(),\n    r => r,\n}","preventionTips":["Keep one branch per stack when merge-based upstream integration is expected","Split multi-base stacks into separate stacks before integrating","Prefer rebase-style updates for stacks allowed to diverge","Check stack shape with the workspace API before requesting integration"],"tags":["git","workspace","merge-strategy","stack-shape"],"backgroundTag":"invalid-state-transition","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}