{"record":{"id":"541208608e8707fb","repo":"GitoxideLabs/gitoxide","slug":"cannot-delete-an-already-missing-reference","errorCode":null,"errorMessage":"cannot delete an already-missing reference","messagePattern":"cannot delete an already-missing reference","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/edit/undo.rs","lineNumber":521,"sourceCode":"\nfn rollback_transitions(transitions: &[WorktreeTransition], mut cause: anyhow::Error) -> anyhow::Error {\n    for transition in transitions.iter().rev() {\n        if let Err(err) = super::forget::apply_tree_transition(&transition.workdir, transition.new, transition.old) {\n            cause = cause.context(format!(\"worktree rollback failed: {err:#}\"));\n        }\n    }\n    cause\n}\n\nfn checked_edit(change: &RefChange) -> Result<RefEdit> {\n    let expected = match &change.before {\n        State::Missing => PreviousValue::MustNotExist,\n        State::Object(id) => PreviousValue::MustExistAndMatch(Target::Object(*id)),\n        State::Symbolic(name) => PreviousValue::MustExistAndMatch(Target::Symbolic(name.clone())),\n    };\n    let tx_change = match &change.after {\n        State::Missing => {\n            ensure!(\n                change.before != State::Missing,\n                \"cannot delete an already-missing reference\"\n            );\n            Change::Delete {\n                expected,\n                log: RefLog::AndReference,\n            }\n        }\n        State::Object(id) => Change::Update {\n            expected,\n            new: Target::Object(*id),\n            log: log_change(),\n        },\n        State::Symbolic(name) => Change::Update {\n            expected,\n            new: Target::Symbolic(name.clone()),\n            log: log_change(),\n        },","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/edit/undo.rs#L503-L539","documentation":"`checked_edit` converts a before/after state pair into a `gix` reference-transaction `Change`. When the `after` state is `Missing` (a delete), the `before` state must exist — deleting a ref that is already recorded as missing is contradictory (there is nothing to delete and nothing to verify), so it raises this error.","triggerScenarios":"Calling `set` or, via `apply_reversed_changes`, applying a queued `RefChange` whose `before` is `State::Missing` and `after` is `State::Missing`; also constructing a change by hand with both states Missing.","commonSituations":"Double-deleting the same reference in one batch; queue metadata where both before/after were Missing; buggy automation emitting no-op delete changes.","solutions":["Skip changes where `before == after` (both Missing) instead of recording/applying them.","Deduplicate RefChanges per ref name so a ref is deleted only once per batch.","If constructing changes manually, represent 'absent before and after' by omitting the change entirely."],"exampleFix":"// before: recording a no-op delete\nchanges.push(RefChange { name, before: State::Missing, after: State::Missing });\n// after: drop no-op changes\nif change.before != change.after {\n    changes.push(change);\n}","handlingStrategy":"validation","validationCode":"// drop no-op and contradictory changes before building the edit batch\nlet changes: Vec<RefChange> = changes\n    .into_iter()\n    .filter(|c| c.before != c.after)\n    .collect();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Skip changes where before and after are identical.","Deduplicate changes per ref name so deletes happen only once per batch.","Model 'absent before and after' as absence of a change, not a Missing->Missing change."],"tags":["git","references","delete","undo"],"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"}