GitoxideLabs/gitoxide · error
undo/redo cannot delete a worktree HEAD
Error message
undo/redo cannot delete a worktree HEAD
What it means
During undo/redo, `worktree_transitions` projects what each affected worktree's HEAD will look like after applying the ref changes. If the projection yields `State::Missing` for a worktree HEAD, applying the changes would leave that worktree without a valid HEAD, which is unsupported, so the operation aborts with this error before touching anything.
Solutions
- Point the affected worktree's HEAD at a branch that survives the undo/redo (e.g. `git -C <worktree> switch <other-branch>`).
- Detach the worktree HEAD (`git switch --detach <commit>`) if the underlying branch must remain un-undoable.
- Remove the worktree (`git worktree remove`) if it is no longer needed.
- Re-order operations so the worktree HEAD is first re-created before undoing past its deletion.
Example fix
// before: undo deletes the branch worktree B has checked out -> HEAD would go Missing repo.undo()?; // after: re-point worktree B's HEAD first // git -C ../wt-b switch main repo.undo()?;
Defensive patterns
Strategy: validation
Validate before calling
// before undo, ensure no worktree HEAD resolves solely through refs the change deletes
for wt in affected_worktrees {
let head = wt.head_ref_name();
let dies = changes.iter().any(|c| &c.name == head && c.after == State::Missing);
if dies { anyhow::bail!("worktree {wt:?} HEAD would be deleted; re-point it first"); }
} Prevention
- Don't delete branches that are checked out in any worktree.
- Detach or re-point worktree HEADs before operations that delete their branch.
- List worktrees (`git worktree list`) and their HEADs before undo/redo.
When it happens
Trigger: Running undo or redo (`apply_with_worktrees` -> `worktree_transitions`) when the recorded changes delete or move the ref(s) that a worktree's HEAD (symbolic or direct) resolves through, such that the projected HEAD state becomes Missing.
Common situations: Undoing a change that deleted a branch which is another worktree's HEAD; deleting branches checked out in linked worktrees then trying to undo back across that deletion.
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
- Need a worktree to clean, this is a bare repository
- JSON output isn't implemented yet
- a tree change cannot be amended from the worktree
- cannot delete because another worktree has it checked out
- a detached checked-out root commit cannot be removed
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/57e880e1993dc7ba.
Report an issue: GitHub.
Appendix: source
Thrown at gix-tix/src/edit/undo.rs:426
}
let mut out = Vec::new();
let mut seen = HashSet::new();
for worktree_repo in repos {
if !seen.insert(worktree_repo.git_dir().to_owned()) {
continue;
}
let head = worktree_repo.head().context("could not inspect a worktree HEAD")?;
let old_id = head.id().map(gix::Id::detach);
let raw_head = worktree_repo
.find_reference("HEAD")
.context("could not read a worktree HEAD reference")?;
let raw_head = state_from_target_ref(raw_head.target());
let current = gix::path::realpath(worktree_repo.git_dir())
.context("could not resolve an affected worktree Git directory")?
== current_git_dir;
let projected_head = projected_ref_state(&worktree_repo, b"HEAD".as_bstr(), &raw_head, changes, current)?;
ensure!(
projected_head != State::Missing,
"undo/redo cannot delete a worktree HEAD"
);
let new_id = resolve_state(&worktree_repo, &projected_head, changes, current, &mut HashSet::new())?;
let old = tree_id(&worktree_repo, old_id).context("could not inspect the current worktree tree")?;
let new = tree_id(&worktree_repo, new_id).context("could not inspect the destination worktree tree")?;
if old == new || (worktree_repo.workdir().is_none() && worktree_repo.is_bare()) {
continue;
}
let workdir = worktree_repo
.workdir()
.filter(|path| path.is_dir())
.context("an affected worktree is inaccessible")?
.to_owned();
out.push(WorktreeTransition {
repo: worktree_repo,
workdir,
old,View on GitHub (pinned to e73179060b)