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

cannot reset hunks for untracked files

Error message

cannot reset hunks for untracked files

What it means

reset_hunk refuses to operate when `entry.untracked == true`. Untracked files have no tracked baseline in HEAD, so the reverse-patch logic reset_hunk uses (`git apply -R` / `-R --index`) is meaningless for them. reset_path already handles untracked entries by deleting them from the filesystem; reset_hunk is the wrong entry point.

Source

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

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",
    )
}

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",
            )

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Branch on `entry.untracked`: route untracked entries to reset_path (which deletes them) instead of reset_hunk.
  2. For untracked entries, `git_status_patch_view` already returns None — derive hunks only from that view so untracked hunks never reach reset_hunk.
  3. Hide the per-hunk reset control on untracked rows.

Example fix

// before
reset_hunk(&worktree, &entry, &hunk)?;

// after
if entry.untracked {
    reset_path(&worktree, &entry)?; // deletes the untracked file
} else {
    reset_hunk(&worktree, &entry, &hunk)?;
}
Defensive patterns

Strategy: type-guard

Validate before calling

use crate::worktree::{GitStatusEntry, reset_hunk, reset_path};

fn reset_entry_or_hunk(
    worktree: &WorktreeInfo,
    entry: &GitStatusEntry,
    hunk: Option<&GitPatchHunk>,
) -> anyhow::Result<()> {
    if entry.untracked {
        return reset_path(worktree, entry); // deletes the file
    }
    match hunk {
        Some(h) => reset_hunk(worktree, entry, h),
        None => reset_path(worktree, entry),
    }
}

Type guard

fn is_tracked(entry: &GitStatusEntry) -> bool {
    !entry.untracked
}

Try / catch

match reset_hunk(&worktree, &entry, &hunk) {
    Ok(()) => { /* refresh */ }
    Err(e) if format!("{e}").contains("untracked files") => {
        reset_path(&worktree, &entry)?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Caller selected a hunk belonging to an untracked file (where `git_status_patch_view` returns `None`, but a stale hunk was constructed elsewhere) and passed it with the entry to reset_hunk. UI offered a per-hunk reset on an untracked row.

Common situations: Stale GitStatusEntry retained after the file transitioned to untracked; UI dispatch table does not distinguish untracked rows from tracked ones for the reset verb.

Related errors


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