{"record":{"id":"1cf4b9c619e0a4ab","repo":"GitoxideLabs/gitoxide","slug":"descendant-merge-commits-cannot-be-rebased","errorCode":null,"errorMessage":"descendant merge commits cannot be rebased","messagePattern":"descendant merge commits cannot be rebased","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/rebase.rs","lineNumber":2547,"sourceCode":"    index.sort_entries();\n    index.remove_tree();\n    index\n        .write(gix::index::write::Options::default())\n        .context(\"could not update selected index paths\")\n}\n\nfn validate(\n    repo: &gix::Repository,\n    graph: &HistoryGraph,\n    affected: &[ObjectId],\n    removed: bool,\n    repeat: bool,\n    tree: Tree,\n) -> Result<()> {\n    for (position, id) in affected.iter().enumerate() {\n        let parents = graph.parents_of(*id).context(\"an affected commit is incomplete\")?;\n        if parents.len() > 1 && (position > 0 || removed || tree == Tree::CherryPick) {\n            anyhow::bail!(\"descendant merge commits cannot be rebased\");\n        }\n        if repeat && position == 0 {\n            let commit = repo.find_commit(*id)?.decode()?.into_owned()?;\n            if !is_pending(&commit) {\n                anyhow::bail!(\"the root of a repeated rebase must be pending\");\n            }\n        }\n    }\n    if repeat\n        && let Some(base) = affected.first()\n        && let Some(parent) = graph.parents_of(*base).and_then(|parents| parents.first().copied())\n        && is_pending(&repo.find_commit(parent)?.decode()?.into_owned()?)\n    {\n        anyhow::bail!(\"the parent of a repeated rebase must not be pending\");\n    }\n    Ok(())\n}\n","sourceCodeStart":2529,"sourceCodeEnd":2565,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/rebase.rs#L2529-L2565","documentation":"When rebasing/cherry-picking a set of affected commits, a descendant merge commit can only be tolerated at the tip (position 0) of an unremoved, non-cherry-pick rewrite. Any merge found deeper in the affected set — or at position 0 when commits are removed or the mode is cherry-pick — cannot be rewritten, so the operation bails.","triggerScenarios":"Rewriting (reword/drop/squash/cherry-pick) a commit that has merge commits among its descendants, with `position > 0`, or `removed == true`, or `tree == Tree::CherryPick`, detected via `parents_of(id).len() > 1`.","commonSituations":"Dropping or rewording an old commit when feature branches were merged into the line since; cherry-picking commits that later merges depend on; automated history edits over merge-heavy team histories.","solutions":["Linearize the descendants (rebase their merges away) before rewriting the target commit","Restrict the rewrite to commits without merge descendants (compute descendants first and abort/flatten merges)","For cherry-pick, copy only commits that are not ancestors of any merge"],"exampleFix":"// before\nreword(repo, old_commit)?; // old_commit has merge descendants\n// after\nif has_merge_descendants(&graph, old_commit) {\n    linearize_descendants(&repo, old_commit)?;\n}\nreword(repo, old_commit)?;","handlingStrategy":"validation","validationCode":"fn rewrite_safe(graph: &Graph, affected: &[ObjectId], position: usize, removed: bool, cherry_pick: bool) -> bool {\n    affected.iter().enumerate().all(|(i, id)| {\n        let merge = graph.parents_of(id).map(|p| p.len() > 1).unwrap_or(false);\n        !merge || (i == 0 && !removed && !cherry_pick && position == 0)\n    })\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"descendant merge commits\") => {\n        eprintln!(\"linearize or exclude commits with merge descendants before rewriting\");\n    }\n    r => r?,\n}","preventionTips":["Compute descendants and check for merges before any history rewrite","Linearize merge-heavy branches before reword/drop/cherry-pick operations","Restrict cherry-picks to commits not merged downstream"],"tags":["rebase","merge-commit","history-rewrite"],"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"}