affaan-m/ECC · error · anyhow::Error

git restore failed for {}: {stderr}

Error message

git restore failed for {}: {stderr}

What it means

reset_path runs `git restore --source=HEAD --staged --worktree -- <path>` for tracked entries (untracked entries are deleted from the filesystem instead). The bail! re-throws git's stderr. Failure means git could not restore both the index and working tree to HEAD for that path.

Source

Thrown at ecc2/src/worktree/mod.rs:353

        } else {
            fs::remove_file(&target)
                .with_context(|| format!("Failed to remove {}", target.display()))?;
        }
        return Ok(());
    }

    let output = Command::new("git")
        .arg("-C")
        .arg(&worktree.path)
        .args(["restore", "--source=HEAD", "--staged", "--worktree", "--"])
        .arg(&entry.path)
        .output()
        .with_context(|| format!("Failed to reset {}", entry.path))?;
    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("git restore failed for {}: {stderr}", entry.path);
    }
}

pub fn git_status_patch_view(
    worktree: &WorktreeInfo,
    entry: &GitStatusEntry,
) -> Result<Option<GitStatusPatchView>> {
    if entry.untracked {
        return Ok(None);
    }

    let staged_patch =
        git_diff_patch_text_for_paths(&worktree.path, &["--cached"], &[entry.path.clone()])?;
    let unstaged_patch = git_diff_patch_text_for_paths(&worktree.path, &[], &[entry.path.clone()])?;

    let mut sections = Vec::new();
    let mut hunks = Vec::new();

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Verify HEAD exists: `git -C <path> rev-parse --verify HEAD^{commit}` before calling reset_path.
  2. Handle the no-HEAD case by falling back to `git rm --cached` plus filesystem delete.
  3. Clear `.git/index.lock` if a previous git process was terminated.
  4. Run `git fsck` in the repo if object corruption is suspected.

Example fix

// before
reset_path(&worktree, &entry)?;

// after
let has_head = Command::new("git")
    .arg("-C").arg(&worktree.path)
    .args(["rev-parse", "--verify", "HEAD^{commit}"])
    .output()?.status.success();
if !has_head {
    anyhow::bail!("cannot reset: HEAD has no commits yet");
}
reset_path(&worktree, &entry)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_head_commit(worktree: &WorktreeInfo) -> anyhow::Result<bool> {
    let out = std::process::Command::new("git")
        .arg("-C").arg(&worktree.path)
        .args(["rev-parse", "--verify", "HEAD^{commit}"])
        .output()?;
    Ok(out.status.success())
}

if !has_head_commit(&worktree)? {
    anyhow::bail!("cannot reset tracked path: HEAD has no commits yet");
}
reset_path(&worktree, &entry)?;

Try / catch

match reset_path(&worktree, &entry) {
    Ok(()) => { /* refresh */ }
    Err(e) => {
        let m = format!("{e:#}");
        if m.contains("unknown revision") || m.contains("HEAD") {
            // fall back: remove from index + delete file
        } else {
            return Err(e);
        }
    }
}

Prevention

When it happens

Trigger: Resetting a path on a repository with no HEAD commit yet (unborn branch); a path that does not exist in HEAD (e.g. it was added in a different branch and never committed to base); `.git/index.lock` held by another process; corrupted loose object referenced by HEAD.

Common situations: Reset-all action on a freshly created worktree whose base branch has no commits; HEAD moved out from under the operation by a concurrent rebase/checkout; object database corrupted by disk-full or hardware error.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/a6091ab4ebc802f8. Report an issue: GitHub.