gitbutlerapp/gitbutler · error

BUG: we have to avoid using these legacy types

Error message

BUG: we have to avoid using these legacy types

What it means

In set_base_branch, the raw HEAD reference name string is parsed into a legacy `Refname` type and the parse result is unwrapped with expect. The developer marked this as a bug-prone legacy dependency: the codebase is migrating away from the legacy Refname parsing, and any HEAD ref name that does not parse as a Refname panics. It signals tech debt rather than a user-facing validation error.

Solutions

  1. Return to the GitButler workspace (checkout the workspace ref) before changing the target branch — the function already bail_precondition! when HEAD is outside the workspace
  2. Refactor to the modern ref name types (gix::refs::FullName / Refname from gitbutler-refname) and return a Result instead of expect
  3. Check `git symbolic-ref HEAD` / status before the operation to ensure HEAD is attached to a valid branch

Example fix

// before
let head_name: Refname = head_ref_name
    .to_string()
    .parse()
    .expect("BUG: we have to avoid using these legacy types");
// after
let head_name: Refname = head_ref_name
    .to_string()
    .parse()
    .context("Failed to parse HEAD reference name")?;
Defensive patterns

Strategy: validation

Validate before calling

// Before calling set_base_branch, check HEAD is on the workspace
let head = repo.head_name()?;
if head.as_bstr() != WORKSPACE_REF_NAME.as_bytes() {
    anyhow::bail!("HEAD is outside the GitButler workspace; return to workspace first");
}

Type guard

fn head_is_valid_refname(head_ref_name: &str) -> bool {
    head_ref_name.parse::<Refname>().is_ok()
}

Try / catch

// Callers should treat precondition failures as recoverable:
match actions.set_base_branch(target) {
    Err(err) if err.to_string().contains("outside the GitButler workspace") => {
        prompt_user_to_return_to_workspace();
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling set_base_branch (changing the GitButler workspace target branch) when HEAD points at a ref whose name cannot be parsed into the legacy Refname type, e.g. HEAD detached or on an unusual ref name.

Common situations: User is in detached-HEAD state or on a ref with an unexpected prefix; HEAD points at something other than the workspace ref or a normal branch while attempting to change the target; legacy/new ref-type migration left inconsistent state.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/036b9d917e5fbefd. Report an issue: GitHub.

Appendix: source

Thrown at crates/gitbutler-branch-actions/src/base.rs:221

        None,
    )?;

    let project_meta = ProjectMeta {
        target_ref: Some(target_branch_ref.to_string().try_into()?),
        target_commit_id: Some(target_commit_oid),
        push_remote: None,
    };
    project_meta.remote_url_with_fallback(&repo)?;

    // TODO: make sure this is a real branch
    let head_ref_name = current_head
        .referent_name()
        .context("Failed to get HEAD reference name")?;
    let head_is_workspace = head_ref_name == WORKSPACE_REF_NAME;
    let head_name: Refname = head_ref_name
        .to_string()
        .parse()
        .expect("BUG: we have to avoid using these legacy types");
    if workspace_ref_exists
        && !head_is_workspace
        && existing_target_ref.is_none_or(|target_ref| target_branch_ref != target_ref.as_ref())
    {
        bail_precondition!(
            "cannot change the target while HEAD is outside the GitButler workspace - return to workspace first"
        );
    }
    ctx.set_project_meta(project_meta)?;

    let mut workspace_to_initialize = None;
    if !head_is_workspace {
        // if there are any commits on the head branch or uncommitted changes in the working directory, we need to
        // put them into a virtual branch

        let changes = but_core::diff::worktree_changes(&*ctx.repo.get()?)?.changes;
        if !changes.is_empty() || current_head_commit != target_commit_oid {
            let branch_matches_target = if let Refname::Local(head_name) = &head_name {

View on GitHub (pinned to 58e5313667)