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
- Stage some changes (e.g. `git add -p`) if only worktree changes exist, or make additional worktree edits, so both groups are non-empty
- Unstage some changes (`git restore --staged <path>`) if everything is staged
- Verify with `git status` / the tix review listing that both staged and unstaged entries exist before invoking split
- 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
- Run `git status` (or the tix review listing) before split to confirm both staged and unstaged entries exist
- Check for unresolved conflicts first — split aborts on unmerged paths too
- Avoid scripted splits immediately after commit or full staging operations
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
- cannot split with unresolved conflicts
- changes can only be stashed at the current HEAD
- changes can only be unstashed at the current HEAD
- the HEAD pin must point to a local branch
- attaching requires detached HEAD
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)