{"record":{"id":"a1ac9781c6289391","repo":"GitoxideLabs/gitoxide","slug":"moving-a-stack-requires-every-commit-to-have-exact","errorCode":null,"errorMessage":"moving a stack requires every commit to have exactly one parent","messagePattern":"moving a stack requires every commit to have exactly one parent","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/rebase.rs","lineNumber":665,"sourceCode":"    target: ObjectId,\n) -> Result<Plan> {\n    if repo.head_id()?.detach() != head {\n        anyhow::bail!(\"the move source must be the current HEAD\");\n    }\n    if !graph.is_ancestor(base, head) {\n        anyhow::bail!(\"the stack base must be an ancestor of HEAD\");\n    }\n    graph\n        .parents_of(target)\n        .context(\"the move target is not in the loaded history\")?;\n\n    let mut stack = vec![head];\n    let mut stack_parent = HashMap::new();\n    loop {\n        let id = *stack.last().expect(\"a stack always contains HEAD\");\n        let parents = graph.parents_of(id).context(\"a moved stack commit is incomplete\")?;\n        let [parent] = parents.as_slice() else {\n            anyhow::bail!(\"moving a stack requires every commit to have exactly one parent\");\n        };\n        stack_parent.insert(id, *parent);\n        if id == base {\n            break;\n        }\n        stack.push(*parent);\n    }\n    stack.reverse();\n    let stack_set: HashSet<_> = stack.iter().copied().collect();\n    if stack_set.contains(&target) {\n        anyhow::bail!(\"the move target must not be part of the moved stack\");\n    }\n    let base_parent = stack_parent[&base];\n    if base_parent == target {\n        anyhow::bail!(\"the stack is already directly above the move target\");\n    }\n\n    let target_rewritten = graph.is_ancestor(base, target);","sourceCodeStart":647,"sourceCodeEnd":683,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/rebase.rs#L647-L683","documentation":"While walking the stack from `head` down to `base`, every commit on the stack must have exactly one parent, because moving the stack re-parents each of its commits linearly. If any commit on the walk is a root (no parent) or a merge (multiple parents), the move cannot be represented as a plan and this error is raised.","triggerScenarios":"Calling `move_insert_plan`/`stack_insert_plan` where a commit between `base` and `head` (inclusive) has 0 or 2+ parents — e.g. HEAD's history contains a merge commit, or `base` is a root commit.","commonSituations":"Stacks built on top of a merge from another branch; trying to move a stack whose bottom is the repository's initial commit; repositories with interleaved merge workflow.","solutions":["Choose a `base` below any merge commits so the stack base..head is linear.","Rebase the stack linearly (e.g. rebase --onto, dropping merges) before moving it with tix.","Move only the linear segment above the merge by adjusting `base`."],"exampleFix":"// before\ntix move-stack --base <merge-commit> --head HEAD\n\n// after\nlet base = first_linear_commit_below_merges(&graph, head);\ntix move-stack --base base --head HEAD","handlingStrategy":"validation","validationCode":"let mut id = head;\nloop {\n    let parents = graph.parents_of(id)?;\n    if parents.len() != 1 {\n        eprintln!(\"stack contains a root or merge commit; cannot move\");\n        return Ok(());\n    }\n    if id == base { break; }\n    id = parents[0];\n}","typeGuard":"fn stack_is_linear(graph: &HistoryGraph, base: ObjectId, head: ObjectId) -> bool {\n    let mut id = head;\n    loop {\n        match graph.parents_of(id) {\n            Ok(p) if p.len() == 1 => {\n                if id == base { return true; }\n                id = p[0];\n            }\n            _ => return false,\n        }\n    }\n}","tryCatchPattern":"match stack_insert_plan(&repo, &graph, base, head, target) {\n    Err(e) if e.to_string().contains(\"exactly one parent\") => eprintln!(\"stack spans a merge; linearize first\"),\n    other => other?,\n}","preventionTips":["Only allow stack moves over first-parent-linear ranges","Mark merge commits clearly so users do not include them in a stack","Prefer single-commit moves (`move_insert_plan`) when the range is not verified linear"],"tags":["git","rebase","merge-commit","linear-history"],"backgroundTag":"unsupported-operation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}