gitbutlerapp/gitbutler · error · anyhow::Error
Cherry picks can only be done for single-parent commits. Mer
Error message
Cherry picks can only be done for single-parent commits. Merge-commits need to be re-merged
What it means
During graph rebase, cherry-picking rewrites a commit's parent to its new base. `set_parent` refuses commits with more than one parent: a merge commit cannot be recreated by substituting a single parent — its multiple parents must be re-merged relative to the new base.
Source
Thrown at crates/but-rebase/src/cherry_pick.rs:81
&& commit_to_rebase.parents.contains(&base.id.detach())
{
return Ok(commit_to_rebase.id);
};
let mut cherry_pick = cherry_pick_tree(&base, &commit_to_rebase)?;
let tree_id = cherry_pick.tree.write()?;
let conflict_kind = gix::merge::tree::TreatAsUnresolved::forced_resolution();
if cherry_pick.has_unresolved_conflicts(conflict_kind) {
commit_from_conflicted_tree(base, commit_to_rebase, tree_id, cherry_pick, conflict_kind)
} else {
commit_from_unconflicted_tree(base, commit_to_rebase, tree_id, empty_commit)
}
}
fn set_parent(to_rebase: &mut gix::objs::Commit, new_parent: gix::ObjectId) -> anyhow::Result<()> {
if to_rebase.parents.len() > 1 {
bail!(
"Cherry picks can only be done for single-parent commits. Merge-commits need to be re-merged"
)
}
to_rebase.parents.clear();
to_rebase.parents.push(new_parent);
Ok(())
}
/// Rebase `to_rebase` onto `new_base`, dealing with the intricacies of conflicted trees, and return the newly
/// merged tree.
/// Note that all merges are made to succeed, possibly recording the original trees in a special tree.
fn cherry_pick_tree<'repo>(
new_base: &but_core::Commit<'repo>,
to_rebase: &but_core::Commit<'repo>,
) -> anyhow::Result<gix::merge::tree::Outcome<'repo>> {
let repo = to_rebase.id.repo;
let (base, ours, theirs) = find_cherry_pick_trees(new_base, to_rebase)?;
use but_core::RepositoryExt;View on GitHub (pinned to caf1f223d3)
Solutions
- Model merge commits as re-merge steps in the graph plan/editor instead of picks
- Skip or drop merge commits from the pick sequence where the semantics allow
- Fall back to git rebase strategies that preserve merges, or re-merge the commits manually
Example fix
// before: merge commit scheduled as a plain pick
plan.push(Step::Pick { id: merge_commit, ..Default::default() }); // bails in set_parent
// after: detect merges when building the plan and handle them separately
if parents_of(repo, commit_id)?.len() > 1 {
plan.push(Step::ReMerge { id: commit_id, .. }); // or skip
} else {
plan.push(Step::Pick { id: commit_id, ..Default::default() });
} Defensive patterns
Strategy: validation
Validate before calling
// Rust: classify merge commits while building the plan
let commit = repo.find_object(commit_id)?.try_into_commit()?;
let is_merge = commit.parent_ids().count() > 1;
if is_merge {
plan.push_re_merge(commit_id); // or skip
} else {
plan.push_pick(commit_id);
} Try / catch
Catch the bail, identify the merge commit from the plan, and either drop it from the pick sequence or switch it to a re-merge step before rebuilding.
Prevention
- Check parent counts when generating rebase plans; never schedule multi-parent commits as plain picks
- Test plan builders against merge-heavy fixture repositories
When it happens
Trigger: A Pick step in the rebase plan whose commit is a merge commit (parents.len() > 1) reaching `set_parent` — usually a plan built without merge handling or with merge steps misclassified as picks.
Common situations: Histories containing merge commits (PR merges, criss-cross merges) fed into a pick-only plan; custom graph tooling that forgets to special-case merges.
Related errors
- Failed to merge bases while cherry picking commit {target}.
- When using OpenRouter, you must provide a valid API key
- Commit {} was marked as not conflictable, but resulted in a
- Failed to find commit {target} in rebase editor
- Failed to find reference {target} in rebase editor
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/fbccdec4383bb62f.
Report an issue: GitHub.