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

  1. Refresh the worktree status (`but status`) and re-select only files that currently have uncommitted changes.
  2. Confirm the selected files' changes are assigned to (or unassigned from) the target stack, and reassign if needed.
  3. 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

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


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)