{"record":{"id":"0c58437473b4f802","repo":"GitoxideLabs/gitoxide","slug":"the-squash-target-must-have-exactly-one-parent","errorCode":null,"errorMessage":"the squash target must have exactly one parent","messagePattern":"the squash target must have exactly one parent","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/rebase.rs","lineNumber":459,"sourceCode":"    graph: &HistoryGraph,\n    source: ObjectId,\n    target: ObjectId,\n) -> Result<Plan> {\n    if source == target || !graph.is_ancestor(target, source) {\n        anyhow::bail!(\"the squash target must be a strict ancestor of the source\");\n    }\n    let source_parents = graph\n        .parents_of(source)\n        .context(\"the squash source is not in the loaded history\")?;\n    let [source_parent] = source_parents.as_slice() else {\n        anyhow::bail!(\"the squash source must have exactly one parent\");\n    };\n    let source_parent = *source_parent;\n    let target_parents = graph\n        .parents_of(target)\n        .context(\"the squash target is not in the loaded history\")?;\n    let [base] = target_parents.as_slice() else {\n        anyhow::bail!(\"the squash target must have exactly one parent\");\n    };\n    let base = *base;\n    let scope = graph\n        .descendants_in_parent_order(target)\n        .context(\"the squash target is not in the loaded history\")?;\n    let scope_set: HashSet<_> = scope.iter().copied().collect();\n    let mut non_leaves = HashSet::new();\n    for id in &scope {\n        let parents = graph\n            .parents_of(*id)\n            .context(\"an affected squash commit is incomplete\")?;\n        if parents.len() > 1 {\n            anyhow::bail!(\"descendant merge commits cannot be squashed\");\n        }\n        non_leaves.extend(parents.into_iter().filter(|parent| scope_set.contains(parent)));\n    }\n    let tips: Vec<_> = scope.iter().copied().filter(|id| !non_leaves.contains(id)).collect();\n    let mut steps = Vec::with_capacity(scope.len().saturating_sub(1));","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/rebase.rs#L441-L477","documentation":"The squash algorithm rewrites the target commit's single-parent base onto the new combined tree; it therefore requires the target to have exactly one parent. If the target is a root commit (no parents) or a merge commit (multiple parents), the rewrite cannot be expressed and the plan is rejected.","triggerScenarios":"Calling squash planning with a `target` whose `graph.parents_of(target)` yields anything other than exactly one parent — a root commit (0 parents) or a merge commit (2+ parents).","commonSituations":"Squashing into the repository's very first commit (root, no parent); accidentally choosing a merge commit as the squash target.","solutions":["Choose a non-root, non-merge commit as the squash target.","If the goal is to edit the root commit, use a root-aware edit/rebase workflow instead of squash.","Linearize history first so the intended target becomes an ordinary single-parent commit."],"exampleFix":"// before: squashing into the root commit\nplan_squash(repo, &graph, tip, root_commit)?\n// error: the squash target must have exactly one parent\n\n// after: squash into the root's first non-root descendant's parent chain,\n// or use a root-edit operation\nplan_squash(repo, &graph, tip, first_non_root_commit)?","handlingStrategy":"validation","validationCode":"// Rust: ensure the target has exactly one parent\nlet parents = graph.parents_of(target)?;\nif parents.len() != 1 { eprintln!(\"squash target must not be a root or merge commit\"); }","typeGuard":"fn is_single_parent(commit: ObjectId, graph: &HistoryGraph) -> bool {\n    graph.parents_of(commit).map(|p| p.len() == 1).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Do not squash into the repository's root commit; use root-edit workflows instead.","Exclude merge commits from squash-target selection."],"tags":["rebase","squash","merge-commit","root-commit","git"],"backgroundTag":"unsupported-operation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}