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

  1. Choose a move target whose affected descendant set is linear.
  2. Linearize the merge-heavy region (rebase without merges) before moving the stack.
  3. 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

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


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)