gitbutlerapp/gitbutler · error · anyhow::Error

Invalid branch name: {err}

Error message

Invalid branch name: {err}

What it means

normalize_local_branch_ref turns a user-supplied branch string into a gix::refs::FullName: it rejects anything under refs/ but outside refs/heads/, prefixes bare names with refs/heads/, and then FullName::try_from validates the result against Git's reference-name rules. The error wraps gix's validation failure, meaning the constructed full name contains characters or patterns Git forbids (space, ~, ^, :, ?, *, [, \, control chars, '..' sequences, leading/trailing issues, '.lock' suffix, etc.).

Source

Thrown at crates/but/src/command/branch/update.rs:164

                reference.name.shorten()
            )
        }
        _ => bail!(
            "Expected a local branch, but '{}' is not under refs/heads/",
            reference.name.shorten()
        ),
    }
}

fn normalize_local_branch_ref(branch: &str) -> anyhow::Result<FullName> {
    let full = if branch.starts_with("refs/heads/") {
        branch.to_owned()
    } else if branch.starts_with("refs/") {
        bail!("Only local branches under refs/heads/ are supported");
    } else {
        format!("refs/heads/{branch}")
    };
    FullName::try_from(full).map_err(|err| anyhow::anyhow!("Invalid branch name: {err}"))
}

fn output_apply_result(
    branch_ref: &FullNameRef,
    divergence: &but_workspace::branch::IntegrationDivergenceDisplay,
    dry_run: bool,
    verbose: bool,
    result: IntegrateBranchResult,
    out: &mut OutputChannel,
) -> anyhow::Result<()> {
    if let Some(out) = out.for_human() {
        if dry_run {
            write!(
                out,
                "{}",
                format_dry_run(divergence, branch_ref, &result, verbose)
            )?;
        } else {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Inspect the exact name in the error's '{err}' from gix; it names the first invalid character or pattern — remove it.
  2. Quote the argument and strip whitespace: `but branch update "$(printf '%s' "$name" | tr -d '[:space:]')"`.
  3. Replace forbidden characters with '-' or '_' (spaces, ~ ^ : ? * [ \ .., leading '-', trailing '.lock', trailing '/').
  4. If you meant a non-branch ref (tag, remote ref), note only refs/heads/* local branches are supported here — retarget the command.

Example fix

# before
$ but branch update "feature x"
Error: Invalid branch name: ...

# after
$ but branch update "feature-x"
Defensive patterns

Strategy: validation

Validate before calling

// Validate a branch name before calling update
fn is_valid_branch_name(name: &str) -> bool {
    if name.starts_with("refs/") && !name.starts_with("refs/heads/") { return false; }
    let full = if name.starts_with("refs/heads/") { name.to_owned() } else { format!("refs/heads/{name}") };
    gix::refs::FullName::try_from(full).is_ok()
}

Type guard

fn valid_full_name(full: &str) -> Option<gix::refs::FullName> {
    gix::refs::FullName::try_from(full).ok()
}

Prevention

When it happens

Trigger: Calling `but branch update <name>` with a branch containing forbidden characters, e.g. 'feature x' (space), 'foo..bar', 'release*', 'branch.lock', a name ending in '/' or '.'; or a copy-pasted name carrying a trailing newline/whitespace; or a ref like refs/tags/v1 passed where only local branches are valid (that hits the separate 'Only local branches under refs/heads/' bail first only for refs/tags — non-refs names go straight to FullName validation).

Common situations: Names pasted from issue trackers or Slack that include spaces or unicode punctuation; shell glob characters unquoted; Windows copy-paste with trailing carriage returns; scripts interpolating empty variables producing 'refs/heads/'.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/69b8a7be52ee9158. Report an issue: GitHub.