GitoxideLabs/gitoxide · error

the root of a repeated rebase must be pending

Error message

the root of a repeated rebase must be pending

What it means

This error is thrown during rebase edit validation in gix-tix. When an operation repeats a rebase (the `repeat` flag with the edited commit at position 0, i.e. it is the root of the rebased range), the root commit must itself be in a 'pending' rebase state. If the root commit is not pending, the repeated rebase would start from a commit that is not part of an unfinished rebase, so the edit is rejected.

Solutions

  1. Ensure the root commit of the repeated rebase is still in pending state before invoking the edit, or omit `repeat`/start the range at position > 0.
  2. Re-create or refresh the pending rebase state so the root is pending again.
  3. Pick the correct root commit id for the in-progress rebase instead of a completed one.
  4. If the rebase is actually finished, perform a normal (non-repeated) rebase edit instead.

Example fix

// before: repeating a rebase from an already-completed root
edit.rebase(root_of_finished_rebase, /* repeat */ true, ...)?;
// after: verify the root is pending, else do a fresh rebase
let commit = repo.find_commit(root_id)?.decode()?.into_owned()?;
let repeat = gix_tix::edit::rebase::is_pending(&commit);
edit.rebase(root_id, repeat, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

let commit = repo.find_commit(root_id)?.decode()?.into_owned()?;
if !gix_tix::edit::rebase::is_pending(&commit) {
    // either don't set repeat, or re-establish the pending state first
}

Type guard

fn is_repeatable_root(repo: &gix::Repository, id: ObjectId) -> anyhow::Result<bool> {
    Ok(gix_tix::edit::rebase::is_pending(&repo.find_commit(id)?.decode()?.into_owned()?))
}

Prevention

When it happens

Trigger: Calling a rebase-edit operation with `repeat` enabled where the first commit in the edit range (position == 0, single-parent commit whose id is `*id`) decodes to a commit that `is_pending()` reports as NOT pending. This happens in the validation loop after the merge-commit check (parents.len() > 1 && (position > 0 || removed || tree == Tree::CherryPick)).

Common situations: Re-running a stored/repeated rebase script after the pending rebase was already completed, aborted, or its root was rewritten by an earlier edit; pointing the edit at the wrong (already-finalized) root commit.

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/8ea09b34a088c932. Report an issue: GitHub.

Appendix: source

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

}

fn validate(
    repo: &gix::Repository,
    graph: &HistoryGraph,
    affected: &[ObjectId],
    removed: bool,
    repeat: bool,
    tree: Tree,
) -> Result<()> {
    for (position, id) in affected.iter().enumerate() {
        let parents = graph.parents_of(*id).context("an affected commit is incomplete")?;
        if parents.len() > 1 && (position > 0 || removed || tree == Tree::CherryPick) {
            anyhow::bail!("descendant merge commits cannot be rebased");
        }
        if repeat && position == 0 {
            let commit = repo.find_commit(*id)?.decode()?.into_owned()?;
            if !is_pending(&commit) {
                anyhow::bail!("the root of a repeated rebase must be pending");
            }
        }
    }
    if repeat
        && let Some(base) = affected.first()
        && let Some(parent) = graph.parents_of(*base).and_then(|parents| parents.first().copied())
        && is_pending(&repo.find_commit(parent)?.decode()?.into_owned()?)
    {
        anyhow::bail!("the parent of a repeated rebase must not be pending");
    }
    Ok(())
}

fn reject_pending_checkout_path(repo: &gix::Repository, mut id: ObjectId) -> Result<()> {
    let mut seen = HashSet::new();
    while seen.insert(id) {
        let commit = repo.find_commit(id)?.decode()?.into_owned()?;
        if is_pending(&commit) {

View on GitHub (pinned to e73179060b)