affaan-m/ECC · warning · anyhow::Error
cannot reset a staged hunk while the file also has unstaged
Error message
cannot reset a staged hunk while the file also has unstaged changes; unstage it first
What it means
reset_hunk on a Staged hunk additionally requires that the file has no unstaged changes (`!entry.unstaged`). The reverse-with-`--index` apply would otherwise desynchronize the index from a working tree that has independent unstaged edits, leaving the repo in a confusing half-reset state. The library refuses rather than risk silent corruption.
Source
Thrown at ecc2/src/worktree/mod.rs:441
)
}
pub fn reset_hunk(
worktree: &WorktreeInfo,
entry: &GitStatusEntry,
hunk: &GitPatchHunk,
) -> Result<()> {
if entry.untracked {
anyhow::bail!("cannot reset hunks for untracked files");
}
match hunk.section {
GitPatchSectionKind::Unstaged => {
git_apply_patch(&worktree.path, &["-R"], &hunk.patch, "reset selected hunk")
}
GitPatchSectionKind::Staged => {
if entry.unstaged {
anyhow::bail!(
"cannot reset a staged hunk while the file also has unstaged changes; unstage it first"
);
}
git_apply_patch(
&worktree.path,
&["-R", "--index"],
&hunk.patch,
"reset selected staged hunk",
)
}
}
}
pub fn commit_staged(worktree: &WorktreeInfo, message: &str) -> Result<String> {
let message = message.trim();
if message.is_empty() {
anyhow::bail!("commit message cannot be empty");
}View on GitHub (pinned to 01e15490f0)
Solutions
- First call unstage_path (or unstage_hunk for the staged hunk) to clear the staged portion, then re-derive the entry and reset.
- Inspect `entry.staged` and `entry.unstaged` before reset_hunk and surface a 'unstage first' prompt instead of calling through.
- After unstaging, refresh `git_status_entries` so the entry's flags reflect the new state.
Example fix
// before
reset_hunk(&worktree, &entry, &hunk)?;
// after
if entry.unstaged && hunk.section == GitPatchSectionKind::Staged {
unstage_path(&worktree, &entry.path)?;
let fresh = git_status_entries(&worktree)?.into_iter()
.find(|e| e.path == entry.path).unwrap();
// re-derive hunks, then reset
} else {
reset_hunk(&worktree, &entry, &hunk)?;
} Defensive patterns
Strategy: validation
Validate before calling
use crate::worktree::{GitPatchSectionKind, GitStatusEntry, unstage_path};
fn can_reset_staged_hunk(entry: &GitStatusEntry) -> bool {
// Safe only when the file has no parallel unstaged edits.
entry.staged && !entry.unstaged
}
if hunk.section == GitPatchSectionKind::Staged && !can_reset_staged_hunk(&entry) {
// must unstage first to avoid index/worktree desync
unstage_path(&worktree, &entry.path)?;
// refresh entry + hunks, then retry reset on the now-unstaged hunk
} else {
reset_hunk(&worktree, &entry, &hunk)?;
} Type guard
fn staged_only(entry: &GitStatusEntry) -> bool {
entry.staged && !entry.unstaged
} Try / catch
match reset_hunk(&worktree, &entry, &hunk) {
Ok(()) => { /* refresh */ }
Err(e) if format!("{e}").contains("unstaged changes") => {
unstage_path(&worktree, &entry.path)?;
// refresh and retry
}
Err(e) => return Err(e),
} Prevention
- Inspect both `entry.staged` and `entry.unstaged` before resetting a staged hunk.
- When a file is in a mixed staged/unstaged state, force the user to unstage first.
- Refresh `git_status_entries` after every mutating op so flags stay accurate.
When it happens
Trigger: Calling reset_hunk on a Staged hunk for a file whose GitStatusEntry reports both `staged == true` and `unstaged == true` (the XY porcelain status has both an index and a worktree change). The file was partially staged and then edited further.
Common situations: User staged a hunk, kept editing the same file, then asked to reset the staged hunk; mixed staged/unstaged state for one path that the UI collapses into a single entry.
Related errors
- selected hunk is already staged
- selected hunk is not staged
- cannot reset hunks for untracked files
- no staged changes to commit
- git add failed for {path}: {stderr}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/85c757bdda8a9ffb.
Report an issue: GitHub.