{"record":{"id":"4626cd681358bf33","repo":"GitoxideLabs/gitoxide","slug":"the-squash-target-must-be-a-strict-ancestor-of-the","errorCode":null,"errorMessage":"the squash target must be a strict ancestor of the source","messagePattern":"the squash target must be a strict ancestor of the source","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/rebase.rs","lineNumber":446,"sourceCode":"                new: Some(old),\n                follows_tip: tips.contains(&old),\n                editable: !reference.name().as_bstr().starts_with(crate::history::PIN_PREFIX)\n                    && !reference.name().as_bstr().starts_with(crate::history::REVIEW_PREFIX),\n                placement: None,\n            });\n        }\n    }\n    Ok(out)\n}\n\npub(crate) fn squash_plan(\n    repo: &gix::Repository,\n    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\")?;","sourceCodeStart":428,"sourceCodeEnd":464,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/rebase.rs#L428-L464","documentation":"A squash in tix folds the source commit into a strict ancestor (the target). If the source equals the target, or the target is not an ancestor of the source in the loaded `HistoryGraph`, there is no valid fold direction, so the plan is rejected. This prevents creating nonsensical rewritten history.","triggerScenarios":"Calling the squash planning function (used by `perform_inner`) with `source == target`, or with a `target` that is a descendant of (or unrelated to) `source` according to `graph.is_ancestor(target, source)`.","commonSituations":"Selecting two sibling commits (neither is an ancestor of the other) for squash; accidentally passing the newer commit as the target; picking the same commit twice in the interactive UI.","solutions":["Swap the arguments so the target is the older, ancestor commit and the source is the newer one.","Choose a different target that is a strict ancestor of the source commit.","If the commits are unrelated siblings, use a different operation (e.g. fixup via rebase) instead of squash."],"exampleFix":"// before: wrong direction\nplan_squash(repo, &graph, older_commit, newer_commit)?\n\n// after: target must be the ancestor\nplan_squash(repo, &graph, newer_commit, older_commit)?","handlingStrategy":"validation","validationCode":"// Rust: verify ancestry before planning a squash\nassert_ne!(source, target, \"squash source and target must differ\");\nassert!(graph.is_ancestor(target, source), \"target must be an ancestor of source\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass the older commit as target and the newer as source.","In UIs, disable squash options when the selected pair is not ancestor/descendant."],"tags":["rebase","squash","history-graph","git","invalid-argument"],"backgroundTag":"invalid-argument-value","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"}