GitoxideLabs/gitoxide · error · anyhow::Error

could not atomically move references and the undo cursor

Error message

could not atomically move references and the undo cursor

What it means

When applying an undo, `edit_references()` failed to atomically move references and the undo cursor. Because the failure occurred after transitions were prepared, the prepared transitions are rolled back and the original error is wrapped with this context. It signals the atomic reference update could not be committed.

Solutions

  1. Re-run the undo after re-checking that refs are unchanged (the undo snapshot may be stale)
  2. Inspect the wrapped source error to find the underlying ref-transaction failure (locks, permissions, ref shape)
  3. Retry when the cause is transient lock contention
Defensive patterns

Strategy: try-catch

Validate before calling

// Before applying, verify refs still match the undo snapshot
for (ref_path, expected) in &snapshot.refs {
    if repo.find_reference(ref_path).map(|r| r.id()) != Ok(expected) {
        // undo is stale; refresh or abort
    }
}

Try / catch

if let Err(err) = undo.apply(&mut repo) {
    let cause: Option<&(dyn std::error::Error + 'static)> = err.source();
    while let Some(c) = cause {
        eprintln!("caused by: {c}");
        cause = c.source();
    }
    // rollback happened; refresh undo snapshot and retry
}

Prevention

When it happens

Trigger: `Repository::edit_references()` returns an error during `apply_with_worktrees()` — e.g. reference updates rejected by the ref store (lock contention, non-fast-forward against expectations, failing ref transaction).

Common situations: Concurrent modification of refs while undoing; refs changed by another process between undo capture and apply; filesystem permission or lock issues in `.git/refs`.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/62ab201fe4e6e8e1. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/undo.rs:105

                &transition.workdir,
                transition.old,
                transition.new,
            )
            .context("local changes prevent undo/redo; stash them manually and retry")?;
        }
        for (applied, transition) in transitions.iter().enumerate() {
            if let Err(err) = super::forget::apply_tree_transition(&transition.workdir, transition.old, transition.new)
            {
                return Err(rollback_transitions(
                    &transitions[..=applied],
                    err.context("could not align a worktree with the undo queue"),
                ));
            }
        }
        if let Err(err) = repo.edit_references(self.edits) {
            return Err(rollback_transitions(
                &transitions,
                anyhow::Error::new(err).context("could not atomically move references and the undo cursor"),
            ));
        }
        Ok(())
    }
}

pub(crate) fn is_queue_ref(name: &BStr) -> bool {
    name.as_bytes() == TIP_REF.as_bytes() || name.as_bytes() == CURSOR_REF.as_bytes()
}

pub(crate) fn ref_chain_reaches_queue(repo: &gix::Repository, name: &FullNameRef) -> Result<bool> {
    let mut name = name.to_owned();
    let mut seen = HashSet::new();
    loop {
        if is_queue_ref(name.as_bstr()) {
            return Ok(true);
        }
        ensure!(seen.insert(name.clone()), "a symbolic reference chain contains a cycle");

View on GitHub (pinned to e73179060b)