GitoxideLabs/gitoxide · error
merge commits cannot be picked by the rebase editor
Error message
merge commits cannot be picked by the rebase editor
What it means
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.
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
Example fix
// before
steps.push(pick(merge_id));
// after
if graph.parents_of(merge_id)?.len() == 1 {
steps.push(pick(merge_id));
} // merges skipped Defensive patterns
Strategy: validation
Validate before calling
fn all_linear(graph: &Graph, picks: &[ObjectId]) -> bool {
picks.iter().all(|p| graph.parents_of(p).map(|ps| ps.len() <= 1).unwrap_or(false))
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- the squash source must have exactly one parent
- the squash target must have exactly one parent
- descendant merge commits cannot be squashed
- copying a commit requires it to have exactly one parent
- copying a commit cannot rewrite root or merge commits
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/4d251adb63ea99c4.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/rebase.rs:1655
let scope: HashSet<_> = plan.scope.iter().copied().collect();
let mut picked = HashSet::new();
for step in &plan.steps {
if let PlanCommit::Copy(id) = step.commit
&& graph.parents_of(id).context("a copied commit is incomplete")?.len() != 1
{
anyhow::bail!("copying a commit requires it to have exactly one parent");
}
let ids = match step.commit {
PlanCommit::Pick(id) | PlanCommit::Resolved(id) => Some(id).into_iter().chain(step.squash.iter().copied()),
PlanCommit::Copy(_) | PlanCommit::Empty(_) => None.into_iter().chain(step.squash.iter().copied()),
};
for id in ids {
if !scope.contains(&id) || !picked.insert(id) {
anyhow::bail!("a rebase plan contains an invalid or duplicate pick");
}
if graph.parents_of(id).context("a picked commit is incomplete")?.len() > 1 {
anyhow::bail!("merge commits cannot be picked by the rebase editor");
}
}
}
let checkout_target = if repo.workdir().is_some() {
plan.checkout
.as_ref()
.map(|checkout| checkout.target)
.or(infer_plan_checkout(&repo, graph, &plan)?)
} else {
None
};
if plan.checkout.is_none()
&& checkout_target.is_some()
&& !plan
.steps
.iter()
.any(|step| matches!(step.commit, PlanCommit::Resolved(_)))View on GitHub (pinned to e73179060b)