Validate Git Branch and Tag Names Before You Use Them
You will use git check-ref-format to reject malformed Git reference names before a script creates, fetches or pushes anything. The command validates input and reports success with exit status 0; it does not create a branch, tag or other repository object.
The route
Jump straight to the step you need, or tick off Done means at the end.
Prerequisites: Git 2.43.0 is installed with the git-man package on the machine used for these examples. You need a shell and a Git repository only for the branch-specific example. Allow about 10 minutes. No elevated privileges are needed.
1. Check a complete reference name
A full refname normally includes a namespace such as refs/heads/ for a branch or refs/tags/ for a tag. Pass the name as one quoted argument. Quoting matters when input comes from a variable: it prevents the shell from treating wildcard characters as filename patterns before Git sees them.
git check-ref-format 'refs/heads/release/2026'
There is no output when the name is valid, and the command exits successfully. Check the status directly when writing a script:
if git check-ref-format --no-allow-onelevel "$refname"; then
printf 'valid ref: %s\n' "$refname"
else
printf 'invalid ref: %s\n' "$refname" >&2
exit 1
fi
The default is --no-allow-onelevel. A name such as release is rejected because it has no slash-separated category. Keep that default when accepting complete refs. If you deliberately accept a one-level name, say so explicitly:
git check-ref-format --allow-onelevel 'release'
printf 'status=%s\n' "$?"
Checkpoint
A valid complete ref produces no diagnostic and status 0. A rejected ref produces a non-zero status. Do not interpret silence alone as success in a longer pipeline; test the status.
2. Validate a branch shorthand correctly
Use --branch when the value is a branch name supplied by a user or another command, rather than a complete refs/heads/... name.
branch_name='feature/report-export'
if git check-ref-format --branch "$branch_name"; then
printf 'branch name accepted: %s\n' "$branch_name"
else
printf 'branch name rejected: %s\n' "$branch_name" >&2
exit 1
fi
This check is stricter than simply prefixing the value with refs/heads/. A branch name cannot begin with a dash, even though a dash can occur at the beginning of a ref component in the complete-ref check. Treat the two forms as different interfaces and validate the form your command will actually receive.
Inside a repository, --branch also expands the previous-checkout shorthand @{-n}. For example, @{-1} refers to the last thing checked out by git switch or git checkout. That previous checkout can be a commit rather than a branch when detached HEAD was involved, so do not assume the printed value is always a branch name.
3. Use normalisation only when you want canonical input
--normalize removes leading slashes and collapses repeated slashes between components. It prints the resulting refname if that result is valid. This is useful when a program intentionally accepts a path-like value and wants one canonical spelling.
git check-ref-format --normalize '//refs//heads///feature/report-export'
Expected output is:
refs/heads/feature/report-export
Normalisation is not a general repair tool. It does not make a name containing .., a trailing dot, a component ending in .lock, or forbidden punctuation valid. It also changes the value, so use the printed result rather than the original input if you continue:
normalised=$(git check-ref-format --normalize "$candidate") || {
printf 'invalid ref: %s\n' "$candidate" >&2
exit 1
}
printf 'canonical ref: %s\n' "$normalised"
The older spelling --print is deprecated. Prefer --normalize in new scripts.
4. Recognise the rules behind common failures
Git permits slashes for hierarchy, but rejects a slash-separated component that begins with a dot or ends with .lock. It also rejects consecutive dots, control characters, spaces, ~, ^, :, ?, *, [, backslashes, leading or trailing slashes, repeated slashes, a trailing dot, @{, and the single character @. A full ref must contain at least one slash unless you enable --allow-onelevel.
These restrictions are more than naming style. Git uses double dots for revision ranges, tildes and carets for revision expressions, colons for source and destination refs, and @{ for reflog notation. Rejecting them keeps a value from being mistaken for an expression in a later command.
for candidate in \
'refs/heads/good-name' \
'refs/heads/bad..name' \
'refs/heads/.hidden' \
'refs/heads/temporary.lock'; do
if git check-ref-format "$candidate"; then
printf 'accepted: %s\n' "$candidate"
else
printf 'rejected: %s\n' "$candidate"
fi
done
Expected output includes one accepted line for refs/heads/good-name and rejected lines for the other three. The loop is read-only. It does not need sudo.
5. Allow a wildcard only for a refspec pattern
Ordinary ref validation rejects *. A remote refspec pattern is a different input type, so validate it with --refspec-pattern. Git permits one wildcard, not multiple wildcards.
git check-ref-format --refspec-pattern 'refs/heads/release/*'
Do not add this option merely to make a rejected branch name pass. A wildcard pattern can select multiple remote refs and deserves separate handling. Keep the pattern quoted and validate it before passing it to fetch or push logic.
6. Verify the result in the operation that follows
This command validates syntax, not existence. A successful check does not prove that a branch exists, that a tag points at an object, or that a remote will accept a push. After validation, use the operation-specific Git command and check its status. For example, to test whether a local branch name already exists without changing anything:
branch_name='feature/report-export'
git check-ref-format --branch "$branch_name" >/dev/null || exit 1
if git show-ref --verify --quiet "refs/heads/$branch_name"; then
printf 'branch already exists: %s\n' "$branch_name"
else
printf 'branch name is valid and currently unused: %s\n' "$branch_name"
fi
Safety boundary
Neither example creates or deletes anything. Commands such as git branch -D, git push --delete and ref updates change repository state and need a separate confirmation and recovery plan. Validation is not that confirmation.
Done means
- You use complete refs with the default one-level rejection unless your interface explicitly allows otherwise.
- You use
--branchfor branch shorthand and account for its stricter rules. - You capture the output of
--normalizewhen canonical input is required. - You reserve
--refspec-patternfor genuine refspec patterns. - You check exit status, quote untrusted values, and perform a separate existence or remote-operation check.