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
- Capture all changes atomically from a single consistent snapshot of ref states.
- Refresh the intermediate `before` value by re-reading the current ref state between captures.
- Split into separate `record` calls so each change is validated against actual ref state.
- 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
- Capture all RefChanges from a single consistent snapshot.
- Re-read ref state between captures that happen at different times.
- Never guess `before` values; read the live ref instead.
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
- the undo queue cannot record itself
- tix stash reference does not use a canonical full commit ID
- rewritten commit already has saved worktree state
- already has saved worktree state
- the @ command and @ reference point to different results
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)