GitoxideLabs/gitoxide · error

the stack base must be an ancestor of HEAD

Error message

the stack base must be an ancestor of HEAD

What it means

The stack-insert operation moves the contiguous linear run of commits from `base` up to `head`. For this run to exist, `base` must be an ancestor of `head`; otherwise there is no well-defined stack to move. `graph.is_ancestor(base, head)` returning false triggers this bail.

Solutions

  1. Pass a `base` that is an ancestor of `head`, typically the commit just below the stack you want to move.
  2. Verify with the graph before calling: `graph.is_ancestor(base, head)`.
  3. For moving a single commit, use `move_insert_plan` with base == head instead of a custom base.

Example fix

// before
stack_insert_plan(&repo, &graph, unrelated_base, head, target)?

// after
anyhow::ensure!(graph.is_ancestor(base, head), "base must be an ancestor of HEAD");
stack_insert_plan(&repo, &graph, base, head, target)?
Defensive patterns

Strategy: validation

Validate before calling

if !graph.is_ancestor(base, head) {
    eprintln!("base must be an ancestor of head");
    return Ok(());
}

Type guard

fn is_valid_stack(graph: &HistoryGraph, base: ObjectId, head: ObjectId) -> bool {
    graph.is_ancestor(base, head)
}

Try / catch

match stack_insert_plan(&repo, &graph, base, head, target) {
    Err(e) if e.to_string().contains("ancestor of HEAD") => eprintln!("base is not on HEAD's history; pick a base below the stack"),
    other => other?,
}

Prevention

When it happens

Trigger: Calling `move_insert_plan`/`stack_insert_plan(repo, graph, base, head, target)` where `base` is not an ancestor of `head` — e.g. passing two commits on divergent branches, or `base` on a branch that doesn't contain HEAD's history.

Common situations: Selecting a 'stack start' commit in a UI from a different branch than HEAD; scripts computing base/head pairs from the wrong range; history changed (rebase) so the previously valid base is no longer an ancestor.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/8440bac8a9deb517. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/rebase.rs:653

    graph: &HistoryGraph,
    source: ObjectId,
    target: ObjectId,
) -> Result<Plan> {
    stack_insert_plan(repo, graph, source, source, target)
}

pub(crate) fn stack_insert_plan(
    repo: &gix::Repository,
    graph: &HistoryGraph,
    base: ObjectId,
    head: ObjectId,
    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);

View on GitHub (pinned to e73179060b)