gitbutlerapp/gitbutler · error
No uncommitted changes found for the selected files
Error message
No uncommitted changes found for the selected files
What it means
Thrown by absorption_plan()/absorption_plan_with_perm() when AbsorptionTarget::TreeChanges is used: the selected files' paths (matched byte-exact via path_bytes) must have at least one worktree change assignment that is either unassigned or assigned to assigned_stack_id. An empty intersection aborts planning before any commit is touched.
Source
Thrown at crates/but-api/src/legacy/absorb.rs:195
true,
perm.read_permission(),
)?;
let all_assignments = worktree_changes.assignments;
let dependencies = worktree_changes.dependencies;
// Include hunks that are unassigned or assigned to the acting stack,
// so that dependency locks can route unassigned hunks correctly.
let candidates: Vec<AbsorbCandidate> = all_assignments
.into_iter()
.filter(|a| {
changes.iter().any(|c| c.path_bytes == a.path_bytes)
&& (a.stack_id.is_none() || a.stack_id == assigned_stack_id)
})
.map(Into::into)
.collect();
if candidates.is_empty() {
anyhow::bail!("No uncommitted changes found for the selected files");
}
(candidates, dependencies)
}
AbsorptionTarget::Hunks { hunks } => {
// Compute hunk dependencies only for this target since changes_in_worktree isn't called
let (repo, ws, _db) = ctx.workspace_and_db_with_perm(perm.read_permission())?;
let dependencies =
hunk_dependencies_for_workspace_changes_by_worktree_dir(&repo, &ws, None).ok();
drop((repo, ws, _db));
(hunks.into_iter().map(Into::into).collect(), dependencies)
}
AbsorptionTarget::All => {
// Get all worktree changes, assignments, and dependencies
// TODO: Ideally, there's a simpler way of getting the worktree changes without passing the context to it.
// At this time, the context is passed pretty deep into the function.
let worktree_changes = crate::diff::changes_in_worktree_with_perm(
ctx,View on GitHub (pinned to 2497b8007a)
Solutions
- Re-run changes_in_worktree and build the changes list from its current output for the same files
- Pass the assigned_stack_id of the stack the hunks are actually assigned to, or unassign the hunks first
- Make sure the paths are repo-relative and byte-identical to the diff output (no absolute prefixes or different separators)
- If the changes were already absorbed or committed, skip the call
Example fix
// before: paths captured from an older diff view
let plan = absorption_plan(ctx, AbsorptionTarget::TreeChanges { changes: stale_changes, assigned_stack_id })?;
// after: rebuild the selection from the live worktree diff
let wt = changes_in_worktree_with_perm(ctx, ChangesSource::Head, true, perm.read_permission())?;
let changes = wt.assignments.iter()
.filter(|a| selected_files.iter().any(|f| f.as_bytes() == &a.path_bytes[..]))
.map(|a| change_for(&a.path_bytes))
.collect::<Vec<_>>();
if changes.is_empty() { return Ok(vec![]); } // nothing to absorb: no-op instead of error
let plan = absorption_plan_with_perm(ctx, AbsorptionTarget::TreeChanges { changes, assigned_stack_id }, perm)?; Defensive patterns
Strategy: validation
Validate before calling
let wt = changes_in_worktree_with_perm(ctx, ChangesSource::Head, true, perm.read_permission())?;
let selectable = wt.assignments.iter().any(|a|
selected.iter().any(|p| p.as_bytes() == &a.path_bytes[..])
&& (a.stack_id.is_none() || a.stack_id == Some(assigned_stack_id)));
if !selectable {
// refresh the selection from wt; do not call absorption_plan yet
} Try / catch
match absorption_plan(ctx, target) {
Ok(plan) => { /* proceed */ }
Err(err) if err.to_string().contains("No uncommitted changes found for the selected files") => {
// stale selection: re-fetch the diff, rebuild the target, or no-op
}
Err(err) => return Err(err),
} Prevention
- Derive the file selection from the same changes_in_worktree output the absorb API sees
- Refresh the diff right before absorb when the user may have assigned or committed hunks in between
- Keep assigned_stack_id equal to the stack currently open in the UI
When it happens
Trigger: Calling absorption_plan with AbsorptionTarget::TreeChanges where every hunk of the selected files is assigned to a stack other than assigned_stack_id; the selected files no longer have uncommitted worktree changes; or the supplied path bytes are not exactly equal to the assignment paths (case, separator, or encoding differences).
Common situations: An 'absorb selected files' action invoked after the user already assigned those hunks to another branch; a stale file list captured before the hunks were committed or absorbed; renamed or moved files; the worktree changed between listing the diff and calling absorb.
Related errors
- Failed to determine target commit for hunk absorption due to
- Stack has no branches
- Failed to create blank commit in stack: {stack_id:?}
- Failed to create blank commit in leftmost stack
- Unable to determine target commit for unassigned change: {}
AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17).
Data as JSON: /api/errors/8beead263627b00b.
Report an issue: GitHub.