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

  1. Skip changes where `before == after` (both Missing) instead of recording/applying them.
  2. Deduplicate RefChanges per ref name so a ref is deleted only once per batch.
  3. 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

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


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)