Git, CI/CD & GitOpsXVII · ReflogMechanics
Reflog locations and scopes — per-ref logs, the .git/logs tree, and enabling logging for tags
What you'll learn
- Map the reflog namespace to the on-disk tree under .git/logs/
- Distinguish HEAD, per-branch, and per-remote reflogs in their storage locations
- Enable reflog recording for lightweight tags via core.logAllRefUpdates
- Recognise which reflogs are created on demand and which exist for the life of the clone
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
Reflog files are plain text under .git/logs/. The directory
layout mirrors the ref namespace: each ref that has a reflog
gets a file whose path matches the ref’s path with .git/logs/
as the prefix. HEAD is special — it has no ref of its own in
the namespace, so it gets the file .git/logs/HEAD directly.
Understanding the layout is the precondition for forensic work:
when a recovery depends on reading a specific ref’s history, the
engineer needs to know which file holds it.
The .git/logs/ tree
ls "$GIT_DIR/logs"
# HEAD
# refs/
ls "$GIT_DIR/logs/refs"
# heads/
# remotes/
# stash (when stashes exist)
ls "$GIT_DIR/logs/refs/heads"
# main
# feature/iam-rotation
ls "$GIT_DIR/logs/refs/remotes"
# origin/
ls "$GIT_DIR/logs/refs/remotes/origin"
# main
# feature/iam-rotation
The pattern is exact: logs/<ref-path> exists for every ref
that has ever been logged. A branch named feature/iam-rotation
gets a file at logs/refs/heads/feature/iam-rotation; a remote
named origin gets a directory logs/refs/remotes/origin/
containing one file per remote-tracking branch.
flowchart LR
H[".git/logs/HEAD"] --> H1["HEAD reflog"]
B[".git/logs/refs/heads/main"] --> B1["main branch reflog"]
F[".git/logs/refs/heads/feature/x"] --> F1["feature/x reflog"]
R[".git/logs/refs/remotes/origin/main"] --> R1["origin/main reflog"]
A directory that does not exist means no reflog has been
written for that scope yet. The logs/refs/tags/ directory, in
particular, is often absent on clones with no tag logging.
The HEAD reflog is special
HEAD is not a ref in the namespace — it is a symbolic ref that
points at a branch (or directly at a commit in detached mode).
The HEAD reflog is therefore stored at .git/logs/HEAD,
parallel to logs/refs/heads/, not inside it. The HEAD file
contains entries for every operation that moved HEAD: checkouts,
resets, commits, merges, rebases.
git reflog
# shows the HEAD reflog by default
git reflog HEAD
# explicit form, equivalent
git reflog show HEAD
# also equivalent (the default subcommand is `show`)
HEAD’s reflog is the densest of the per-ref logs because HEAD
moves on every commit, every checkout, every reset, every
merge. The HEAD reflog is also the most operationally useful:
it is the file that holds the recovery path for a mistaken
--hard reset, a bad rebase, or an accidental branch deletion.
Per-branch reflogs
Each branch ref has its own reflog. The branch reflog records the moments that branch’s tip moved: a new commit on the branch, a reset of the branch, a force-push that rewrote it.
git reflog main
# 9a8b7c6 main@{0}: commit: bump terraform module to v1.4.0
# 4d2c8e0 main@{1}: commit: update iam policy
git reflog feature/iam-rotation
# 8a3f9d2 feature/iam-rotation@{0}: commit: add trust policy arn
# 6f4e5a6 feature/iam-rotation@{1}: branch: Created from main
The second example shows the typical branch-creation entry:
the branch: Created from main reason is written when the
branch is created. The Created from part names the source
branch — useful diagnostic information when an engineer asks
“where did this branch come from?”.
The per-branch reflog survives branch deletion: deleting
feature/iam-rotation with git branch -D feature/iam-rotation
removes the ref but the reflog file remains at
.git/logs/refs/heads/feature/iam-rotation until the retention
window expires. The orphan commits the branch pointed at are
still reachable via the surviving reflog entries.
Per-remote reflogs
Each remote-tracking branch has its own reflog under
logs/refs/remotes/<remote>/<branch>. The remote reflog
records every fetch, pull, or remote-update that changed the
remote-tracking ref.
git reflog origin/main
# 9a8b7c6 origin/main@{0}: fetch origin: fast-forward
# 4d2c8e0 origin/main@{1}: fetch origin: fast-forward
The fetch origin: fast-forward reason is a hint that the
remote reflog is updated by the network layer, not by local
commands. The remote reflog is rarely useful for recovery
(it tracks the upstream, not the engineer’s work) but it is
useful for diagnostics: “what was origin/main pointing at
three weeks ago when the production cluster last synced?” is
a question the remote reflog can answer.
Enabling reflog for lightweight tags and custom refs
By default, lightweight tags are not reflogged. Moving a
lightweight tag with git tag -f v1.0.0 <oid> updates the ref
but writes no reflog entry. The reason is that tags are
intended to be immutable markers; logging their movement
would contradict that intent for the common case.
The opt-in is core.logAllRefUpdates:
git config core.logAllRefUpdates true
# all ref updates are now logged, including tags and custom refs
With this setting, logs/refs/tags/<name> is created on the
first tag move and persists across git gc. Forensic clones
should set this to true to capture tag moves; routine clones
can leave it at the default false to keep the logs tree
small.
The value always is a stronger opt-in than true: it forces
logging even for repositories that were not bare-born or do
not have an explicit logging policy. The value false is the
default and means “do not log tags”.
Production discipline
- Know which reflogs exist on a clone.
ls "$GIT_DIR/logs/refs"enumerates them; the absence of a directory means the scope has not been logged. - Set
core.logAllRefUpdates trueon audit clones. The tag reflog becomes part of the forensic record. - Treat branch deletion as “reflog-retrievable” for the retention window. Deleting a branch does not lose the OIDs the branch pointed at until the branch’s reflog expires.
- Document the per-ref layout in the team runbook. A new
engineer who needs to find a specific ref’s history should
know to look under
logs/refs/<namespace>/before reading the HEAD reflog.
Cross-course references
- Git, CI/CD & GitOps — Part V (Branches, Refs and HEAD) — Part V lesson 6 introduced the on-disk tree at the foundations level. This lesson is the advanced follow-up on which refs are logged by default.
- Git, CI/CD & GitOps — Part XV (Reset) — the
--hardrecovery path runsgit reflog(which reads the HEAD file) to find the pre-reset OID. - GitOps with Argo CD — Part IV (SyncPhases) — the
GitOps controller sets
core.logAllRefUpdates trueso that every tag the controller has synced remains traceable.
Quiz
Knowledge check · 4 questions
Q1. On a clone with a feature branch `feature/x` and a remote `origin`, which reflog files are guaranteed to exist?
Q2. When a branch is deleted with `git branch -D <name>`, its reflog file under `.git/logs/refs/heads/<name>` is also deleted immediately.
Q3. What configuration option enables reflog recording for lightweight tags, and what are its three valid values?
Q4. Walk the .git/logs/ tree to recover from a branch deletion, identifying the files that hold the recoverable history.
An engineer accidentally ran `git branch -D feature/iam-rotation` and asks: 'can I get the OIDs back?'. The branch had been created three days earlier from `main`, with two commits on top. The clone has `core.logAllRefUpdates` at the default value.
Passing score: 75%. Answers are checked in this browser.