Git Refs and Refspecs: Grammar Reference
Git’s ref system is a tiny language for naming objects and mapping them across repositories. This post is a grammar reference first, and examples second.
Grammar reference
Ref and refname
ref := 'refs/' refpath
refpath := name ('/' name)*
name := 1+ valid-char
valid-char := printable ASCII EXCEPT {space, control, '~', '^', ':', '?', '*', '[', '\\'}
constraints := not (leading '/', trailing '/', '//', '..', '.lock', '@{')
Common namespaces (grammar applies equally):
refs/heads/<branch>refs/tags/<tag>refs/remotes/<remote>/<branch>refs/notes/<name>
Symrefs (symbolic refs)
symref := 'ref: ' ref LF
examples := '.git/HEAD', '.git/worktrees/<wt>/HEAD'
Refspec (fetch/push)
refspec := [force] [neg] src [':' dst]
force := '+'
neg := '^' # fetch-only
src := pattern | ref | '' # '' only when ':' present (delete/matching)
dst := ref | '' # '' allowed for delete/matching
pattern := prefix '*' suffix # exactly one '*'
Semantics:
+permits non-fast-forward update.^removes matches from earlier positive fetch globs.patternmaps the*substring intodst.:with emptysrcdeletesdston push.- Bare
:(no src/dst) is deprecated matching.
Revision-ish / <start-point>
start-point := rev # as used by man pages
rev := ref | objid | rev parent | rev '~' [n] | rev '@{' sel '}' | rev peel
parent := '^' [n] # default n=1
peel := '^{}' | '^{' type '}' # type ∈ {commit, tree, blob, tag}
objid := 40-hex | 64-hex # SHA-1 or SHA-256
sel := integer | date | 'upstream' | 'push'
shorthand := '@' == HEAD
Selectors:
rev^/rev^nparent (n-th for merges)rev~nfirst-parent ancestorrev@{n}n-th reflog entry (0 = current)rev@{<date>}reflog at timerev^{}peel tag to referenced objectrev^{tree|commit|blob}peel to type when ambiguous
Storage notes (minimal)
- Loose refs: files under
.git/refs/...(object ID or symref text). - Packed refs:
.git/packed-refswith optional peeled lines (^<object>). - Resolution prefers loose, then packed.
Examples and ready-to-use patterns
- Track all branches:
refs/heads/*:refs/remotes/origin/* - Exclude on fetch:
^refs/heads/wip/* - Force-push one branch:
+refs/heads/topic:refs/heads/topic - Delete remote branch:
:refs/heads/old-feature - Rename tag on push:
refs/tags/v1:refs/tags/release-1 - Parent/ancestor:
feature^,feature^2,feature~3 - Reflog selectors:
main@{2},main@{yesterday} - Peel annotated tag:
v1.2.3^{}orv1.2.3^{commit}
Quick checklist
- Only one
*per refspec glob. - Avoid control chars, space,
~ ^ : ? * [ \\in refname components. - No
//,..,.lock, or@{substrings; no leading/trailing/. - Provide a destination when globbing on push; fetch often supplies one for remote-tracking writes.