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
- Commit or discard changes first: `commit_staged` plus stage_path for unstaged work, or `reset_path` to discard.
- Pre-check with `has_uncommitted_changes(&worktree)?` and gate the merge UI on a clean tree.
- If untracked build artifacts are the only noise, gitignore them so porcelain goes quiet.
- 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
- Gate the merge button on `has_uncommitted_changes == false`, checked from a fresh status.
- Commit or discard before any merge attempt; never merge a dirty tree.
- Gitignore deterministic build artifacts so they do not keep the tree perpetually dirty.
- Refresh status immediately before the merge to avoid stale-clean assumptions.
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
- Repository root {} has uncommitted changes; commit or stash
- selected hunk is already staged
- selected hunk is not staged
- cannot reset hunks for untracked files
- cannot reset a staged hunk while the file also has unstaged
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/cbe750a9421ab6a3.
Report an issue: GitHub.