{"record":{"id":"7e7e22788871100c","repo":"gitbutlerapp/gitbutler","slug":"invalid-source-expected-an-uncommitted-file-or-br","errorCode":null,"errorMessage":"Invalid source: expected an uncommitted file or branch","messagePattern":"Invalid source: expected an uncommitted file or branch","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/command/legacy/absorb.rs","lineNumber":89,"sourceCode":"        })\n        .transpose()?;\n\n    let target = if let Some(source) = source {\n        match source {\n            CliId::UncommittedHunkOrFile(UncommittedHunkOrFile { hunks, .. }) => {\n                // Absorb this particular file\n                AbsorptionTarget::Hunks {\n                    hunks: hunks.map(|id_and_hunk| id_and_hunk.hunk).into(),\n                }\n            }\n            CliId::Branch(branch) => {\n                // Absorb everything that is assigned to this lane\n                AbsorptionTarget::Branch {\n                    branch_name: branch.name,\n                }\n            }\n            _ => {\n                anyhow::bail!(\"Invalid source: expected an uncommitted file or branch\");\n            }\n        }\n    } else {\n        // Try to absorb everything uncommitted\n        Default::default()\n    };\n\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 absorption_plan =\n        but_api::legacy::absorb::absorption_plan_with_perm(ctx, target, guard.write_permission())?;\n\n    // Absorbing into a landed commit entangles the new change with an entire\n    // already-merged commit, which conflicts on the next `but pull`. Drop such\n    // plan entries and tell the user, instead of silently amending them.\n    let merged = MergedUpstream::from_ctx(ctx, allow_merged)?;\n    let Some((absorption_plan, skipped_merged)) =\n        drop_landed_absorptions(absorption_plan, &merged, out)?","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but/src/command/legacy/absorb.rs#L71-L107","documentation":"`but absorb` resolves its source argument into an AbsorptionTarget (an uncommitted file assignment or a branch/lane). If the resolved value falls into no known arm of the match — i.e. the argument named something that is neither an uncommitted file nor a branch — the handler bails with this generic invalid-source error.","triggerScenarios":"Passing an argument to `but absorb` that resolves to something other than an uncommitted file or a branch: e.g. a committed-but-clean file path, a ref that is not a local branch, an empty selection enum arm, or a stale name for a file whose changes were already committed/stashed.","commonSituations":"Naming a file whose changes were already committed (no longer uncommitted); typos in branch names; trying to absorb against a remote-only ref; running absorb after the working tree changed so a previously valid name no longer resolves.","solutions":["Check `but status` and pass an uncommitted file path or an existing branch name","Commit or note that the file's changes are already absorbed if it is no longer uncommitted","Spell the branch exactly (local branch names, not remotes/origin refs)"],"exampleFix":"// before\nbut absorb origin/feature-x   # remote ref, not a branch target\n// after\nbut absorb feature-x","handlingStrategy":"validation","validationCode":"function assertAbsorbableSource(name, uncommittedFiles, localBranches) {\n  if (!uncommittedFiles.includes(name) && !localBranches.includes(name)) {\n    throw new Error(`Invalid absorb source: ${name} is not an uncommitted file or branch`);\n  }\n}","typeGuard":"const isValidSource = (name, files, branches) => files.includes(name) || branches.includes(name);","tryCatchPattern":"try {\n  await exec(`but absorb ${src}`);\n} catch (e) {\n  if (e.message.includes('Invalid source')) {\n    console.error('Pick a name from but status (uncommitted file or branch)');\n  } else throw e;\n}","preventionTips":["Source arguments from live `but status` output, not from memory","Re-check state after commits/stashes — previously valid names become invalid","Use local branch names, not remote refs"],"tags":["cli","absorb","git"],"backgroundTag":"invalid-argument-value","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}