Refs, Reflogs, Packed Refs, Symbolic References, and Reference Transactions: Concepts, Architecture, and Mental Model
Build the mental model for Git reference namespaces, symbolic HEAD, reflogs, loose versus packed ref storage, guarded compare-and-swap-like updates, and multi-ref transactions.
Learning objectives
- Distinguish immutable objects from movable reference names.
- Explain local branches, tags, remote-tracking refs, custom namespaces, and symbolic refs.
- Treat reflogs as finite local histories of ref movement rather than shared project history.
- Understand loose/packed/reftable storage as implementation beneath logical ref APIs.
- Explain expected-old guarded updates and multi-ref transaction safety.
1. Objects are immutable; automation still needs movable names
Chapter 18 ended with a manually created commit and then used
git update-ref to give that commit a branch name. That
final step is the starting point of this chapter. Git objects are
immutable snapshots and history records. Day-to-day workflows need
references, or refs: names that
can move from one object ID to another as work advances.
This naming layer looks simple until two processes try to move the
same name, a ref is packed into a different storage representation,
a symbolic ref such as HEAD is detached, or recovery
depends on a reflog that may not exist. Production automation
therefore needs a stronger mental model than “a branch is a file
containing a hash.”
2. Inspect logical refs before thinking about storage files
git status --short --branch
git show-ref --head
git for-each-ref --format='%(refname) %(objectname) %(objecttype)'
git symbolic-ref -q HEAD
git reflog list
git rev-parse --show-ref-format
Expected observations:
show-ref reports full ref names and object IDs;
for-each-ref lets you select stable fields;
symbolic-ref reports the branch target of
HEAD when attached; reflog list reports
only refs that actually have reflogs; and
--show-ref-format identifies the repository's
ref-storage backend where supported.
3. Ref namespaces separate different kinds of names
A ref is a Git name stored in a namespace. Common namespaces include:
| Namespace | Meaning | Typical target |
|---|---|---|
refs/heads/* |
Local branch tips | Commit |
refs/tags/* |
Tags | Any Git object; often a tag object or commit |
refs/remotes/* |
Local remote-tracking refs | Commit copied from a remote namespace by fetch |
refs/notes/* |
Git notes namespaces | Notes history |
refs/replace/* |
Replacement-object mapping | Replacement object |
refs/automation/* |
Example custom namespace for team tooling | Policy-defined object, usually commit |
The custom namespace is a convention, not a special built-in branch type. Naming it explicitly helps tooling avoid pretending that every operational pointer is a developer branch.
4. A remote-tracking ref is local state, not the live server ref
refs/remotes/origin/trunk is a ref in your local
repository that records what a fetch or related operation last
learned about the remote. It is not a network link and does not
automatically change when the server changes. This distinction
matters during incident or release automation: inspect the remote
directly or fetch deliberately before treating a remote-tracking ref
as current.
5. A symbolic ref points to another ref rather than directly to an object
The most familiar symbolic ref is HEAD. While attached
to a branch, HEAD names a branch ref such as
refs/heads/trunk; that branch ref then names a commit.
flowchart LR HA[HEAD symbolic ref] -->|ref target| B[refs/heads/trunk] B -->|object ID| C[Commit C] HD[Detached HEAD] -->|object ID directly| D[Commit D]
In the top path, a new commit can advance the branch ref while
HEAD continues to point symbolically at that branch. In
the bottom path, detached HEAD contains an object ID
directly; git symbolic-ref -q HEAD therefore exits
non-zero.
6. Use git symbolic-ref instead of treating HEAD as a
filesystem trick
git symbolic-ref HEAD
git symbolic-ref --short HEAD
Git's symbolic-ref API exists because the implementation needs to be
portable and because ref storage can evolve. Directly opening or
replacing .git/HEAD is unnecessary for normal tooling.
7. Loose refs and packed refs are two storage representations of the same logical names
In the traditional files ref backend, a recently
created or updated ref may be stored as a loose file under the ref
hierarchy. git pack-refs can move eligible logical refs
into a packed representation for efficiency. A later update can make
a branch loose again.
This means “scan .git/refs” is not a complete ref query
even in the traditional backend. Current Git also has a reftable
ref-storage format. Therefore scripts should ask Git for refs rather
than parsing storage files.
8. show-ref is the simple logical-ref inventory
git show-ref
git show-ref --branches
git show-ref --tags
git show-ref --verify refs/heads/trunk
Use an exact full ref with --verify when automation
needs to prove a specific name exists. The command is designed to
hide whether the ref is loose, packed, or stored by another
supported backend.
9. for-each-ref is the structured ref query engine
git for-each-ref \
--sort=refname \
--format='%(refname) %(objectname) %(objecttype)' \
refs/heads refs/tags
Unlike a hand-written directory walk, this interface understands Git's logical ref database and exposes selectable fields. It can filter by reachability, points-at relationships, namespace, and more.
10. A reflog is local history of ref movement, not project history
A reflog records recent updates to a ref in one
local repository. For example, a branch reflog can show old
branch-tip values; the HEAD reflog additionally records
branch switches. Reflogs support selectors such as
HEAD@{2}.
git reflog show HEAD
git reflog show refs/heads/trunk
git reflog exists refs/heads/trunk
A reflog is not guaranteed to exist for every ref, every repository, or every server. Its retention is finite. It is recovery evidence, not a permanent audit database.
11. Reflogs do not automatically synchronize between clones
Fetch and push exchange objects and selected refs according to refspecs. They do not merge your local reflog history into another clone. If an incident report relies on a reflog entry, record which repository produced that evidence.
12. One ref update should be atomic; a guarded update should also verify what you observed
Suppose automation reads refs/heads/release at object A
and decides to move it to B. Another process may move the ref to C
before the first process writes. A blind write would overwrite C. A
guarded update supplies the expected old object ID:
git update-ref refs/heads/release "$B" "$A"
Git performs the update only if the current ref value still matches A. This is analogous to compare-and-swap: “move it only if nobody changed it since I observed it.”
13. A reference transaction groups several guarded ref changes
flowchart TD Q[Queue updates with expected old values] --> P[Prepare] P -->|all refs lock and match| K[Commit ref updates] P -->|one lock/value fails| A[Abort all queued updates] K --> V[Verify through ref APIs]
git update-ref --stdin supports transactional commands
such as start, prepare, and
commit. If the required refs cannot all be locked with
matching expected values, the transaction is not committed.
Individual ref updates are atomic, although current documentation
warns that a concurrent reader may still observe only a subset while
a multi-ref transaction becomes visible.
14. Current Git exposes a reference-transaction hook, but it is executable policy
The current hook contract can notify executable local policy when reference transactions are preparing, prepared, committed, or aborted. It can receive each old value, new value, and full ref name on standard input, and current documentation also includes symbolic-ref transaction support.
15. DevOps connection — named source is a concurrency-sensitive deployment input
Release tools, CI coordinators, deployment systems, and maintenance automation often move refs to publish source state. The safe design is to read logical refs through Git, retain the observed old value, perform a guarded or transactional update, add meaningful reflog messages where appropriate, and verify the final ref. Filesystem overwrites of ref storage bypass those protections.
16. Knowledge check
Question 1. Is origin/trunk the live branch on the
remote server?
Question 2. What does an attached HEAD usually point to?
refs/heads/trunk, which
then points to a commit.
Question 3. Why can scanning only .git/refs miss
valid refs?
Question 4. What extra safety does the old-object argument to
git update-ref provide?
Question 5. Why should reflog evidence identify its repository?
17. Summary
Refs are the movable naming layer over immutable objects. Symbolic refs add one level of naming, reflogs record local ref movement, packed refs are a storage optimization rather than a second logical namespace, and guarded transactions prevent stale automation from silently overwriting concurrent changes.
Authoritative references
git-show-ref
git-for-each-ref
git-symbolic-ref
git-reflog
git-update-ref
git-pack-refs
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.