Engineer's toolkit
Refs, HEAD and the reflog
Branches, tags and HEAD are small files that point at commits, and the reflog records every move, which is why almost any committed work can be recovered.
If objects are the database, refs are the names you use. A ref is a named pointer to a commit. Branches, tags and HEAD are all refs. Once you see that, branching, checking out and resetting stop looking like magic.
Concept
Before you start: a commit hash is the ID of a commit object (see Git's object store). A ref holds that hash, or the name of another ref.
A branch is a file with a hash in it
$ cat .git/refs/heads/main
607d042a5cb0663df9073d24a108e5d3cb5ca3a8
$ cat .git/HEAD
ref: refs/heads/mainThat is the whole implementation of a branch: a file of about 41 bytes. Creating a branch writes one file. That is why branching in Git is instant regardless of repository size. Older systems such as Subversion copied directories.
refs/heads/*are your local branches.refs/remotes/origin/*are remote-tracking branches. They hold your last-known copy of the server's branches.git fetchupdates them and never touches your local branches.refs/tags/*are tags. A lightweight tag is just a ref. An annotated tag is a ref that points at a tag object.
HEAD is where you are
HEAD normally names a branch. This is a symbolic ref. When you commit, Git writes the new commit with the current HEAD commit as its parent, then moves the branch that HEAD names to the new commit.
Detached HEAD means HEAD holds a commit hash directly instead of a branch name. This happens after git checkout <sha>, and during some rebases. Commits made there are not on any branch, which is why Git warns you.
The reflog: your safety net
Every time a ref moves, whether from a commit, reset, rebase, checkout, merge or pull, Git appends a line to a local log called the reflog:
$ git reflog
d2c7d31 HEAD@{0}: rebase (finish): returning to refs/heads/feature
d2c7d31 HEAD@{1}: rebase (pick): Add retries
dfcebc5 HEAD@{2}: rebase (start): checkout main
f8d2642 HEAD@{3}: checkout: moving from main to feature
dfcebc5 HEAD@{4}: commit: Bump timeoutHEAD@{3} means "where HEAD was three moves ago". Here, f8d2642 is the feature branch's tip from before the rebase. The rebase created new commits, but the old ones still exist, just unreferenced. Unreachable objects survive until garbage collection prunes them. By default, reflog entries for unreachable commits are kept for 30 days, and for reachable ones for 90 days.
The reflog is local. It is not pushed, so it cannot save work that only ever existed on someone else's machine.
Common mistakes
- Deleting a branch that has unmerged work. Its tip is in the reflog, so you can get it back with
git branch <name> <sha>. - Assuming the reflog is shared. It lives only on your machine.
- Waiting too long to recover. Unreachable commits are pruned after the retention window.
Check yourself
- You ran
git reset --hard HEAD~3and lost three commits from view. How do you get them back? Rungit reflog, find the entry from before the reset, and point a branch orgit reset --hardat that hash. - Why is creating a branch instant? It writes one small file holding a commit hash. No files are copied.
Key takeaways
- A branch is a file with a commit hash. HEAD names the current branch, or holds a hash when detached.
- Remote-tracking branches are your last-known view of the server, updated by fetch.
- The reflog records every pointer move for 30 to 90 days, so almost any committed work can be recovered locally.
Further learning
Next, see what each way of combining branches does to these pointers, in merge, rebase and squash.
For the tools that work with this model, read Pro Git: Git Internals.
Related
Git's object store
The four object types Git stores, how a hash of the contents becomes the object's ID, and why that gives Git deduplication, integrity and cheap comparisons.
Editing history and recovering work
How interactive rebase, fixup commits and backup branches make your own history clean, and the recovery steps for a botched reset, rebase or deleted branch.
Debugging
A scientific loop for finding bugs fast — reproduce, hypothesize, bisect, instrument — from local failures to production incidents without making things worse.