GitoxideLabs/gitoxide · error

the rebase state contains duplicate scope commits

Error message

the rebase state contains duplicate scope commits

What it means

During state validation, `validate_state` collects all `scope` OIDs into a `HashSet` and compares its size against the original vector. A size mismatch means the same commit OID was listed in `scope` more than once; since scope membership is a set concept, duplicates indicate a malformed state and validation aborts.

Solutions

  1. Deduplicate `scope` lines in the state file so each commit OID appears once.
  2. Regenerate the state by restarting the rebase/edit operation.
  3. Fix any automation that builds the scope list to deduplicate OIDs before writing.

Example fix

// before (state file)
scope 0123abc...
scope 0123abc...
// after
scope 0123abc...
Defensive patterns

Strategy: validation

Validate before calling

fn scope_lines_are_unique(text: &str) -> bool {
    let ids: Vec<&str> = text.lines()
        .filter_map(|l| l.strip_prefix("scope "))
        .collect();
    ids.iter().collect::<std::collections::HashSet<_>>().len() == ids.len()
}

Try / catch

match todo::parse(state_text) {
    Ok(state) => apply(state),
    Err(e) if e.to_string().contains("duplicate scope commits") => {
        eprintln!("deduplicate scope lines before parsing");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: `parse_state` (via validate_state, gix-tix/src/edit/todo.rs:782) parses a state file where two or more `scope <oid>` lines repeat the same commit OID.

Common situations: Hand-edited state duplicating a scope entry; scripts appending scope commits without deduplication; merged state files from divergent sessions.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/edit/todo.rs:782

        head_ref,
        edit_refs,
        expected_refs,
        resolved,
        continuation_sources,
    };
    validate_state(repo, &state)?;
    Ok(Some(state))
}

fn validate_state(repo: &gix::Repository, state: &State) -> Result<()> {
    repo.find_commit(state.base)
        .context("could not find the recorded rebase base")?;
    repo.find_commit(state.onto)
        .context("could not find the recorded rebase target")?;
    let scope: HashSet<_> = state.scope.iter().copied().collect();
    let continuation_sources: HashSet<_> = state.continuation_sources.iter().copied().collect();
    if scope.len() != state.scope.len() {
        anyhow::bail!("the rebase state contains duplicate scope commits");
    }
    if state.tips.iter().copied().collect::<HashSet<_>>().len() != state.tips.len() {
        anyhow::bail!("the rebase state contains duplicate tips");
    }
    let mut refs = HashSet::new();
    for reference in &state.expected_refs {
        if !refs.insert(reference.name.as_bstr()) {
            anyhow::bail!("the rebase state contains duplicate refs");
        }
        if !scope.contains(&reference.target) && reference.target != state.base && reference.target != state.onto {
            anyhow::bail!("a captured ref does not logically point into the rebase scope");
        }
    }
    if let Some(name) = &state.head_ref
        && !state
            .expected_refs
            .iter()
            .any(|reference| reference.editable && reference.name == *name)

View on GitHub (pinned to e73179060b)