gitbutlerapp/gitbutler · error
No changes to move
Error message
No changes to move
What it means
After constructing the tree of changes to move, if that tree equals the worktree's head tree, the move would relocate nothing (the selection covers everything but resolves to the same content, or the constructed destination tree has no delta). The library bails instead of performing a meaningless checkout.
Solutions
- Verify the selection actually captures a non-empty delta against the worktree head before moving
- Recompute the diff of the worktree; if it is empty, treat 'nothing to move' as a success no-op in the caller
- Adjust the selection so it represents real changes to relocate
Example fix
// before: assuming a move always changes something
move_uncommitted_changes(ctx, &wt, Some(selection), None)?;
// after: short-circuit no-op moves
let diff = worktree_repo.diff_tree_to_workdir_with_index(...)?;
if diff.is_empty() { return Ok(MoveOutcome::Noop); }
move_uncommitted_changes(ctx, &wt, Some(selection), None)?; Defensive patterns
Strategy: validation
Validate before calling
let workdir_diff = worktree_repo.diff_tree_to_workdir_with_index(Some(&worktree_head_tree), None)?; anyhow::ensure!(!workdir_diff.is_empty(), "worktree has no delta against head; nothing to move");
Try / catch
match move_uncommitted_changes(ctx, &wt, Some(selection), None) {
Err(e) if e.to_string().contains("No changes to move") => Ok(MoveOutcome::Noop),
r => r,
} Prevention
- Verify the selection yields a real content delta before moving
- Handle edit/re-edit netting to zero as an explicit no-op
- Check the worktree diff emptiness early and skip the call
- Don't select 'everything' when the destination already matches
When it happens
Trigger: move_uncommitted_changes where moved_tree == worktree_head_tree — the moved content is identical to the current worktree head, e.g. the selection math produced a tree with no actual differences, or a preceding context("No changes to move") on the destination tree was bypassed with identical trees.
Common situations: Selecting changes that only revert each other (edit + re-edit netting zero); moving with a selection whose resulting tree matches head because the destination already contains that content; automation passing a full-copy selection with no delta.
Understand the failure class
Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.
Related errors
- No changes were selected to move
- `but setup` cannot run from a linked worktree; run it from…
- Cannot currently work in repositories without a worktree
- Cannot currently work in repositories without a worktree
- Cannot hash directory entries that aren't files or symlinks
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/66cddb83ceb5cbdf.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-workspace/src/worktrees.rs:212
selection,
context_lines,
)?;
let rejected: Vec<_> = outcome
.rejected_specs
.iter()
.map(|(reason, spec)| format!("{reason:?}: {}", spec.path))
.collect();
if !rejected.is_empty() {
bail!(
"Some selected changes no longer match the worktree's uncommitted changes: {}",
rejected.join(", ")
);
}
outcome.destination_tree.context("No changes to move")?
}
};
if moved_tree == worktree_head_tree {
bail!("No changes to move");
}
let destination_outcome = but_core::worktree::safe_checkout_from_head(
moved_tree,
main_repo,
but_core::worktree::checkout::Options {
merge_base_override: Some(worktree_head_tree),
allow_uncommitted_changes_to_conflict_with_new_head: true,
..Default::default()
},
)?;
but_core::worktree::safe_checkout_from_head(
worktree_head_tree,
worktree_repo,
but_core::worktree::checkout::Options {
merge_base_override: Some(moved_tree),
allow_uncommitted_changes_to_conflict_with_new_head: true,View on GitHub (pinned to 58e5313667)