GitoxideLabs/gitoxide · error
moving a stack requires every commit to have exactly one…
Error message
moving a stack requires every commit to have exactly one parent
What it means
While walking the stack from `head` down to `base`, every commit on the stack must have exactly one parent, because moving the stack re-parents each of its commits linearly. If any commit on the walk is a root (no parent) or a merge (multiple parents), the move cannot be represented as a plan and this error is raised.
Solutions
- Choose a `base` below any merge commits so the stack base..head is linear.
- Rebase the stack linearly (e.g. rebase --onto, dropping merges) before moving it with tix.
- Move only the linear segment above the merge by adjusting `base`.
Example fix
// before tix move-stack --base <merge-commit> --head HEAD // after let base = first_linear_commit_below_merges(&graph, head); tix move-stack --base base --head HEAD
Defensive patterns
Strategy: validation
Validate before calling
let mut id = head;
loop {
let parents = graph.parents_of(id)?;
if parents.len() != 1 {
eprintln!("stack contains a root or merge commit; cannot move");
return Ok(());
}
if id == base { break; }
id = parents[0];
} Type guard
fn stack_is_linear(graph: &HistoryGraph, base: ObjectId, head: ObjectId) -> bool {
let mut id = head;
loop {
match graph.parents_of(id) {
Ok(p) if p.len() == 1 => {
if id == base { return true; }
id = p[0];
}
_ => return false,
}
}
} Try / catch
match stack_insert_plan(&repo, &graph, base, head, target) {
Err(e) if e.to_string().contains("exactly one parent") => eprintln!("stack spans a merge; linearize first"),
other => other?,
} Prevention
- Only allow stack moves over first-parent-linear ranges
- Mark merge commits clearly so users do not include them in a stack
- Prefer single-commit moves (`move_insert_plan`) when the range is not verified linear
When it happens
Trigger: Calling `move_insert_plan`/`stack_insert_plan` where a commit between `base` and `head` (inclusive) has 0 or 2+ parents — e.g. HEAD's history contains a merge commit, or `base` is a root commit.
Common situations: Stacks built on top of a merge from another branch; trying to move a stack whose bottom is the repository's initial commit; repositories with interleaved merge workflow.
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/a1ac9781c6289391.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/rebase.rs:665
target: ObjectId,
) -> Result<Plan> {
if repo.head_id()?.detach() != head {
anyhow::bail!("the move source must be the current HEAD");
}
if !graph.is_ancestor(base, head) {
anyhow::bail!("the stack base must be an ancestor of HEAD");
}
graph
.parents_of(target)
.context("the move target is not in the loaded history")?;
let mut stack = vec![head];
let mut stack_parent = HashMap::new();
loop {
let id = *stack.last().expect("a stack always contains HEAD");
let parents = graph.parents_of(id).context("a moved stack commit is incomplete")?;
let [parent] = parents.as_slice() else {
anyhow::bail!("moving a stack requires every commit to have exactly one parent");
};
stack_parent.insert(id, *parent);
if id == base {
break;
}
stack.push(*parent);
}
stack.reverse();
let stack_set: HashSet<_> = stack.iter().copied().collect();
if stack_set.contains(&target) {
anyhow::bail!("the move target must not be part of the moved stack");
}
let base_parent = stack_parent[&base];
if base_parent == target {
anyhow::bail!("the stack is already directly above the move target");
}
let target_rewritten = graph.is_ancestor(base, target);View on GitHub (pinned to e73179060b)