Core Idea
A ref (reference) is simply a named pointer to a Git object (usually a commit).
name
│
▼
commit
Examples:
main
feature/login
v1.0
HEAD
refs/changes/45/12345/2
They are all refs.
A Branch is Just a Ref
When creating a branch:
git branch featureGit creates:
refs/heads/feature
pointing to the current commit.
So:
Branch ⊂ Ref
Every branch is a ref, but not every ref is a branch.
Common Ref Namespaces
refs/
├── heads/ Local branches
├── remotes/ Remote-tracking branches
├── tags/ Tags
├── notes/ Git Notes
└── ... Free for tools to use
Git reserves several namespaces, but tools are free to create their own.
HEAD
HEAD is also a ref.
Normally:
HEAD
│
▼
refs/heads/main
│
▼
commit
Detached HEAD:
HEAD
│
▼
commit
What Does Rebase Actually Do?
Rebase does not modify commits.
Instead it creates new commits.
Before:
A -- B -- C (feature)
Rebase onto main:
A -- D -- E (feature)
where:
-
D is a rewritten version of B
-
E is a rewritten version of C
Finally, Git moves:
refs/heads/feature
from C to E.
Therefore:
Rebase rewrites commits, then updates a ref.
A Ref Can Point Anywhere
A ref is only:
name → commit
Git does not care about the name.
For example:
refs/cats/fluffy
refs/banana/test
refs/my-company/build
are all valid refs.
The meaning comes from the tool using them.
Gerrit Refs
Gerrit introduces its own namespace:
refs/changes/
Example:
refs/changes/45/12345/2
Meaning:
45 last two digits of change number
12345 change number
2 patch set
Git does not understand “review” or “patch set”.
To Git, this is just another ref.
Gerrit gives it meaning.
Why Gerrit Uses refs/changes
Every patch set is immutable.
Patch set 1
│
▼
refs/changes/.../1
Patch set 2
│
▼
refs/changes/.../2
Patch set 3
│
▼
refs/changes/.../3
Instead of moving one ref, Gerrit creates a new one for each patch set.
This preserves review history.
refs/for Is Different
When pushing:
git push origin HEAD:refs/for/mainGit thinks:
push HEAD
to
refs/for/main
But Gerrit intercepts this push.
It does not create:
refs/for/main
Instead Gerrit:
-
creates a new change, or
-
creates a new patch set (if the Change-Id already exists),
and stores it under:
refs/changes/...
Think of refs/for/... as a command understood by Gerrit, not a persistent ref.
Push Syntax
General form:
git push <remote> <source>:<destination>Examples:
HEAD:refs/for/main
Current checked-out commit
↓
Submit for review on main
feature:main
Local branch feature
↓
Remote branch main
main:backup
Local main
↓
Remote backup
Mental Model
Objects
=======
blob
tree
commit
tag
▲
│
Refs
====
main
feature
HEAD
v1.0
refs/changes/...
Objects are immutable.
Refs are movable names pointing to objects.
Key Takeaways
-
A branch is just one kind of ref.
-
HEAD is also a ref.
-
Tags are refs.
-
Remote branches are refs.
-
Gerrit patch sets are refs.
-
Git only understands refs as names pointing to objects.
-
Gerrit builds its review system by defining new ref namespaces.
-
Rebase creates new commits and then moves a ref to point at them.
-
refs/for/*is a Gerrit command endpoint, whilerefs/changes/*stores the actual patch set refs.