{"record":{"id":"ffd895a617275756","repo":"gitbutlerapp/gitbutler","slug":"snapshot-checkout-ref-is-not-a-local-branch","errorCode":null,"errorMessage":"snapshot checkout ref is not a local branch","messagePattern":"snapshot checkout ref is not a local branch","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-oplog/src/oplog.rs","lineNumber":540,"sourceCode":"}\n\nfn snapshot_checkout(\n    snapshot_tree: &gix::Tree<'_>,\n    repo: &gix::Repository,\n) -> Result<Option<SnapshotCheckout>> {\n    let Some(ref_entry) = snapshot_tree.lookup_entry_by_path(\"checkout/ref\")? else {\n        return Ok(None);\n    };\n    let commit_entry = snapshot_tree\n        .lookup_entry_by_path(\"checkout/commit\")?\n        .context(\"snapshot checkout ref has no commit\")?;\n    let ref_blob = repo\n        .find_blob(ref_entry.id())\n        .context(\"failed to read snapshot checkout ref\")?;\n    let ref_name = gix::refs::FullName::try_from(ref_blob.data.as_bstr())\n        .context(\"snapshot checkout ref is invalid\")?;\n    if ref_name.category() != Some(gix::refs::Category::LocalBranch) {\n        bail!(\"snapshot checkout ref is not a local branch\");\n    }\n    let commit_blob = repo\n        .find_blob(commit_entry.id())\n        .context(\"failed to read snapshot checkout commit\")?;\n    let commit_id = gix::ObjectId::from_hex(&commit_blob.data)\n        .context(\"snapshot checkout commit is invalid\")?;\n    if commit_id.is_null() {\n        bail!(\"snapshot checkout commit is null\");\n    }\n    Ok(Some(SnapshotCheckout {\n        ref_name,\n        commit_id,\n    }))\n}\n\nfn snapshot_metadata(\n    snapshot_tree: &gix::Tree<'_>,\n    repo: &gix::Repository,","sourceCodeStart":522,"sourceCodeEnd":558,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-oplog/src/oplog.rs#L522-L558","documentation":"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).","triggerScenarios":"Restoring a snapshot whose `checkout/ref` blob contains e.g. `refs/remotes/origin/main` or `refs/tags/v1` instead of a `refs/heads/` name.","commonSituations":"Hand-edited or migrated snapshot trees; writers storing the wrong ref kind; snapshots produced by incompatible versions.","solutions":["Fix the snapshot's `checkout/ref` to a local branch name and retry the restore","Restore an earlier snapshot with a valid checkout record","Report as a bug if the snapshot was produced by the same app version"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let ref_name = gix::refs::FullName::try_from(ref_blob.data.as_bstr())?;\nif ref_name.category() != Some(gix::refs::Category::LocalBranch) {\n    return Err(anyhow::anyhow!(\"snapshot checkout ref {ref_name} is not a local branch; refusing\"));\n}","typeGuard":"fn is_local_branch(ref_name: &gix::refs::FullName) -> bool {\n    ref_name.category() == Some(gix::refs::Category::LocalBranch)\n}","tryCatchPattern":"match oplog.restore(snapshot_id) {\n    Err(err) if err.to_string().contains(\"not a local branch\") => { /* pick another snapshot */ }\n    other => other,\n}","preventionTips":["Validate the ref category when writing checkout records","Don't hand-craft snapshot trees"],"tags":["rust","gitbutler","oplog","restore","ref-validation","checkout"],"backgroundTag":"invalid-reference-format","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}