gitbutlerapp/gitbutler · error
No uncommitted changes found for the selected files
Error message
No uncommitted changes found for the selected files
What it means
`absorption_plan_with_perm` throws this when the caller selected specific files for absorption but, after filtering the worktree changes to those files (and the relevant stack assignment), no eligible uncommitted changes remain. Nothing matches the file selection, so there is nothing to absorb and the operation bails.
Solutions
- Refresh the worktree status (`but status`) and re-select only files that currently have uncommitted changes.
- Confirm the selected files' changes are assigned to (or unassigned from) the target stack, and reassign if needed.
- If the changes were already committed, use commit-amend/squash operations instead of absorb.
Example fix
// before absorb(target, ["src/clean_file.rs"]) // Err: No uncommitted changes found for the selected files // after let dirty = status().changes.filter(|c| c.uncommitted); absorb(target, dirty.map(|c| c.path))
Defensive patterns
Strategy: validation
Validate before calling
// filter the selection to files that actually have uncommitted changes
const status = await api.workspaceState();
const dirtyPaths = new Set(status.fileChanges.filter(f => f.uncommitted).map(f => f.path));
const eligible = selectedFiles.filter(p => dirtyPaths.has(p));
if (!eligible.length) throw new Error('Selected files have no uncommitted changes; refresh selection'); Type guard
function filesHaveChanges(paths, fileChanges) {
const dirty = new Set(fileChanges.filter(f => f.uncommitted).map(f => f.path));
return paths.some(p => dirty.has(p));
} Try / catch
try {
await api.absorb({ files: selectedFiles });
} catch (e) {
if (String(e).includes('No uncommitted changes found for the selected files')) {
const fresh = await refreshStatusAndReselect(); // re-query worktree state
if (fresh.length) await api.absorb({ files: fresh });
} else throw e;
} Prevention
- Always re-query worktree status immediately before building the absorb selection.
- Drop already-committed or clean files from the selection instead of passing stale lists.
- Verify assignment (or unassigned status) matches the target stack for each selected file.
When it happens
Trigger: Calling `absorption_plan` with an `AbsorptionTarget::TreeChanges { changes, .. }` (or selected-file target) where the filter `a.stack_id.is_none() || a.stack_id == assigned_stack_id` over the requested files yields an empty candidate list — e.g. the files were already committed, are unmodified, or their changes belong to a different stack.
Common situations: Stale UI file list — the user committed the changes before hitting absorb; file paths that are actually clean in the worktree; changes assigned to a different branch than the target stack; path typos in the selection.
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 uncommitted changes assigned to branch
- Branch not found
- Failed to create blank commit in stack
- Failed to determine target commit for hunk absorption due…
- Invalid source: expected an uncommitted file or branch
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/8beead263627b00b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-api/src/legacy/absorb.rs:188
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 58e5313667)