GitoxideLabs/gitoxide · error

successive changes to

Error message

successive changes to {} are not continuous

What it means

When multiple `RefChange`s target the same reference in one batch, `normalize_changes` merges them by collapsing the sequence into `before = first.before` and `after = last.after`. This only works if the chain is continuous: each change's `before` must equal the previous change's `after`. If there is a gap (someone else moved the ref in between, or the changes were computed against different starting states), this error is raised.

Solutions

  1. Capture all changes atomically from a single consistent snapshot of ref states.
  2. Refresh the intermediate `before` value by re-reading the current ref state between captures.
  3. Split into separate `record` calls so each change is validated against actual ref state.
  4. Log and inspect the conflicting change's before/after to find where the state diverged.

Example fix

// before: stale snapshot for the second change
let c1 = RefChange { name, before: a, after: b };
let c2 = RefChange { name, before: stale_a, after: c };
// after: re-read the ref so c2.before matches c1.after
let c2 = RefChange { name, before: b, after: c };
Defensive patterns

Strategy: validation

Validate before calling

// assert continuity of per-ref change chains before recording
for (name, group) in changes_by_name {
    let ok = group.windows(2).all(|w| w[0].after == w[1].before);
    if !ok { anyhow::bail!("gap in changes for {name}"); }
}

Prevention

When it happens

Trigger: Passing two RefChanges for the same FullName to `record`/`changes_from_edits` where `change[n].before != change[n-1].after`, e.g. changes captured at different times or against different repositories.

Common situations: Concurrent modifications between captures; building change lists from stale snapshots; hand-assembling RefChanges with guessed before/after values.

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

Appendix: source

Thrown at gix-tix/src/edit/undo.rs:347

fn empty_position() -> Position {
    Position {
        title: START_TITLE.into(),
        undo: 0,
        redo: 0,
    }
}

fn normalize_changes(changes: impl IntoIterator<Item = RefChange>) -> Result<Vec<RefChange>> {
    let mut by_name = BTreeMap::<FullName, RefChange>::new();
    for change in changes {
        ensure!(
            !is_queue_ref(change.name.as_bstr()),
            "the undo queue cannot record itself"
        );
        match by_name.get_mut(&change.name) {
            Some(existing) => {
                ensure!(
                    existing.after == change.before,
                    "successive changes to {} are not continuous",
                    change.name
                );
                existing.after = change.after;
            }
            None => {
                by_name.insert(change.name.clone(), change);
            }
        }
    }
    Ok(by_name
        .into_values()
        .filter(|change| change.before != change.after)
        .collect())
}

fn state_from_expected(expected: &PreviousValue) -> Result<State> {

View on GitHub (pinned to e73179060b)