{"record":{"id":"8beead263627b00b","repo":"gitbutlerapp/gitbutler","slug":"no-uncommitted-changes-found-for-the-selected-file","errorCode":null,"errorMessage":"No uncommitted changes found for the selected files","messagePattern":"No uncommitted changes found for the selected files","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-api/src/legacy/absorb.rs","lineNumber":195,"sourceCode":"                true,\n                perm.read_permission(),\n            )?;\n            let all_assignments = worktree_changes.assignments;\n            let dependencies = worktree_changes.dependencies;\n\n            // Include hunks that are unassigned or assigned to the acting stack,\n            // so that dependency locks can route unassigned hunks correctly.\n            let candidates: Vec<AbsorbCandidate> = all_assignments\n                .into_iter()\n                .filter(|a| {\n                    changes.iter().any(|c| c.path_bytes == a.path_bytes)\n                        && (a.stack_id.is_none() || a.stack_id == assigned_stack_id)\n                })\n                .map(Into::into)\n                .collect();\n\n            if candidates.is_empty() {\n                anyhow::bail!(\"No uncommitted changes found for the selected files\");\n            }\n\n            (candidates, dependencies)\n        }\n        AbsorptionTarget::Hunks { hunks } => {\n            // Compute hunk dependencies only for this target since changes_in_worktree isn't called\n            let (repo, ws, _db) = ctx.workspace_and_db_with_perm(perm.read_permission())?;\n            let dependencies =\n                hunk_dependencies_for_workspace_changes_by_worktree_dir(&repo, &ws, None).ok();\n            drop((repo, ws, _db));\n            (hunks.into_iter().map(Into::into).collect(), dependencies)\n        }\n        AbsorptionTarget::All => {\n            // Get all worktree changes, assignments, and dependencies\n            // TODO: Ideally, there's a simpler way of getting the worktree changes without passing the context to it.\n            // At this time, the context is passed pretty deep into the function.\n            let worktree_changes = crate::diff::changes_in_worktree_with_perm(\n                ctx,","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but-api/src/legacy/absorb.rs#L177-L213","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before: paths captured from an older diff view\nlet plan = absorption_plan(ctx, AbsorptionTarget::TreeChanges { changes: stale_changes, assigned_stack_id })?;\n\n// after: rebuild the selection from the live worktree diff\nlet wt = changes_in_worktree_with_perm(ctx, ChangesSource::Head, true, perm.read_permission())?;\nlet changes = wt.assignments.iter()\n    .filter(|a| selected_files.iter().any(|f| f.as_bytes() == &a.path_bytes[..]))\n    .map(|a| change_for(&a.path_bytes))\n    .collect::<Vec<_>>();\nif changes.is_empty() { return Ok(vec![]); } // nothing to absorb: no-op instead of error\nlet plan = absorption_plan_with_perm(ctx, AbsorptionTarget::TreeChanges { changes, assigned_stack_id }, perm)?;","handlingStrategy":"validation","validationCode":"let wt = changes_in_worktree_with_perm(ctx, ChangesSource::Head, true, perm.read_permission())?;\nlet selectable = wt.assignments.iter().any(|a|\n    selected.iter().any(|p| p.as_bytes() == &a.path_bytes[..])\n        && (a.stack_id.is_none() || a.stack_id == Some(assigned_stack_id)));\nif !selectable {\n    // refresh the selection from wt; do not call absorption_plan yet\n}","typeGuard":null,"tryCatchPattern":"match absorption_plan(ctx, target) {\n    Ok(plan) => { /* proceed */ }\n    Err(err) if err.to_string().contains(\"No uncommitted changes found for the selected files\") => {\n        // stale selection: re-fetch the diff, rebuild the target, or no-op\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["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"],"tags":["git","absorb","worktree","hunks","selection"],"backgroundTag":"no-uncommitted-changes","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}