{"record":{"id":"8b634d687408d920","repo":"gitbutlerapp/gitbutler","slug":"failed-to-determine-target-commit-for-hunk-absorpt","errorCode":null,"errorMessage":"Failed to determine target commit for hunk absorption due to ambiguous dependencies in path: {}","messagePattern":"Failed to determine target commit for hunk absorption due to ambiguous dependencies in path: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-api/src/legacy/absorb.rs","lineNumber":422,"sourceCode":"fn ensure_target_commit(\n    ctx: &mut Context,\n    candidate: &AbsorbCandidate,\n    locks: Option<&[HunkLock]>,\n    stack_details_cache: &mut HashMap<StackId, StackDetails>,\n    perm: &mut RepoExclusive,\n) -> anyhow::Result<(\n    but_core::ref_metadata::StackId,\n    gix::ObjectId,\n    AbsorptionReason,\n)> {\n    // Priority 1: Check if there's a dependency lock for this hunk\n    if let Some(locks) = locks {\n        if let Some(lock) = find_top_most_lock(locks, ctx, stack_details_cache) {\n            if let HunkLockTarget::Stack(stack_id) = lock.target {\n                return Ok((stack_id, lock.commit_id, AbsorptionReason::HunkDependency));\n            }\n        } else {\n            anyhow::bail!(\n                \"Failed to determine target commit for hunk absorption due to ambiguous dependencies in path: {}\",\n                candidate.hunk.path\n            );\n        }\n    }\n\n    // Priority 2: Use the candidate's stack ID if available\n    if let Some(stack_id) = candidate.stack_id {\n        let branch_ref = candidate.branch_ref.as_ref();\n\n        let stack_details = crate::legacy::workspace::stack_details(ctx, Some(stack_id))?;\n        if let Some(branch) = find_target_branch(&stack_details, branch_ref)\n            && let Some(commit) = branch.commits.first()\n        {\n            return Ok((stack_id, commit.id, AbsorptionReason::StackAssignment));\n        }\n\n        // If there are no commits in the target branch, create a blank commit first","sourceCodeStart":404,"sourceCodeEnd":440,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but-api/src/legacy/absorb.rs#L404-L440","documentation":"During absorption planning, ensure_target_commit() first tries to route a hunk through its dependency locks: find_top_most_lock() walks the stacks referenced by the locks and returns the first lock whose commit_id still exists in that stack's commits. If locks exist for the path but none point to a commit still present in current stack details (or a locked stack cannot be loaded), the dependency graph is unresolvable and absorption of that path aborts.","triggerScenarios":"Hunk dependency locks reference commit ids that disappeared because the target stack was rebased, squashed, or amended after the lock was written; locks point at commits that were absorbed away; locks target stacks that were deleted; stack_details() fails for a locked stack id.","commonSituations":"Absorbing a hunk after editing or reordering the very commits it depended on; a graph rebase rewrote the workspace leaving stale HunkLock commit ids; locks persisted across a project import or restore.","solutions":["Delete or clear the stale hunk dependency locks for that path, then re-plan the absorption","Re-establish the dependency by assigning the hunks to the intended stack/commit again after the rebase","Absorb via AbsorptionTarget::Hunks with an explicit target, or assign the file to a stack so stack-id routing (priority 2) applies","If the locked stack was deleted, remove its leftover locks too"],"exampleFix":"// before: plan while stale locks exist\nlet plan = absorption_plan(ctx, target)?; // fails: ambiguous dependencies\n\n// after: prune locks whose commit no longer exists, then plan\nfor lock in hunk_locks_for_path(&path) {\n    if let HunkLockTarget::Stack(sid) = lock.target {\n        let details = workspace_stack_details(sid);\n        let alive = details.branch_details.iter()\n            .any(|b| b.commits.iter().any(|c| c.id == lock.commit_id));\n        if !alive { remove_hunk_lock(lock.id); }\n    }\n}\nlet plan = absorption_plan(ctx, target)?;","handlingStrategy":"try-catch","validationCode":"for lock in locks_for_candidate_path {\n    if let HunkLockTarget::Stack(stack_id) = lock.target {\n        let details = stack_details(ctx, Some(stack_id))?;\n        let still_present = details.branch_details.iter()\n            .any(|b| b.commits.iter().any(|c| c.id == lock.commit_id));\n        if !still_present {\n            // clear or rewrite the stale lock before calling absorption_plan\n        }\n    }\n}","typeGuard":"fn locks_are_resolvable(\n    locks: &[HunkLock],\n    details_of: impl Fn(StackId) -> Option<&StackDetails>,\n) -> bool {\n    locks.iter().any(|l| match l.target {\n        HunkLockTarget::Stack(sid) => details_of(sid)\n            .map(|d| {\n                d.branch_details.iter().any(|b| b.commits.iter().any(|c| c.id == l.commit_id))\n            })\n            .unwrap_or(false),\n        _ => true,\n    })\n}","tryCatchPattern":"match absorption_plan(ctx, target) {\n    Err(err) if err.to_string().contains(\"ambiguous dependencies\") => {\n        // surface the conflicting locks for the path and offer to clear them\n    }\n    other => other,\n}","preventionTips":["After rebasing or squashing a stack, refresh or clear hunk locks created against the old commits","Do not hold lock commit ids across workspace rewrites","Re-plan absorption immediately after editing dependencies rather than later"],"tags":["git","absorb","hunk-locks","dependencies","rebase"],"backgroundTag":"stale-dependency-lock","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}