{"record":{"id":"7e60f941550f105c","repo":"GitoxideLabs/gitoxide","slug":"successive-changes-to-are-not-continuous","errorCode":null,"errorMessage":"successive changes to {} are not continuous","messagePattern":"successive changes to (.+?) are not continuous","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/undo.rs","lineNumber":347,"sourceCode":"\nfn empty_position() -> Position {\n    Position {\n        title: START_TITLE.into(),\n        undo: 0,\n        redo: 0,\n    }\n}\n\nfn normalize_changes(changes: impl IntoIterator<Item = RefChange>) -> Result<Vec<RefChange>> {\n    let mut by_name = BTreeMap::<FullName, RefChange>::new();\n    for change in changes {\n        ensure!(\n            !is_queue_ref(change.name.as_bstr()),\n            \"the undo queue cannot record itself\"\n        );\n        match by_name.get_mut(&change.name) {\n            Some(existing) => {\n                ensure!(\n                    existing.after == change.before,\n                    \"successive changes to {} are not continuous\",\n                    change.name\n                );\n                existing.after = change.after;\n            }\n            None => {\n                by_name.insert(change.name.clone(), change);\n            }\n        }\n    }\n    Ok(by_name\n        .into_values()\n        .filter(|change| change.before != change.after)\n        .collect())\n}\n\nfn state_from_expected(expected: &PreviousValue) -> Result<State> {","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/undo.rs#L329-L365","documentation":"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.","triggerScenarios":"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.","commonSituations":"Concurrent modifications between captures; building change lists from stale snapshots; hand-assembling RefChanges with guessed before/after values.","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."],"exampleFix":"// before: stale snapshot for the second change\nlet c1 = RefChange { name, before: a, after: b };\nlet c2 = RefChange { name, before: stale_a, after: c };\n// after: re-read the ref so c2.before matches c1.after\nlet c2 = RefChange { name, before: b, after: c };","handlingStrategy":"validation","validationCode":"// assert continuity of per-ref change chains before recording\nfor (name, group) in changes_by_name {\n    let ok = group.windows(2).all(|w| w[0].after == w[1].before);\n    if !ok { anyhow::bail!(\"gap in changes for {name}\"); }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["git","references","consistency","undo-queue"],"backgroundTag":"invalid-state-transition","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}