gitbutlerapp/gitbutler · error

Can only check out local branches under refs/heads, got

Error message

Can only check out local branches under refs/heads, got '{}'

What it means

`Transaction::checkout` records a final branch checkout to materialize at commit time, but only local branches (refs/heads/...) can be checked out. If the FullNameRef passed has any other category (remote-tracking, tags, notes, custom refs), the ensure! fails with this message showing the ref name.

Solutions

  1. Pass a refs/heads/... FullNameRef, e.g. convert refs/remotes/origin/main to its local branch first
  2. Create or verify a local branch exists at the desired commit and check that out instead
  3. If a tag is the target, check out a local branch at the tag's commit rather than the tag ref
  4. Validate branch.category() == Some(Category::LocalBranch) before calling checkout

Example fix

// before
tx.checkout(&"refs/remotes/origin/main".try_into()?)?;
// after
let local: FullName = "refs/heads/main".try_into()?;
tx.checkout(&local)?;
Defensive patterns

Strategy: validation

Validate before calling

use gix::refs::{Category, FullNameRef};
fn can_checkout(branch: &FullNameRef) -> bool {
    branch.category() == Some(Category::LocalBranch)
}
// call: assert!(can_checkout(&name));

Type guard

fn as_local_branch(name: &FullNameRef) -> Option<&FullNameRef> {
    (name.category() == Some(gix::refs::Category::LocalBranch)).then_some(name)
}

Try / catch

match tx.checkout(branch) {
    Err(e) if format!("{e:#}").starts_with("Can only check out local branches") => {
        let local = to_local_branch(branch)?; // e.g. origin/main -> refs/heads/main
        tx.checkout(&local)?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling transaction.checkout() with a fully-qualified ref like refs/remotes/origin/main, refs/tags/v1.0, or refs/notes/commits instead of a name under refs/heads.

Common situations: Resolving a branch via gix from remote-tracking ref names; trying to 'checkout a tag' through the transaction API; passing a user-supplied ref name without normalizing to the local branch form.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at crates/but-transaction/src/lib.rs:377

                editor,
                commit_mappings.map(source),
                changes,
                context_lines,
            )?;

            let new_commit = rebase.lookup_commit(commit_selector)?;
            Ok((new_commit, MaterializeWithoutCheckout::No, rebase))
        })
    }

    /// Check out `branch` when the transaction commits.
    ///
    /// The checkout is deferred until all in-memory commits and reference changes have been
    /// materialized. Consequently, operations after this call still observe the checkout from
    /// before the transaction. Calling this more than once replaces the previously requested final
    /// checkout.
    pub fn checkout(&mut self, branch: &FullNameRef) -> anyhow::Result<()> {
        anyhow::ensure!(
            branch.category() == Some(gix::refs::Category::LocalBranch),
            "Can only check out local branches under refs/heads, got '{}'",
            branch.as_bstr()
        );

        resolve_checkout_target(self.repo(), branch)?;

        self.request_materialization(MaterializeWithoutCheckout::No)?;
        self.inner.pending_checkout = Some(branch.to_owned());
        Ok(())
    }

    pub fn remove_reference(&mut self, ref_name: &FullNameRef) -> anyhow::Result<()> {
        self.rebase(|mut editor, _| {
            let ref_selector = editor.select_reference(ref_name)?;

            let must_disconnect_child = 'must_disconnect: {
                let Some(target_selector) = editor.target_selector() else {

View on GitHub (pinned to 58e5313667)