gitbutlerapp/gitbutler · error

Failed to determine target commit for hunk absorption due to

Error message

Failed to determine target commit for hunk absorption due to ambiguous dependencies in path: {}

What it means

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.

Source

Thrown at crates/but-api/src/legacy/absorb.rs:422

fn ensure_target_commit(
    ctx: &mut Context,
    candidate: &AbsorbCandidate,
    locks: Option<&[HunkLock]>,
    stack_details_cache: &mut HashMap<StackId, StackDetails>,
    perm: &mut RepoExclusive,
) -> anyhow::Result<(
    but_core::ref_metadata::StackId,
    gix::ObjectId,
    AbsorptionReason,
)> {
    // Priority 1: Check if there's a dependency lock for this hunk
    if let Some(locks) = locks {
        if let Some(lock) = find_top_most_lock(locks, ctx, stack_details_cache) {
            if let HunkLockTarget::Stack(stack_id) = lock.target {
                return Ok((stack_id, lock.commit_id, AbsorptionReason::HunkDependency));
            }
        } else {
            anyhow::bail!(
                "Failed to determine target commit for hunk absorption due to ambiguous dependencies in path: {}",
                candidate.hunk.path
            );
        }
    }

    // Priority 2: Use the candidate's stack ID if available
    if let Some(stack_id) = candidate.stack_id {
        let branch_ref = candidate.branch_ref.as_ref();

        let stack_details = crate::legacy::workspace::stack_details(ctx, Some(stack_id))?;
        if let Some(branch) = find_target_branch(&stack_details, branch_ref)
            && let Some(commit) = branch.commits.first()
        {
            return Ok((stack_id, commit.id, AbsorptionReason::StackAssignment));
        }

        // If there are no commits in the target branch, create a blank commit first

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Delete or clear the stale hunk dependency locks for that path, then re-plan the absorption
  2. Re-establish the dependency by assigning the hunks to the intended stack/commit again after the rebase
  3. Absorb via AbsorptionTarget::Hunks with an explicit target, or assign the file to a stack so stack-id routing (priority 2) applies
  4. If the locked stack was deleted, remove its leftover locks too

Example fix

// before: plan while stale locks exist
let plan = absorption_plan(ctx, target)?; // fails: ambiguous dependencies

// after: prune locks whose commit no longer exists, then plan
for lock in hunk_locks_for_path(&path) {
    if let HunkLockTarget::Stack(sid) = lock.target {
        let details = workspace_stack_details(sid);
        let alive = details.branch_details.iter()
            .any(|b| b.commits.iter().any(|c| c.id == lock.commit_id));
        if !alive { remove_hunk_lock(lock.id); }
    }
}
let plan = absorption_plan(ctx, target)?;
Defensive patterns

Strategy: try-catch

Validate before calling

for lock in locks_for_candidate_path {
    if let HunkLockTarget::Stack(stack_id) = lock.target {
        let details = stack_details(ctx, Some(stack_id))?;
        let still_present = details.branch_details.iter()
            .any(|b| b.commits.iter().any(|c| c.id == lock.commit_id));
        if !still_present {
            // clear or rewrite the stale lock before calling absorption_plan
        }
    }
}

Type guard

fn locks_are_resolvable(
    locks: &[HunkLock],
    details_of: impl Fn(StackId) -> Option<&StackDetails>,
) -> bool {
    locks.iter().any(|l| match l.target {
        HunkLockTarget::Stack(sid) => details_of(sid)
            .map(|d| {
                d.branch_details.iter().any(|b| b.commits.iter().any(|c| c.id == l.commit_id))
            })
            .unwrap_or(false),
        _ => true,
    })
}

Try / catch

match absorption_plan(ctx, target) {
    Err(err) if err.to_string().contains("ambiguous dependencies") => {
        // surface the conflicting locks for the path and offer to clear them
    }
    other => other,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/8b634d687408d920. Report an issue: GitHub.