GitoxideLabs/gitoxide · error

review requires a clean index and worktree

Error message

review requires a clean index and worktree

What it means

Starting or finishing a review requires a clean index and worktree, because review setup rewrites HEAD, the index, and possibly the worktree via git plumbing. `ensure_clean` runs `git status --porcelain` and refuses to proceed if any entry is reported.

Solutions

  1. Commit or stash your changes (`git stash -u` to include untracked files), then retry.
  2. Add intended files and commit them (`git add -A && git commit`).
  3. Discard unwanted changes explicitly (`git checkout -- . && git clean -fd`) if they are disposable.

Example fix

// before
review::start(repo, ...)?; // fails on dirty worktree
// after: clean first, then start
// git stash -u
review::start(repo, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

let status = std::process::Command::new("git")
    .args(["status", "--porcelain=v1", "--untracked-files=all"])
    .output()?;
if !status.stdout.is_empty() {
    return Err(anyhow::anyhow!("commit or stash changes before starting a review"));
}

Prevention

When it happens

Trigger: Calling review `start` or `finish_with_progress` while there are unstaged modifications, staged changes, or untracked files in the worktree.

Common situations: Uncommitted edits from ordinary development left in the tree; editor or build artifacts that are untracked; ignored-but-not-ignored generated files; a partially staged change forgotten from an earlier session.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/3aff46f3ee253e9c. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/review.rs:320

        checkout,
        report,
    )? {
        super::rebase::Perform::Complete(outcome) => {
            let finished = outcome
                .map(review)
                .context("finishing review did not produce a commit")?;
            Ok(Finish::Complete(Finished {
                commit: finished,
                outcome,
            }))
        }
        super::rebase::Perform::Conflict(conflict) => Ok(Finish::Conflict(conflict)),
    }
}

pub(super) fn ensure_clean(workdir: &Path) -> Result<()> {
    if is_dirty(workdir)? {
        anyhow::bail!("review requires a clean index and worktree");
    }
    Ok(())
}

pub(super) fn is_dirty(workdir: &Path) -> Result<bool> {
    let output = Command::new("git")
        .arg("-C")
        .arg(workdir)
        .args(["status", "--porcelain=v1", "--untracked-files=all"])
        .output()
        .context("could not inspect worktree status")?;
    if !output.status.success() {
        anyhow::bail!("{}", String::from_utf8_lossy(&output.stderr).trim());
    }
    Ok(!output.stdout.is_empty())
}

fn next_reference(repo: &gix::Repository) -> Result<gix::refs::FullName> {

View on GitHub (pinned to e73179060b)