GitoxideLabs/gitoxide · error
moving a stack cannot rewrite root or merge commits
Error message
moving a stack cannot rewrite root or merge commits
What it means
After determining the move, the plan computes new parents for every affected commit (stack commits plus descendants of base and target). Each affected commit must have exactly one parent for the re-parenting map to be well-defined; root or merge commits among the affected set cannot be rewritten, so this error is raised.
Solutions
- Choose a move target whose affected descendant set is linear.
- Linearize the merge-heavy region (rebase without merges) before moving the stack.
- Restrict the move to a region of history without merges, adjusting base/target accordingly.
Example fix
// before
tix move-stack --base base --head HEAD --onto <merge-adjacent-target>
// after
let affected: Vec<_> = affected_scope(&graph, base, target)?;
if affected.iter().any(|id| graph.parents_of(*id)?.len() != 1) {
eprintln!("move would rewrite a merge commit");
return Ok(());
}
tix move-stack --base base --head HEAD --onto target Defensive patterns
Strategy: validation
Validate before calling
// verify all commits whose parents will be rewritten are linear
for id in affected_scope_ids(&graph, base, target)? {
if graph.parents_of(id)?.len() != 1 {
eprintln!("move would rewrite a root or merge commit");
return Ok(());
}
} Type guard
fn move_scope_is_linear(graph: &HistoryGraph, base: ObjectId, target: ObjectId) -> bool {
let scope = |t| graph.descendants_in_parent_order(t).unwrap_or_default();
scope(base).iter().chain(scope(target).iter())
.all(|id| graph.parents_of(*id).map(|p| p.len() == 1).unwrap_or(false))
} Try / catch
match stack_insert_plan(&repo, &graph, base, head, target) {
Err(e) if e.to_string().contains("root or merge commits") => eprintln!("choose a target whose rewrite scope is linear"),
other => other?,
} Prevention
- Inspect descendants of both base and target before moving in merge-heavy histories
- Linearize merges in the affected region before stack operations
- Prefer regions of history verified linear when offering stack-move destinations
When it happens
Trigger: Calling `move_insert_plan`/`stack_insert_plan` where any commit in scope — descendants of `base` or of `target` (when target is rewritten) — is a root or merge commit, detected during the `new_parent` computation loop.
Common situations: Moving a stack under/over a region of history containing merge commits; repositories where the insertion target has merge descendants that must be rewritten.
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
- copying a commit requires it to have exactly one parent
- copying a commit cannot rewrite root or merge commits
- the squash source must have exactly one parent
- the squash target must have exactly one parent
- descendant merge commits cannot be squashed
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/aa585598e159441d.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/rebase.rs:705
.descendants_in_parent_order(base)
.context("the stack base is not in the loaded history")?
.into_iter()
.chain(
graph
.descendants_in_parent_order(target)
.context("the move target is not in the loaded history")?,
)
{
if (id != target || target_rewritten) && scope_set.insert(id) {
scope.push(id);
}
}
let mut new_parent = HashMap::with_capacity(scope.len());
for id in &scope {
let parents = graph.parents_of(*id).context("an affected move commit is incomplete")?;
let [parent] = parents.as_slice() else {
anyhow::bail!("moving a stack cannot rewrite root or merge commits");
};
new_parent.insert(
*id,
if *id == base {
target
} else if stack_set.contains(id) {
*parent
} else if let Some(old_parent) = stack_parent.get(parent) {
*old_parent
} else if *parent == target {
head
} else {
*parent
},
);
}
let mut steps = Vec::with_capacity(scope.len());View on GitHub (pinned to e73179060b)