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
- Re-run the undo after re-checking that refs are unchanged (the undo snapshot may be stale)
- Inspect the wrapped source error to find the underlying ref-transaction failure (locks, permissions, ref shape)
- 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
- Re-verify ref state immediately before applying an undo
- Avoid concurrent git processes mutating refs during undo application
- Keep undo snapshots short-lived and refresh them when refs change
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
- BUG: tries to obtain object id from symbolic target
- BUG: expected peeled reference target but found symbolic one
- no item index implies having an object id
- this case should have been removed during processing
- Bug in lookup_symbol_has_path - must return lookup symbols
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)