{"record":{"id":"e92a5a613d38e26a","repo":"gitbutlerapp/gitbutler","slug":"can-only-check-out-local-branches-under-refs-heads-got","errorCode":null,"errorMessage":"Can only check out local branches under refs/heads, got '{}'","messagePattern":"Can only check out local branches under refs/heads, got '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-transaction/src/lib.rs","lineNumber":377,"sourceCode":"                editor,\n                commit_mappings.map(source),\n                changes,\n                context_lines,\n            )?;\n\n            let new_commit = rebase.lookup_commit(commit_selector)?;\n            Ok((new_commit, MaterializeWithoutCheckout::No, rebase))\n        })\n    }\n\n    /// Check out `branch` when the transaction commits.\n    ///\n    /// The checkout is deferred until all in-memory commits and reference changes have been\n    /// materialized. Consequently, operations after this call still observe the checkout from\n    /// before the transaction. Calling this more than once replaces the previously requested final\n    /// checkout.\n    pub fn checkout(&mut self, branch: &FullNameRef) -> anyhow::Result<()> {\n        anyhow::ensure!(\n            branch.category() == Some(gix::refs::Category::LocalBranch),\n            \"Can only check out local branches under refs/heads, got '{}'\",\n            branch.as_bstr()\n        );\n\n        resolve_checkout_target(self.repo(), branch)?;\n\n        self.request_materialization(MaterializeWithoutCheckout::No)?;\n        self.inner.pending_checkout = Some(branch.to_owned());\n        Ok(())\n    }\n\n    pub fn remove_reference(&mut self, ref_name: &FullNameRef) -> anyhow::Result<()> {\n        self.rebase(|mut editor, _| {\n            let ref_selector = editor.select_reference(ref_name)?;\n\n            let must_disconnect_child = 'must_disconnect: {\n                let Some(target_selector) = editor.target_selector() else {","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-transaction/src/lib.rs#L359-L395","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a refs/heads/... FullNameRef, e.g. convert refs/remotes/origin/main to its local branch first","Create or verify a local branch exists at the desired commit and check that out instead","If a tag is the target, check out a local branch at the tag's commit rather than the tag ref","Validate branch.category() == Some(Category::LocalBranch) before calling checkout"],"exampleFix":"// before\ntx.checkout(&\"refs/remotes/origin/main\".try_into()?)?;\n// after\nlet local: FullName = \"refs/heads/main\".try_into()?;\ntx.checkout(&local)?;","handlingStrategy":"validation","validationCode":"use gix::refs::{Category, FullNameRef};\nfn can_checkout(branch: &FullNameRef) -> bool {\n    branch.category() == Some(Category::LocalBranch)\n}\n// call: assert!(can_checkout(&name));","typeGuard":"fn as_local_branch(name: &FullNameRef) -> Option<&FullNameRef> {\n    (name.category() == Some(gix::refs::Category::LocalBranch)).then_some(name)\n}","tryCatchPattern":"match tx.checkout(branch) {\n    Err(e) if format!(\"{e:#}\").starts_with(\"Can only check out local branches\") => {\n        let local = to_local_branch(branch)?; // e.g. origin/main -> refs/heads/main\n        tx.checkout(&local)?;\n    }\n    r => r?,\n}","preventionTips":["Normalize remote-tracking names to local branches before checkout","Never pass tag or note ref names to checkout APIs","Validate ref category at the boundary where user input is parsed"],"tags":["git","branch","checkout","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}