GitoxideLabs/gitoxide · error

HEAD must be the review commit or one of its successors…

Error message

HEAD must be the review commit or one of its successors before it can be finished

What it means

finishing a review requires HEAD to already be on the review commit or one of its descendants; the library checks `graph.is_ancestor(review, head)` before proceeding. If HEAD points elsewhere, the review's changes are not what is being finished and the operation is refused. This guards against finalizing a review from the wrong position in history.

Solutions

  1. Check out the review commit (or a successor) before calling finish: `git checkout <review-commit>` or use tix's travel/review commands.
  2. Verify you passed the correct review commit id to finish — HEAD may legitimately contain a different review.
  3. If the review branch was rebased, locate the rebased successor of the review commit and pass/checkout that instead.

Example fix

// before: finishing while HEAD sits on main
let finished = review::finish(repo, graph, review_id, None)?;
// after: ensure HEAD descends from the review first
let head = repo.head_id()?.detach();
assert!(graph.is_ancestor(review_id, head), "checkout the review commit first");
let finished = review::finish(repo, graph, review_id, None)?;
Defensive patterns

Strategy: validation

Validate before calling

let head = repo.head_id()?.detach();
if !graph.is_ancestor(review_id, head) {
    return Err(anyhow::anyhow!("checkout the review commit (or a successor) before finishing"));
}

Prevention

When it happens

Trigger: Calling `finish`/`finish_with_progress` while HEAD is an unrelated commit, an ancestor of the review commit, or on a divergent branch that does not contain the review commit.

Common situations: The user switched branches or time-traveled away from the review before finishing; the review was rebased so the old commit is no longer an ancestor of HEAD; a different review is checked out than the one passed to finish.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/edit/review.rs:237

    fallback: Option<ObjectId>,
) -> Result<Finish> {
    finish_with_progress(repo, graph, review, fallback, |_| {})
}

pub(crate) fn finish_with_progress(
    repo: gix::Repository,
    graph: &history::HistoryGraph,
    review: ObjectId,
    fallback: Option<ObjectId>,
    report: impl FnMut(super::rebase::Progress),
) -> Result<Finish> {
    let workdir = repo
        .workdir()
        .context("finishing review requires a worktree")?
        .to_owned();
    let head = repo.head_id()?.detach();
    if !graph.is_ancestor(review, head) {
        anyhow::bail!("HEAD must be the review commit or one of its successors before it can be finished");
    }
    ensure_clean(&workdir)?;
    let commit = repo.find_commit(review)?.decode()?.into_owned()?;
    let review_ref = reference(&commit)?.context("the selected commit is not an active review")?;
    let base = commit
        .parents
        .first()
        .copied()
        .context("a review commit must have a base")?;
    let mut reference = repo
        .find_reference(review_ref.as_ref())
        .context("the review reference is missing")?;
    let legacy_reattach = reference.target().try_name().map(ToOwned::to_owned);
    let tip = reference
        .peel_to_id()
        .context("the review reference does not resolve")?
        .detach();
    let delete_refs = resources(&repo, review_ref.clone())?;

View on GitHub (pinned to e73179060b)