{"record":{"id":"69b8a7be52ee9158","repo":"gitbutlerapp/gitbutler","slug":"invalid-branch-name-err","errorCode":null,"errorMessage":"Invalid branch name: {err}","messagePattern":"Invalid branch name: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but/src/command/branch/update.rs","lineNumber":164,"sourceCode":"                reference.name.shorten()\n            )\n        }\n        _ => bail!(\n            \"Expected a local branch, but '{}' is not under refs/heads/\",\n            reference.name.shorten()\n        ),\n    }\n}\n\nfn normalize_local_branch_ref(branch: &str) -> anyhow::Result<FullName> {\n    let full = if branch.starts_with(\"refs/heads/\") {\n        branch.to_owned()\n    } else if branch.starts_with(\"refs/\") {\n        bail!(\"Only local branches under refs/heads/ are supported\");\n    } else {\n        format!(\"refs/heads/{branch}\")\n    };\n    FullName::try_from(full).map_err(|err| anyhow::anyhow!(\"Invalid branch name: {err}\"))\n}\n\nfn output_apply_result(\n    branch_ref: &FullNameRef,\n    divergence: &but_workspace::branch::IntegrationDivergenceDisplay,\n    dry_run: bool,\n    verbose: bool,\n    result: IntegrateBranchResult,\n    out: &mut OutputChannel,\n) -> anyhow::Result<()> {\n    if let Some(out) = out.for_human() {\n        if dry_run {\n            write!(\n                out,\n                \"{}\",\n                format_dry_run(divergence, branch_ref, &result, verbose)\n            )?;\n        } else {","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/command/branch/update.rs#L146-L182","documentation":"normalize_local_branch_ref turns a user-supplied branch string into a gix::refs::FullName: it rejects anything under refs/ but outside refs/heads/, prefixes bare names with refs/heads/, and then FullName::try_from validates the result against Git's reference-name rules. The error wraps gix's validation failure, meaning the constructed full name contains characters or patterns Git forbids (space, ~, ^, :, ?, *, [, \\, control chars, '..' sequences, leading/trailing issues, '.lock' suffix, etc.).","triggerScenarios":"Calling `but branch update <name>` with a branch containing forbidden characters, e.g. 'feature x' (space), 'foo..bar', 'release*', 'branch.lock', a name ending in '/' or '.'; or a copy-pasted name carrying a trailing newline/whitespace; or a ref like refs/tags/v1 passed where only local branches are valid (that hits the separate 'Only local branches under refs/heads/' bail first only for refs/tags — non-refs names go straight to FullName validation).","commonSituations":"Names pasted from issue trackers or Slack that include spaces or unicode punctuation; shell glob characters unquoted; Windows copy-paste with trailing carriage returns; scripts interpolating empty variables producing 'refs/heads/'.","solutions":["Inspect the exact name in the error's '{err}' from gix; it names the first invalid character or pattern — remove it.","Quote the argument and strip whitespace: `but branch update \"$(printf '%s' \"$name\" | tr -d '[:space:]')\"`.","Replace forbidden characters with '-' or '_' (spaces, ~ ^ : ? * [ \\ .., leading '-', trailing '.lock', trailing '/').","If you meant a non-branch ref (tag, remote ref), note only refs/heads/* local branches are supported here — retarget the command."],"exampleFix":"# before\n$ but branch update \"feature x\"\nError: Invalid branch name: ...\n\n# after\n$ but branch update \"feature-x\"","handlingStrategy":"validation","validationCode":"// Validate a branch name before calling update\nfn is_valid_branch_name(name: &str) -> bool {\n    if name.starts_with(\"refs/\") && !name.starts_with(\"refs/heads/\") { return false; }\n    let full = if name.starts_with(\"refs/heads/\") { name.to_owned() } else { format!(\"refs/heads/{name}\") };\n    gix::refs::FullName::try_from(full).is_ok()\n}","typeGuard":"fn valid_full_name(full: &str) -> Option<gix::refs::FullName> {\n    gix::refs::FullName::try_from(full).ok()\n}","tryCatchPattern":null,"preventionTips":["Sanitize user input: reject ' ', '~', '^', ':', '?', '*', '[', '\\\\', '..', trailing '/'/'.', '.lock'.","Trim whitespace/newlines from pasted names before use.","Quote branch arguments in shell scripts."],"tags":["git","ref-name","validation","branch-update"],"backgroundTag":"invalid-git-ref-name","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}