GitoxideLabs/gitoxide · error

a continuation source is outside the rebase scope

Error message

a continuation source is outside the rebase scope

What it means

Continuation sources are commits where an interrupted rebase may resume; they must all belong to the captured rebase scope. If any continuation-source commit is outside the scope, the state cannot be trusted to resume safely and is rejected.

Solutions

  1. Delete the stale rebase state and restart the rebase
  2. Correct the continuation-source entries so they reference in-scope commits
  3. Ensure the rebase is started and resumed with the same tix version and repository state
Defensive patterns

Strategy: validation

Validate before calling

let sources: HashSet<_> = state.continuation_sources.iter().copied().collect();
let scope: HashSet<_> = state.scope.iter().copied().collect();
assert!(sources.is_subset(&scope), "all continuation sources must be in scope");

Try / catch

match result {
    Err(e) if e.to_string().contains("continuation source is outside the rebase scope") => {
        // state is inconsistent: discard and restart
    }
    r => r?,
}

Prevention

When it happens

Trigger: validate_state's final check `!continuation_sources.is_subset(&scope)` fires when the `continuation-source` entries parsed from the state file include ObjectIds absent from `state.scope`. Caused by hand-edited state, truncated writes, or stale state after history changed under it.

Common situations: Editing the state file to add resume points; resuming a rebase after another git operation rewrote commit IDs; copying rebase state across repositories.

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

Appendix: source

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

    }
    for id in &state.scope {
        let commit = repo
            .find_commit(*id)
            .context("could not find a recorded scope commit")?;
        let parent = commit
            .parent_ids()
            .next()
            .map(gix::Id::detach)
            .context("a recorded scope commit has no parent")?;
        if parent != state.base && !scope.contains(&parent) && !continuation_sources.contains(id) {
            anyhow::bail!("a recorded scope commit is disconnected from the rebase base");
        }
    }
    if state.resolved.is_some_and(|id| !scope.contains(&id)) {
        anyhow::bail!("the resolved conflict is outside the rebase scope");
    }
    if !continuation_sources.is_subset(&scope) {
        anyhow::bail!("a continuation source is outside the rebase scope");
    }
    Ok(())
}

pub(crate) fn parse(repo: &gix::Repository, edited: &[u8]) -> Result<Option<Parsed>> {
    repo.head()?.id().context("rebase todos require a born HEAD")?;
    let input = std::str::from_utf8(edited).context("the rebase todo is not UTF-8")?;
    let Some(mut state) = parse_state(repo, input)? else {
        return Ok(None);
    };
    let scope: HashSet<_> = state.scope.iter().copied().collect();
    let mut picked = HashMap::<ObjectId, usize>::new();
    let mut steps = Vec::<rebase::PlanStep>::new();
    let mut cursor = None;
    let mut checkout_target = None;
    let mut explicit_checkout_reference = None;
    let mut command_marker = false;
    let mut ref_targets = HashMap::new();

View on GitHub (pinned to e73179060b)