gitbutlerapp/gitbutler · error
No changes were selected to move
Error message
No changes were selected to move
What it means
move_uncommitted_changes transfers selected uncommitted changes from a linked worktree into the destination, but an explicitly provided selection (Some) that is empty means nothing was chosen. The library treats an empty selection as a caller mistake rather than a silent no-op.
Solutions
- Pass None instead of an empty selection when nothing should move (or skip the call entirely)
- Validate the selection is non-empty in the caller/UI before invoking
- Rebuild the selection from the current diff if the source list was computed against stale state
Example fix
// before: forwarding an empty selection
move_uncommitted_changes(ctx, &wt, selection /* = [] */, None)?;
// after: skip when empty
if selection.as_ref().is_some_and(|s| s.is_empty()) {
return Ok(()); // or surface "nothing selected" in the UI
}
move_uncommitted_changes(ctx, &wt, selection, None)?; Defensive patterns
Strategy: validation
Validate before calling
if let Some(sel) = &selection {
anyhow::ensure!(!sel.is_empty(), "select at least one change to move, or pass None");
} Try / catch
match move_uncommitted_changes(ctx, &wt, selection, None) {
Err(e) if e.to_string().contains("No changes were selected") => {
// surface 'nothing selected' in UI or skip as no-op
}
r => r,
} Prevention
- Pass None (not Some(vec![])) when nothing should move
- Validate selections in the UI before invoking
- Rebuild selections from the live diff to avoid empty filters
- Treat empty selections as an explicit no-op branch in automation
When it happens
Trigger: Calling move_uncommitted_changes with Some(selection) where selection.is_empty() — e.g. the UI collected zero hunks/files, the user confirmed a move dialog with nothing ticked, or a filter produced an empty hunk list.
Common situations: Agent-driven automation building a selection from a diff that turned out empty; a UI bug sending an empty array instead of None; a user pressing 'move' without selecting any files or hunks.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- No changes to move
- Aborting due to empty branch name
- Branch name ' ' collides with existing branch
- `but setup` cannot run from a linked worktree; run it from…
- Can only check out local branches under refs/heads, got
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/ad3b3a24deb2e739.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-workspace/src/worktrees.rs:189
/// `DiffSpec`s. If `None`, every uncommitted change in the worktree is moved and `context_lines`
/// is unused.
pub fn move_uncommitted_changes(
main_repo: &gix::Repository,
worktree_repo: &gix::Repository,
selection: Option<Vec<DiffSpec>>,
context_lines: u32,
) -> anyhow::Result<MoveUncommittedChangesOutcome> {
let worktree_head_tree = worktree_repo.head_tree_id_or_empty()?.detach();
let moved_tree = match selection {
None =>
{
#[expect(deprecated)]
worktree_repo.create_wd_tree(0)?
}
Some(selection) => {
if selection.is_empty() {
bail!("No changes were selected to move");
}
let outcome = but_core::tree::create_tree(
worktree_repo,
worktree_head_tree,
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(", ")
);
}View on GitHub (pinned to 58e5313667)