skip to content

In Git, which operations append reflog entries, and which refs keep a reflog at all?

level: middleimportance: should knowfreq 42%

answer

  1. Not a list of commands — one rule about refs
  2. One config key decides which refs qualify
  3. Two namespaces are quietly left out
  4. A bare repository defaults the other way
  5. Deleting a branch deletes its journal too

basics

~10 s

Any update of a journalled ref appends an entry: commit, amend, checkout, switch, reset, merge, rebase, pull, cherry-pick, fetch. By default core.logAllRefUpdates journals HEAD, branches under refs/heads, remote-tracking refs, and notes — not tags.

solid answer

~40 s

A reflog entry is written whenever a **journalled ref changes value**, so the list is broad: `git commit` and `git commit --amend`, `git checkout` and `git switch`, `git reset`, `git merge`, `git rebase` (several entries per run, as it detaches, replays, and finishes), `git cherry-pick`, `git pull`, and `git fetch` for the remote-tracking refs it updates. Which refs are journalled is governed by `core.logAllRefUpdates`: when true — the default in a non-bare repository — Git keeps reflogs for HEAD, `refs/heads/*`, `refs/remotes/*` and `refs/notes/*`. Tags under `refs/tags/*` get none, and in a bare repository the setting defaults to false, so a server-side repo has no reflogs unless someone turns them on. Each entry stores the old and new object ids, the updater's identity and timestamp, and an action message such as `reset: moving to HEAD~2`.

code

console · 7 lines
console
$ git reflog show main
d4e5f6a main@{0}: rebase (finish): refs/heads/main onto 8c7b6a5
8c7b6a5 main@{1}: commit: tighten validation
1a2b3c4 main@{2}: merge feature/api: Fast-forward

$ git reflog show origin/main
9f8e7d6 refs/remotes/origin/main@{0}: fetch: fast-forward

go deeper

for a junior

Know that anything moving HEAD or a branch — commit, checkout, reset, merge, rebase, pull — leaves a reflog entry, and that the entry says which command caused it.

for a middle

State the rule as 'ref update writes an entry', name core.logAllRefUpdates and the namespaces it covers, and note that tags and bare repositories are the exceptions.

for a senior

Use the action messages diagnostically to reconstruct an incident, and know that a deleted branch's journal is gone so HEAD's reflog is the remaining evidence.

for a principal

Own the auditability angle: whether mirrors and bare repositories should enable ref logging deliberately, and what a local, per-clone journal can and cannot prove after an incident.

## The rule There is no list of "commands that write reflogs" in Git's design. The rule is simpler: **updating a ref that has logging enabled appends a line to that ref's journal.** Everything else follows from which refs are journalled and which commands move them. ## Commands you will see in the output Because almost every porcelain command ends by moving HEAD or a branch, almost every command shows up: - `git commit` — `commit: <subject>`; an amend appears as `commit (amend):`, an initial commit as `commit (initial):`. - `git checkout` / `git switch` — `checkout: moving from <old> to <new>`, even when no commit is created. - `git reset` — `reset: moving to <target>`. - `git merge` — `merge <branch>: Fast-forward` or `Merge made by the 'ort' strategy`. - `git rebase` — several entries for one run: a start, one per replayed commit, and `rebase (finish): returning to refs/heads/<branch>`. - `git cherry-pick` — `cherry-pick: <subject>`. - `git pull` — the fetch's tracking-ref updates plus the merge or rebase it performs. - `git fetch` — updates `refs/remotes/<remote>/*`, and those refs have journals of their own, which is how you can see what a remote branch looked like before the last fetch. - `git clone` — a single `clone: from <url>` entry, the first line of a new repository's HEAD journal. ## Which refs are journalled The switch is `core.logAllRefUpdates`. When it is true, Git enables reflogs for: - **HEAD** - **branch heads** — `refs/heads/*` - **remote-tracking refs** — `refs/remotes/*` - **note refs** — `refs/notes/*` The default is **true in a repository with a working tree** and **false in a bare repository**. Two consequences follow that interviewers like: 1. **Tags have no reflog by default.** `refs/tags/*` is outside the journalled set, so moving a tag leaves no local trace of its previous target. 2. **Server-side repositories usually have no reflogs.** A bare repo does not journal unless someone sets `core.logAllRefUpdates=true` there, which is worth doing deliberately if you want a record of ref updates on a mirror. Setting the value to `always` journals updates to *all* refs, including tags and other namespaces. ## Where the journals live Each journalled ref has a file under the repository's `logs` directory: HEAD's journal in `.git/logs/HEAD`, a branch's in `.git/logs/refs/heads/<name>`, a tracking ref's in `.git/logs/refs/remotes/<remote>/<name>`. The line format records the old id, the new id, the committer identity, the timestamp, and the action message after a tab. Two facts follow directly: **deleting a branch deletes its journal**, so after `git branch -D feature` you must fall back to HEAD's reflog to find that branch's old tip; and the action messages are the audit trail that lets you reconstruct what a command sequence did. ## What is not covered - **Content that was never committed.** Working-tree edits destroyed by `git reset --hard` and untracked files removed by `git clean` were never Git objects; no ref moved, so no entry exists and no recovery is possible. - **Other repositories.** Journals are per-repository and are never pushed, fetched, or cloned. - **`ORIG_HEAD`.** It sits outside the journalled namespaces, so it has no indexed history of its own. One related mechanism worth recognising: Git's stash is implemented as a ref with a journal, which is why stash entries are addressed with the same brace notation. ## Interview framing The strong answer states the rule (ref update ⇒ entry), names `core.logAllRefUpdates` and its default set, calls out the two exceptions (tags, bare repositories), and closes with the boundary: the reflog records *ref movements*, so anything that never became a commit is outside it.

  • Why does a bare repository on a server usually have no reflogs?
    Because `core.logAllRefUpdates` defaults to false when the repository has no working tree. Bare repos are treated as publishing points rather than places where someone recovers local mistakes. You can set it to true explicitly on a mirror if you want a record of how refs moved there — a deliberate choice, not the default.
  • Does git fetch leave any reflog trace?
    Yes. Fetch updates remote-tracking refs under `refs/remotes/`, and those are journalled by default, so `git reflog show origin/main` shows what that tracking ref pointed at before each fetch. That is how you can tell whether a remote branch was force-updated between two of your fetches, using only local data.

saying these in an interview costs you the question

  • Believing only commits produce reflog entries
  • Assuming tags are journalled like branches
  • Expecting a bare server repository to keep reflogs by default
  • Thinking a deleted branch's own reflog survives the deletion
  • Claiming fetch leaves no reflog trace at all

context