{"record":{"id":"4d251adb63ea99c4","repo":"GitoxideLabs/gitoxide","slug":"merge-commits-cannot-be-picked-by-the-rebase-edito","errorCode":null,"errorMessage":"merge commits cannot be picked by the rebase editor","messagePattern":"merge commits cannot be picked by the rebase editor","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/rebase.rs","lineNumber":1655,"sourceCode":"\n    let scope: HashSet<_> = plan.scope.iter().copied().collect();\n    let mut picked = HashSet::new();\n    for step in &plan.steps {\n        if let PlanCommit::Copy(id) = step.commit\n            && graph.parents_of(id).context(\"a copied commit is incomplete\")?.len() != 1\n        {\n            anyhow::bail!(\"copying a commit requires it to have exactly one parent\");\n        }\n        let ids = match step.commit {\n            PlanCommit::Pick(id) | PlanCommit::Resolved(id) => Some(id).into_iter().chain(step.squash.iter().copied()),\n            PlanCommit::Copy(_) | PlanCommit::Empty(_) => None.into_iter().chain(step.squash.iter().copied()),\n        };\n        for id in ids {\n            if !scope.contains(&id) || !picked.insert(id) {\n                anyhow::bail!(\"a rebase plan contains an invalid or duplicate pick\");\n            }\n            if graph.parents_of(id).context(\"a picked commit is incomplete\")?.len() > 1 {\n                anyhow::bail!(\"merge commits cannot be picked by the rebase editor\");\n            }\n        }\n    }\n\n    let checkout_target = if repo.workdir().is_some() {\n        plan.checkout\n            .as_ref()\n            .map(|checkout| checkout.target)\n            .or(infer_plan_checkout(&repo, graph, &plan)?)\n    } else {\n        None\n    };\n    if plan.checkout.is_none()\n        && checkout_target.is_some()\n        && !plan\n            .steps\n            .iter()\n            .any(|step| matches!(step.commit, PlanCommit::Resolved(_)))","sourceCodeStart":1637,"sourceCodeEnd":1673,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/rebase.rs#L1637-L1673","documentation":"The rebase editor can only rewrite linear history, so picking a commit with more than one parent (a merge) into the plan is forbidden. The validator checks `parents_of(id).len() > 1` for each picked or squashed id and bails.","triggerScenarios":"A plan includes a `PlanCommit::Pick` or a squash entry whose commit is a merge commit; scripts that build todo lists from `git log` output including merge entries.","commonSituations":"Rebasing across merge-heavy feature branches; copying a todo list generated without `--no-merges`; users explicitly reordering merge commits expecting git-preserve-merges semantics that this editor does not implement.","solutions":["Exclude merge commits from the pick list (use no-merges enumeration)","Linearize the history before rebasing (rebase merges into linear commits first)","Remove the merge pick step from the plan and keep the merge out of the rewritten range"],"exampleFix":"// before\nsteps.push(pick(merge_id));\n// after\nif graph.parents_of(merge_id)?.len() == 1 {\n    steps.push(pick(merge_id));\n} // merges skipped","handlingStrategy":"validation","validationCode":"fn all_linear(graph: &Graph, picks: &[ObjectId]) -> bool {\n    picks.iter().all(|p| graph.parents_of(p).map(|ps| ps.len() <= 1).unwrap_or(false))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Generate pick lists with merges excluded (no-merges semantics)","Never hand-edit todo files to add merge lines","Linearize history before rebasing merge-heavy branches"],"tags":["rebase","merge-commit","plan-validation"],"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"}