{"record":{"id":"3dabc66aea132c72","repo":"gitbutlerapp/gitbutler","slug":"refusing-to-operation-symbolic-ref-due-to-p","errorCode":null,"errorMessage":"Refusing to {operation} symbolic ref '{}' due to potential ambiguity","messagePattern":"Refusing to (.+?) symbolic ref '(.+?)' due to potential ambiguity","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/branch/mod.rs","lineNumber":540,"sourceCode":"}\n\n/// Find `branch` in `repo` and reject it if it resolves to a symbolic reference.\n///\n/// `operation` is used only for the error message so callers such as apply and unapply can share\n/// validation while still reporting the action they refused to perform.\n///\n/// Missing references are returned as `Ok(None)` so each caller can decide whether absence is an error or a no-op.\npub(crate) fn try_find_validated_ref<'repo>(\n    repo: &'repo gix::Repository,\n    branch: &gix::refs::FullNameRef,\n    operation: &str,\n) -> anyhow::Result<Option<gix::Reference<'repo>>> {\n    let branch_ref = repo.try_find_reference(branch)?;\n    if branch_ref\n        .as_ref()\n        .is_some_and(|r| matches!(r.target(), gix::refs::TargetRef::Symbolic(_)))\n    {\n        anyhow::bail!(\n            \"Refusing to {operation} symbolic ref '{}' due to potential ambiguity\",\n            branch.shorten()\n        );\n    }\n    Ok(branch_ref)\n}\n\n/// Functions and types related to adding a branch to the workspace.\npub mod apply;\npub use apply::apply;\n\n/// Functions and types related to removing a branch from the workspace.\npub mod unapply;\npub use unapply::function::unapply;\n\n/// related types for removing a workspace reference.\npub mod remove_reference;\npub use remove_reference::remove_reference;","sourceCodeStart":522,"sourceCodeEnd":558,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/branch/mod.rs#L522-L558","documentation":"`try_find_validated_ref` (branch/mod.rs) refuses to continue an operation when the located reference's target is symbolic (`gix::refs::TargetRef::Symbolic`), i.e. the ref points at another ref rather than directly at an object id. Because dereferencing semantics would be ambiguous (act on the symref itself or its target?), the operation names the branch and bails. Missing refs are fine (`Ok(None)`); only symbolic ones are fatal.","triggerScenarios":"Calling an operation that funnels through `try_find_validated_ref(repo, branch, operation)` (branch removal/move paths in but-workspace) where `branch` resolves to a symbolic ref — e.g. a hand-crafted `refs/heads/foo -> refs/heads/bar`, or certain `refs/remotes/origin/HEAD`-style aliases if passed as the branch.","commonSituations":"Repos with manually created symrefs (`git symbolic-ref`), mirrors where branches alias each other, or tooling that passes a HEAD-style alias where a concrete branch ref is required. The ref exists, so callers expecting 'not found' handling are surprised by the refusal.","solutions":["Resolve the symref to its target ref first (`gix::Repository::find_reference(...).follow()` / peel to the direct ref) and pass the concrete branch name.","Or delete/replace the symbolic ref if it was created by accident (`git update-ref --no-deref` tooling or `git symbolic-ref -d`).","Check `repo.try_find_reference(branch)` target kind before invoking the operation and surface a targeted message."],"exampleFix":"// before\nlet branch_ref = try_find_validated_ref(repo, &branch_name, \"delete\"); // bails on symref\n\n// after\nlet target = repo.find_reference(&branch_name)?.follow()?.target;\nlet concrete: gix::refs::FullName = /* peel symbolic target to the real branch */;\nlet branch_ref = try_find_validated_ref(repo, &concrete.as_ref(), \"delete\");","handlingStrategy":"validation","validationCode":"// Resolve symrefs before invoking operations that use try_find_validated_ref:\nif let Ok(Some(r)) = repo.try_find_reference(branch) {\n    if matches!(r.target(), gix::refs::TargetRef::Symbolic(_)) {\n        anyhow::bail!(\"branch {} is a symbolic ref; resolve it to its target first\", branch.shorten());\n    }\n}","typeGuard":"fn is_symbolic_ref(repo: &gix::Repository, branch: &gix::refs::FullNameRef) -> bool {\n    repo.try_find_reference(branch).ok().flatten().is_some_and(|r| matches!(r.target(), gix::refs::TargetRef::Symbolic(_)))\n}","tryCatchPattern":"match try_find_validated_ref(repo, branch, \"delete\") {\n    Err(err) if err.to_string().contains(\"symbolic ref\") => { /* follow the symref and retry with the concrete ref */ }\n    other => other,\n}","preventionTips":["Pass concrete branch refs, never aliases or HEAD-style symrefs.","Follow symbolic targets with gix's Reference::follow() before calling ref-mutating APIs."],"tags":["rust","git","gix","but-workspace","symbolic-ref","ref-validation"],"backgroundTag":"symbolic-ref-rejected","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}