GitoxideLabs/gitoxide · error

splitting requires both staged and worktree changes

Error message

splitting requires both staged and worktree changes

What it means

This error means `gix tix edit split` was asked to split a commit, but the working tree does not have changes in both the staged (index) and unstaged (worktree) change groups. The split operation partitions the diff of a commit by interacting with staged and worktree state, so both must be present in the change list. The library checks `changes.paths` for at least one path in `ChangeGroup::Staged` and at least one in `ChangeGroup::Unstaged` and bails if either group is empty.

Solutions

  1. Stage some changes (e.g. `git add -p`) if only worktree changes exist, or make additional worktree edits, so both groups are non-empty
  2. Unstage some changes (`git restore --staged <path>`) if everything is staged
  3. Verify with `git status` / the tix review listing that both staged and unstaged entries exist before invoking split
  4. If split is not the intended operation, use a different edit command that tolerates a single change group

Example fix

// before: invoking split with only staged changes
edit_split(&repo, target)?;
// after: guard before invoking
let changes = collect_changes(&repo, target)?;
let has_staged = changes.paths.iter().any(|c| c.group == ChangeGroup::Staged);
let has_unstaged = changes.paths.iter().any(|c| c.group == ChangeGroup::Unstaged);
if has_staged && has_unstaged {
    edit_split(&repo, target)?;
}
Defensive patterns

Strategy: validation

Validate before calling

let changes = collect_changes(&repo, target)?;
let has_staged = changes.paths.iter().any(|c| c.group == ChangeGroup::Staged);
let has_unstaged = changes.paths.iter().any(|c| c.group == ChangeGroup::Unstaged);
if !has_staged || !has_unstaged {
    anyhow::bail!("split needs both staged and worktree changes; run `git status` first");
}

Prevention

When it happens

Trigger: Calling the split preparation (via the CLI `tix edit split` flow) when the index matches HEAD (nothing staged) or when the worktree is clean relative to the index (nothing unstaged), so `changes.paths` lacks one of the two groups.

Common situations: Running `split` right after committing (staged group empty); running `split` with only `git add`ed changes but no worktree edits; running split twice, the second time after all hunks were consumed into splits.

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/8a350f810c57facf. Report an issue: GitHub.

Appendix: source

Thrown at gix-tix/src/edit/split.rs:29

    target: ObjectId,
    source: gix::objs::Commit,
    tree: ObjectId,
}

#[tracing::instrument(skip_all)]
pub(crate) fn prepare(mut repo: gix::Repository, todo: bool) -> Result<Prepared> {
    let target = repo
        .head_id()
        .context("splitting requires an existing HEAD commit")?
        .detach();
    let changes = load_worktree_changes_without_lines(&repo)?;
    if changes.paths.iter().any(|change| change.kind == ChangeKind::Unmerged) {
        anyhow::bail!("cannot split with unresolved conflicts");
    }
    if !changes.paths.iter().any(|change| change.group == ChangeGroup::Staged)
        || !changes.paths.iter().any(|change| change.group == ChangeGroup::Unstaged)
    {
        anyhow::bail!("splitting requires both staged and worktree changes");
    }

    let mut source = repo
        .find_commit(target)
        .context("could not find HEAD commit")?
        .decode()
        .context("could not decode HEAD commit")?
        .into_owned()
        .context("could not own HEAD commit")?;
    let mut create = create::prepare_from(repo.clone(), Some(target), create::Source::Default, None, todo)?;
    repo.objects.set_object_memory(std::mem::take(&mut create.objects));

    let head_tree = source.tree;
    let index_tree = create.tree;
    let index = repo
        .find_tree(index_tree)
        .context("could not load the prepared index tree")?;
    let worktree_tree = create::worktree_tree_with_changes(&repo, &index, &changes)?;

View on GitHub (pinned to e73179060b)