gitbutlerapp/gitbutler · error

snapshot checkout ref is not a local branch

Error message

snapshot checkout ref is not a local branch

What it means

A snapshot records which branch and commit was checked out; on restore the checkout ref must be a local branch (`refs/heads/...`) because GitButler only restores checkouts of local branches. This bail fires when the recorded ref parses as a valid ref name but belongs to another category (remote-tracking ref, tag, or notes ref).

Source

Thrown at crates/gitbutler-oplog/src/oplog.rs:540

}

fn snapshot_checkout(
    snapshot_tree: &gix::Tree<'_>,
    repo: &gix::Repository,
) -> Result<Option<SnapshotCheckout>> {
    let Some(ref_entry) = snapshot_tree.lookup_entry_by_path("checkout/ref")? else {
        return Ok(None);
    };
    let commit_entry = snapshot_tree
        .lookup_entry_by_path("checkout/commit")?
        .context("snapshot checkout ref has no commit")?;
    let ref_blob = repo
        .find_blob(ref_entry.id())
        .context("failed to read snapshot checkout ref")?;
    let ref_name = gix::refs::FullName::try_from(ref_blob.data.as_bstr())
        .context("snapshot checkout ref is invalid")?;
    if ref_name.category() != Some(gix::refs::Category::LocalBranch) {
        bail!("snapshot checkout ref is not a local branch");
    }
    let commit_blob = repo
        .find_blob(commit_entry.id())
        .context("failed to read snapshot checkout commit")?;
    let commit_id = gix::ObjectId::from_hex(&commit_blob.data)
        .context("snapshot checkout commit is invalid")?;
    if commit_id.is_null() {
        bail!("snapshot checkout commit is null");
    }
    Ok(Some(SnapshotCheckout {
        ref_name,
        commit_id,
    }))
}

fn snapshot_metadata(
    snapshot_tree: &gix::Tree<'_>,
    repo: &gix::Repository,

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Fix the snapshot's `checkout/ref` to a local branch name and retry the restore
  2. Restore an earlier snapshot with a valid checkout record
  3. Report as a bug if the snapshot was produced by the same app version
Defensive patterns

Strategy: validation

Validate before calling

let ref_name = gix::refs::FullName::try_from(ref_blob.data.as_bstr())?;
if ref_name.category() != Some(gix::refs::Category::LocalBranch) {
    return Err(anyhow::anyhow!("snapshot checkout ref {ref_name} is not a local branch; refusing"));
}

Type guard

fn is_local_branch(ref_name: &gix::refs::FullName) -> bool {
    ref_name.category() == Some(gix::refs::Category::LocalBranch)
}

Try / catch

match oplog.restore(snapshot_id) {
    Err(err) if err.to_string().contains("not a local branch") => { /* pick another snapshot */ }
    other => other,
}

Prevention

When it happens

Trigger: Restoring a snapshot whose `checkout/ref` blob contains e.g. `refs/remotes/origin/main` or `refs/tags/v1` instead of a `refs/heads/` name.

Common situations: Hand-edited or migrated snapshot trees; writers storing the wrong ref kind; snapshots produced by incompatible versions.

Related errors


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