GitoxideLabs/gitoxide · error
cannot delete an already-missing reference
Error message
cannot delete an already-missing reference
What it means
`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.
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.
Example fix
// before: recording a no-op delete
changes.push(RefChange { name, before: State::Missing, after: State::Missing });
// after: drop no-op changes
if change.before != change.after {
changes.push(change);
} Defensive patterns
Strategy: validation
Validate before calling
// drop no-op and contradictory changes before building the edit batch
let changes: Vec<RefChange> = changes
.into_iter()
.filter(|c| c.before != c.after)
.collect(); Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Double-deleting the same reference in one batch; queue metadata where both before/after were Missing; buggy automation emitting no-op delete changes.
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
- tix stash reference does not use a canonical full commit ID
- rewritten commit already has saved worktree state
- already has saved worktree state
- could not read reference
- could not inspect a stash reference before rebasing
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/541208608e8707fb.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/undo.rs:521
fn rollback_transitions(transitions: &[WorktreeTransition], mut cause: anyhow::Error) -> anyhow::Error {
for transition in transitions.iter().rev() {
if let Err(err) = super::forget::apply_tree_transition(&transition.workdir, transition.new, transition.old) {
cause = cause.context(format!("worktree rollback failed: {err:#}"));
}
}
cause
}
fn checked_edit(change: &RefChange) -> Result<RefEdit> {
let expected = match &change.before {
State::Missing => PreviousValue::MustNotExist,
State::Object(id) => PreviousValue::MustExistAndMatch(Target::Object(*id)),
State::Symbolic(name) => PreviousValue::MustExistAndMatch(Target::Symbolic(name.clone())),
};
let tx_change = match &change.after {
State::Missing => {
ensure!(
change.before != State::Missing,
"cannot delete an already-missing reference"
);
Change::Delete {
expected,
log: RefLog::AndReference,
}
}
State::Object(id) => Change::Update {
expected,
new: Target::Object(*id),
log: log_change(),
},
State::Symbolic(name) => Change::Update {
expected,
new: Target::Symbolic(name.clone()),
log: log_change(),
},View on GitHub (pinned to e73179060b)