GitoxideLabs/gitoxide · error

the squash target must be a strict ancestor of the source

Error message

the squash target must be a strict ancestor of the source

What it means

A squash in tix folds the source commit into a strict ancestor (the target). If the source equals the target, or the target is not an ancestor of the source in the loaded `HistoryGraph`, there is no valid fold direction, so the plan is rejected. This prevents creating nonsensical rewritten history.

Solutions

  1. Swap the arguments so the target is the older, ancestor commit and the source is the newer one.
  2. Choose a different target that is a strict ancestor of the source commit.
  3. If the commits are unrelated siblings, use a different operation (e.g. fixup via rebase) instead of squash.

Example fix

// before: wrong direction
plan_squash(repo, &graph, older_commit, newer_commit)?

// after: target must be the ancestor
plan_squash(repo, &graph, newer_commit, older_commit)?
Defensive patterns

Strategy: validation

Validate before calling

// Rust: verify ancestry before planning a squash
assert_ne!(source, target, "squash source and target must differ");
assert!(graph.is_ancestor(target, source), "target must be an ancestor of source");

Prevention

When it happens

Trigger: Calling the squash planning function (used by `perform_inner`) with `source == target`, or with a `target` that is a descendant of (or unrelated to) `source` according to `graph.is_ancestor(target, source)`.

Common situations: Selecting two sibling commits (neither is an ancestor of the other) for squash; accidentally passing the newer commit as the target; picking the same commit twice in the interactive UI.

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/4626cd681358bf33. Report an issue: GitHub.

Appendix: source

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

                new: Some(old),
                follows_tip: tips.contains(&old),
                editable: !reference.name().as_bstr().starts_with(crate::history::PIN_PREFIX)
                    && !reference.name().as_bstr().starts_with(crate::history::REVIEW_PREFIX),
                placement: None,
            });
        }
    }
    Ok(out)
}

pub(crate) fn squash_plan(
    repo: &gix::Repository,
    graph: &HistoryGraph,
    source: ObjectId,
    target: ObjectId,
) -> Result<Plan> {
    if source == target || !graph.is_ancestor(target, source) {
        anyhow::bail!("the squash target must be a strict ancestor of the source");
    }
    let source_parents = graph
        .parents_of(source)
        .context("the squash source is not in the loaded history")?;
    let [source_parent] = source_parents.as_slice() else {
        anyhow::bail!("the squash source must have exactly one parent");
    };
    let source_parent = *source_parent;
    let target_parents = graph
        .parents_of(target)
        .context("the squash target is not in the loaded history")?;
    let [base] = target_parents.as_slice() else {
        anyhow::bail!("the squash target must have exactly one parent");
    };
    let base = *base;
    let scope = graph
        .descendants_in_parent_order(target)
        .context("the squash target is not in the loaded history")?;

View on GitHub (pinned to e73179060b)