Thuta Learning
AdvancedDevOps & Toolsintermediate

Reflog and Git Hooks

What you'll walk away with

  • Explain the core ideas behind Reflog and Git Hooks
  • Read the diagram and trace how state or data flows through the Git/GitHub workflow
  • Decide how this applies to your own project or team

Build the mental model

Reflog and hooks are both quietly powerful because they operate below the level of commits and branches: reflog tracks where HEAD has actually been, and hooks let scripts react automatically to what you are about to do.

The reflog is a local log of every place your HEAD and branch tips have pointed, including commits that no longer belong to any branch, like ones erased by a hard reset. Git keeps the commit object around, unreachable but intact, until garbage collection eventually cleans it up, usually after 30 to 90 days. Run git reflog, find the hash from before the mistake, and git reset --hard back to it.

Reflog is not a backup

Reflog is purely local, tied to one machine's .git directory. It is not synced when you clone or fetch, and it eventually expires. It is a recovery tool for recent local mistakes, never a substitute for real backups.

Hooks are scripts Git runs automatically at defined points in its workflow, stored as executable files in .git/hooks, one file per trigger point. A pre-commit hook runs right before a commit is finalized, commonly used to lint or format changed files and reject the commit if checks fail. A commit-msg hook can enforce message conventions, and a pre-push hook can run a test suite before code ever leaves your machine.

Reflog
A local, chronological log of every position HEAD and branch tips have held, including commits no longer reachable from any branch.
Git Hook
An executable script Git runs automatically at a defined point in its workflow, such as pre-commit or pre-push.
text
REFLOG TIMELINE AND HOOK TRIGGER POINTS
---------------------------------------
REFLOG (local history of where HEAD has pointed)

HEAD@{2}   HEAD@{1}              HEAD@{0}
  A ------> B ------> (hard reset back to A)
            ^
            still exists, just orphaned from any
            branch -- recoverable via its hash

HOOKS (scripts triggered at points in the workflow)

edit files -> pre-commit -> commit -> pre-push -> push
              (lint/test)            (run tests)

Connect it to a real scenario

The reflog example commits a notes file twice, Add project notes then Add important analysis, then deliberately loses the second commit with git reset --hard HEAD~1. git log --oneline afterward shows only the first commit, as if the second never existed.

See the ghost commit

git reflog lists every position HEAD has held, including the now-orphaned Add important analysis commit and its full hash.

Recover

git reset --hard with that hash brings the branch right back; git log --oneline confirms both commits are present again.

Create the hook

A short shell script is written to .git/hooks/pre-commit and marked executable.

Trigger it

A real commit runs the hook automatically; its two echoed lines appear before the commit completes, since -m suppresses only git's own summary output.

Try the working example

bash
export GIT_AUTHOR_NAME="Thuta Learner"
export GIT_AUTHOR_EMAIL="learner@example.com"
export GIT_COMMITTER_NAME="Thuta Learner"
export GIT_COMMITTER_EMAIL="learner@example.com"

git init -q -b main
export GIT_AUTHOR_DATE="2026-01-01T09:00:00"
export GIT_COMMITTER_DATE="2026-01-01T09:00:00"
echo "draft 1" > notes.txt
git add notes.txt
git commit -m "Add project notes"

export GIT_AUTHOR_DATE="2026-01-01T10:00:00"
export GIT_COMMITTER_DATE="2026-01-01T10:00:00"
echo "draft 2 - important work" > notes.txt
git add notes.txt
git commit -m "Add important analysis"
LOST_COMMIT=$(git rev-parse HEAD)

git log --oneline
git reset --hard HEAD~1
git log --oneline
git reflog

git reset --hard $LOST_COMMIT
git log --oneline

# --- hooks ---
printf "#!/bin/sh
echo \"Running pre-commit checks...\"
echo \"pre-commit hook: OK\"
" > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

export GIT_AUTHOR_DATE="2026-01-01T11:00:00"
export GIT_COMMITTER_DATE="2026-01-01T11:00:00"
echo "more notes" >> notes.txt
git add notes.txt
git commit -m "Add more notes"
You should see
After the hard reset, git log --oneline shows only:

ea0d552 Add project notes

git reflog shows the full history, including the lost commit:

ea0d552 HEAD@{0}: reset: moving to HEAD~1
a628635 HEAD@{1}: commit: Add important analysis
ea0d552 HEAD@{2}: commit (initial): Add project notes

After git reset --hard a628635, git log --oneline shows both commits again:

a628635 Add important analysis
ea0d552 Add project notes

For the hook: committing with the pre-commit hook installed printed, before the commit completed:

Running pre-commit checks...
pre-commit hook: OK

5-minute try-it

Deliberately lose a commit with git reset --hard HEAD~1 in a test repo, then recover it using git reflog and git reset --hard. Then write a pre-commit hook that rejects any commit whose message doesn't start with a capital letter (exit 1 with an error message), and confirm it actually blocks a bad commit.

One important caution

Assuming reflog protects you forever — it's local-only, doesn't survive a fresh clone, and its entries eventually expire and get garbage-collected.

Forgetting to make a hook file executable (chmod +x) means Git silently skips it instead of running it, with no error to tell you it didn't fire.

Git - githooks DocumentationGit & GitHub

Easy traps

  • Assuming reflog protects you forever — it's local-only, doesn't survive a fresh clone, and its entries eventually expire and get garbage-collected.
  • Forgetting to make a hook file executable (chmod +x) means Git silently skips it instead of running it, with no error to tell you it didn't fire.
  • Always run `git status` before any destructive or history-rewriting command, to know exactly what state you're in.

Exercise

Deliberately lose a commit with git reset --hard HEAD~1 in a test repo, then recover it using git reflog and git reset --hard. Then write a pre-commit hook that rejects any commit whose message doesn't start with a capital letter (exit 1 with an error message), and confirm it actually blocks a bad commit.

You'll know it worked when: After the hard reset, git log --oneline shows only: ea0d552 Add project notes git reflog shows the full history, including the lost commit: ea0d552 HEAD@{0}: reset: moving to HEAD~1 a628635 HEAD@{1}: commit: Add important analysis ea0d552 HEAD@{2}: commit (initial): Add project notes After git reset --hard a628635, git log --oneline shows both commits again: a628635 Add important analysis ea0d552 Add project notes For the hook: committing with the pre-commit hook installed printed, before the commit completed: Running pre-commit checks... pre-commit hook: OK

Reflog and Git Hooks | Thuta Learning