{"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":"exception","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/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/command/legacy/absorb.rs#L71-L107","documentation":"`but absorb` folds uncommitted changes into the commits they belong to. Its optional source must be an uncommitted file/hunk ID (absorb just that file) or a branch/lane ID (absorb everything assigned to that lane); no source absorbs everything uncommitted. This bail is the `_` catch-all of the AbsorptionTarget conversion: the resolved CliId was neither `UncommittedHunkOrFile` nor `Branch` (e.g. a commit or another entity kind reached the match).","triggerScenarios":"A resolved CLI ID of an entity kind other than uncommitted file/hunks or branch (e.g. `CliId::Commit`) reaching the AbsorptionTarget match in crates/but/src/command/legacy/absorb.rs:89. Note the handler pre-filters sources at absorb.rs:56-59 and rejects bad selectors with a different message, so this arm fires when an unexpected CliId kind slips through (other callers, future variants).","commonSituations":"Passing a commit ID copied from `but status` to `but absorb`; scripts invoking the absorb handler with an unvalidated IdMap result; CLI versions drifting so new entity kinds are not covered by the match arms.","solutions":["Run `but absorb` with no source to absorb all uncommitted changes.","Pass an uncommitted-file ID or a branch/lane ID from `but status` (`but absorb 2a` for a file, `but absorb b1` for a lane).","If calling absorb programmatically, filter IdMap results to `CliId::UncommittedHunkOrFile` or `CliId::Branch` before converting to AbsorptionTarget."],"exampleFix":"# before\nbut absorb 4z   # 4z is a commit ID -> Invalid source: expected an uncommitted file or branch\n\n# after\nbut absorb       # absorb all uncommitted changes\nbut absorb 2a    # 2a = uncommitted file ID\nbut absorb b1    # b1 = branch/lane ID","handlingStrategy":"validation","validationCode":"# Verify the ID names an uncommitted file or a lane before absorbing\nbut status | grep -F '<id>' || { echo 'not an absorbable source'; exit 2; }\nbut absorb '<id>'","typeGuard":"// For callers of the IdMap / but API: only these variants are absorbable sources\nfn is_absorbable_source(clid: &CliId) -> bool {\n    matches!(clid, CliId::UncommittedHunkOrFile { .. } | CliId::Branch(_))\n}","tryCatchPattern":null,"preventionTips":["Only pass IDs that `but status` lists under uncommitted files or lanes.","Omit the source argument to absorb everything uncommitted.","When scripting against the API, filter IdMap results to UncommittedHunkOrFile/Branch before converting to AbsorptionTarget."],"tags":["cli","argument-validation","absorb","gitbutler","rust"],"backgroundTag":"invalid-cli-argument","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}