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 feature

Git 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/main

Git 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, while refs/changes/* stores the actual patch set refs.