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
- Pass a `base` that is an ancestor of `head`, typically the commit just below the stack you want to move.
- Verify with the graph before calling: `graph.is_ancestor(base, head)`.
- 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
- Derive base from the graph (e.g. walk parents from head) instead of accepting arbitrary ids
- Restrict UI stack-base selection to commits on HEAD's first-parent chain
- Re-verify ancestry after any history-changing operation
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
- the squash target must be a strict ancestor of the source
- the copy source and target must differ
- the move target must not be part of the moved stack
- Cannot derive commit or tree from blob at
- rebase todo requires at least one -x/--hide revision when…
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)