gitbutlerapp/gitbutler · warning

target OID must exist when ahead calculation is enabled

Error message

target OID must exist when ahead calculation is enabled

What it means

Defensive expect in 'but branch list': when commits-ahead counting is enabled (the default; disabled with --no-ahead), the workspace target's commit id must be present. Upstream, target_oid is resolved exactly when '!empty || ahead || check_merge' (list.rs:47-55), and ResolvedTarget::oid() returns a plain ObjectId - resolution either succeeds or propagates its error via '?'. With the current wiring the expect cannot fire; it guards against future flag-wiring desync. When the target genuinely cannot be resolved (workspace without a base branch), users see the propagated resolution error instead of this panic.

Source

Thrown at crates/but/src/command/legacy/branch/list.rs:194

                // in the target and has no commits ahead to show.
                repo.merge_base_with_graph(branch.head, target_oid, &mut graph)
                    .map(|merge_base| merge_base.detach() != branch.head)
                    .unwrap_or(true)
            })
            .take(num_branches_to_take)
            .collect()
    } else {
        branches.into_iter().take(num_branches_to_take).collect()
    };

    let has_more_branches = branches_to_show.len() > max_branches;
    let branches_to_show: Vec<_> = branches_to_show.into_iter().take(max_branches).collect();

    // Calculate commits ahead if requested
    let commits_ahead_map: Option<HashMap<String, usize>> = if ahead {
        Some(calculate_commits_ahead(
            ctx,
            target_oid.expect("target OID must exist when ahead calculation is enabled"),
            &branches_to_show,
        )?)
    } else {
        None
    };

    // Check merge status if requested
    let merge_status_map: Option<HashMap<String, bool>> = if check_merge {
        Some(check_branches_merge_cleanly(
            ctx,
            target_oid.expect("target OID must exist when merge check is enabled"),
            &applied_stacks,
            &branches_to_show,
        )?)
    } else {
        None
    };

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Keep the resolution condition at list.rs:47 in sync with every flag that consumes target_oid
  2. Maintainer: replace the expects with ok_or_else errors naming the flag that required the target
  3. Add a CLI test matrix over --no-ahead, --no-check and --empty combinations

Example fix

// before
target_oid.expect("target OID must exist when ahead calculation is enabled")

// after
let target_oid = target_oid.ok_or_else(|| {
    anyhow::anyhow!("--ahead needs a resolvable workspace target branch; run fetch/sync first")
})?;
Defensive patterns

Strategy: validation

Validate before calling

// Lock the wiring: target resolution must cover every flag that needs the target
#[test]
fn branch_list_flags_never_panic_on_target() {
    // exercises default (ahead+check on), --no-ahead, --no-check, --empty combos
    for args in [["branch", "list"], ["branch", "list", "--no-ahead"], ["branch", "list", "--no-check", "--empty"]] {
        env.but(&args).assert().success(); // or expected failure - but never a panic
    }
}

Prevention

When it happens

Trigger: Only a refactor that resolves target_oid under a condition no longer implied by 'ahead' - for example adding a new mode that turns on ahead counting without updating the resolution condition at list.rs:47.

Common situations: Contributors adding flags that need (or deliberately skip) target resolution; not reachable by end users through valid CLI input.

Related errors


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