Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXVII · ReflogMechanics

Reflog locations and scopes — per-ref logs, the .git/logs tree, and enabling logging for tags

Advanced⏱ ~18 mingit

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

Not yet marked complete on this device.

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

  1. 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.
  2. Set core.logAllRefUpdates true on audit clones. The tag reflog becomes part of the forensic record.
  3. 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.
  4. 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 --hard recovery path runs git 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 true so that every tag the controller has synced remains traceable.

Quiz

Knowledge check · 4 questions

  1. Q1. On a clone with a feature branch `feature/x` and a remote `origin`, which reflog files are guaranteed to exist?

  2. Q2. When a branch is deleted with `git branch -D <name>`, its reflog file under `.git/logs/refs/heads/<name>` is also deleted immediately.

  3. Q3. What configuration option enables reflog recording for lightweight tags, and what are its three valid values?

  4. 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.