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

Worktree {} has uncommitted changes; commit or discard them

Error message

Worktree {} has uncommitted changes; commit or discard them before merging

What it means

merge_into_base's second guard: after passing the merge-readiness check, it calls has_uncommitted_changes (any non-empty `git status --porcelain` output) on the worktree and bails if dirty. Merging a worktree with uncommitted changes risks conflicts git cannot resolve, so the library requires a clean tree first.

Source

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

pub fn has_uncommitted_changes(worktree: &WorktreeInfo) -> Result<bool> {
    Ok(!git_status_short(&worktree.path)?.is_empty())
}

pub fn has_staged_changes(worktree: &WorktreeInfo) -> Result<bool> {
    Ok(git_status_entries(worktree)?
        .iter()
        .any(|entry| entry.staged))
}

pub fn merge_into_base(worktree: &WorktreeInfo) -> Result<MergeOutcome> {
    let readiness = merge_readiness(worktree)?;
    if readiness.status == MergeReadinessStatus::Conflicted {
        anyhow::bail!(readiness.summary);
    }

    if has_uncommitted_changes(worktree)? {
        anyhow::bail!(
            "Worktree {} has uncommitted changes; commit or discard them before merging",
            worktree.branch
        );
    }

    let repo_root = base_checkout_path(worktree)?;
    let current_branch = get_current_branch(&repo_root)?;
    if current_branch != worktree.base_branch {
        anyhow::bail!(
            "Base branch {} is not checked out in repo root (currently {})",
            worktree.base_branch,
            current_branch
        );
    }

    if !git_status_short(&repo_root)?.is_empty() {
        anyhow::bail!(
            "Repository root {} has uncommitted changes; commit or stash them before merging",

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Commit or discard changes first: `commit_staged` plus stage_path for unstaged work, or `reset_path` to discard.
  2. Pre-check with `has_uncommitted_changes(&worktree)?` and gate the merge UI on a clean tree.
  3. If untracked build artifacts are the only noise, gitignore them so porcelain goes quiet.
  4. Refresh status immediately before the merge to avoid stale-clean assumptions.

Example fix

// before
let outcome = merge_into_base(&worktree)?;

// after
if has_uncommitted_changes(&worktree)? {
    return Err(anyhow!("commit or discard changes in {} before merging", worktree.branch));
}
let outcome = merge_into_base(&worktree)?;
Defensive patterns

Strategy: validation

Validate before calling

use crate::worktree::has_uncommitted_changes;

if has_uncommitted_changes(&worktree)? {
    return Err(anyhow!(
        "worktree {} has uncommitted changes; commit or discard before merging",
        worktree.branch
    ));
}
let outcome = merge_into_base(&worktree)?;

Type guard

fn is_clean(worktree: &WorktreeInfo) -> anyhow::Result<bool> {
    Ok(!has_uncommitted_changes(worktree)?)
}

Try / catch

match merge_into_base(&worktree) {
    Ok(outcome) => Ok(outcome),
    Err(e) if format!("{e:#}").contains("uncommitted changes") => {
        // prompt user to commit or discard, then retry
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling merge_into_base on a worktree that has staged or unstaged modifications, untracked files (porcelain reports them with `??`), or leftover conflict markers from a prior operation.

Common situations: Agent edited files but never committed; a build tool wrote artifacts that git now reports as untracked; an earlier commit failed partway and left the index dirty.

Related errors


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