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

selected hunk is already staged

Error message

selected hunk is already staged

What it means

stage_hunk rejects any hunk whose `section` field is not `GitPatchSectionKind::Unstaged`. This is a precondition guard, not a git failure: the caller handed the function a hunk that lives in the Staged section of the patch view. Staging an already-staged hunk is a no-op the library refuses to perform silently.

Source

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

            &unstaged_patch,
        ));
    }

    if sections.is_empty() {
        Ok(None)
    } else {
        Ok(Some(GitStatusPatchView {
            path: entry.path.clone(),
            display_path: entry.display_path.clone(),
            patch: sections.join("\n\n"),
            hunks,
        }))
    }
}

pub fn stage_hunk(worktree: &WorktreeInfo, hunk: &GitPatchHunk) -> Result<()> {
    if hunk.section != GitPatchSectionKind::Unstaged {
        anyhow::bail!("selected hunk is already staged");
    }
    git_apply_patch(
        &worktree.path,
        &["--cached"],
        &hunk.patch,
        "stage selected hunk",
    )
}

pub fn unstage_hunk(worktree: &WorktreeInfo, hunk: &GitPatchHunk) -> Result<()> {
    if hunk.section != GitPatchSectionKind::Staged {
        anyhow::bail!("selected hunk is not staged");
    }
    git_apply_patch(
        &worktree.path,
        &["-R", "--cached"],
        &hunk.patch,
        "unstage selected hunk",

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Before calling stage_hunk, branch on `hunk.section == GitPatchSectionKind::Unstaged`; for Staged hunks either no-op or route to unstage_hunk.
  2. Refresh `git_status_patch_view` immediately before acting so hunk.section reflects current state.
  3. Disable the 'stage' UI affordance when the selected hunk is in the Staged section.

Example fix

// before
stage_hunk(&worktree, &hunk)?;

// after
match hunk.section {
    GitPatchSectionKind::Unstaged => stage_hunk(&worktree, &hunk)?,
    GitPatchSectionKind::Staged => { /* already staged; no-op */ }
}
Defensive patterns

Strategy: validation

Validate before calling

use crate::worktree::{GitPatchHunk, GitPatchSectionKind, stage_hunk};

fn try_stage_hunk(worktree: &WorktreeInfo, hunk: &GitPatchHunk) -> anyhow::Result<()> {
    if hunk.section != GitPatchSectionKind::Unstaged {
        return Ok(()); // already staged — nothing to do
    }
    stage_hunk(worktree, hunk)
}

Type guard

fn is_unstaged(hunk: &GitPatchHunk) -> bool {
    matches!(hunk.section, GitPatchSectionKind::Unstaged)
}

Try / catch

match stage_hunk(&worktree, &hunk) {
    Ok(()) => { /* refresh */ }
    Err(e) if format!("{e}").contains("already staged") => { /* benign no-op */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing a `GitPatchHunk` obtained from the `--- Staged diff ---` section of `git_status_patch_view` into `stage_hunk`. UI dispatches the stage action on the wrong section because it did not check `hunk.section` before choosing the verb.

Common situations: TUI list selection drift where the cursor is on a staged hunk but the stage action is still enabled; copy-paste of hunk references across sections; stale hunk object retained after a status refresh moved it to Staged.

Related errors


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