Have you ever lost work because of an error done while using Git? Perhaps by deleting an unpushed branch by mistake, or having done a hard reset on the wrong repository? A destructive error could mean hours of reconstruction, unless you’re aware that Git has a feature meant for recovery from this sort of mistake.
This feature is known as the reference log, or “reflog” for short. The reflog is your local record of where your branch pointer is, and where it has been in recent history. Every time a change to a branch pointer happens, Git remembers the position.
It should be noted that the reflog is local to specific clones. Short of packing up the .git folder and sending it to a colleague, doing a recovery via the reflog needs to be done on the copy that the error was done on.
So, what exactly does the reflog track, and what does it not? As mentioned, branch pointers are tracked, but so are remote tracking branch updates, HEAD movements and stashes. A list of common operations this includes are:
- clone (will be the initial state until it falls out of history)
- pull (a merge done as a part of a pull step will add an additional entry)
- commit (including separate instances of –amend)
- checkout
- stash
- merge
- rebase
- reset
Most importantly, what the reflog does not track are changes that were never staged or committed. The key takeaway for this being: stage/commit your code often; don’t allow large monolithic deltas to catch you off-guard.
Basic Operations
Interacting with the reference log is done via the `git reflog` command.
Here is an example reflog output:
ebe1ad46f (HEAD -> dev, origin/dev) HEAD@{0}: commit: Build and Run on MacOS
3fb7c8ff4 HEAD@{1}: pull: Fast-forward
f9d877784 HEAD@{2}: commit: Fixing contrib dead linkage
372bbea2b HEAD@{3}: reset: moving to origin/dev
372bbea2b HEAD@{4}: checkout: moving from master to dev
3b6a7e870 (origin/master, origin/HEAD, master) HEAD@{5}: clone: from <repo URL>Each entry shows, in order:
- The commit hash that the reference points to
- The reference name (e.g. this case “HEAD”, “dev” [local branch], “origin/dev” [remote branch])
- Position information within curly braces (e.g. {4}) indicating how many steps back
- Action with git command used to cause the change, and a brief description of the change
Reference logs for specific branches can also be done:
$ git reflog show master
3b6a7e870 (origin/master, origin/HEAD, master) master@{0}: clone: from <repo URL>As well as timestamps:
$ git reflog show master --relative-date # Also try --date=iso
3b6a7e870 (origin/master, origin/HEAD, master) master@{7 weeks ago}: clone: from <repo URL>And even with patches displayed as part of the output:
$ git reflog show master --relative-date -p
3b6a7e870 (origin/master, origin/HEAD, master) master@{7 weeks ago}: clone: from <repo URL>
diff --git a/src/app/app.cpp b/src/app/app.cpp
index 7833fae15..6da32d596 100644
--- a/src/app/app.cpp
+++ b/src/app/app.cpp
@@ -33,7 +33,7 @@ static void SetCompleted(App *_app, bool &_) { App::s_isCompleted = _isCompleted; }
-App::Model *App::s_model = save::g_appFormatter.RegisterModel<App>(
+App::Model *App::s_model = save::ModelStorage::RegisterModel<App>(
APP_STRINGIFY(App),
save::AttributeAccess<&App::m_loadedSavefileVer,Common scenarios
Now that we have some understanding of what we’re looking at with the reference log output, let’s go over some scenarios.
Example 1: Accidental use of `reset –hard`:
$ git reset --hard HEAD~3
# Oops.
$ git reflog
8a91155e0 (HEAD -> dev) HEAD@{0}: reset: moving to HEAD~3
ebe1ad46f (origin/dev) HEAD@{1}: commit: Build and Run on MacOS
[...]
# Now we use the commit before the reset (ebe1ad46f HEAD@{1})
$ git reset --hard ebe1ad46f # using "HEAD@{1}" in place of the hash is also acceptable
And we have effectively undone a hard reset. Note that in doing this, the first hard reset (the 8a91155e0 hash) is still present in the reference log and is now the new HEAD@{1}.
Example 2: Lost commit from a rebase
$ git rebase -i HEAD~5
Successfully rebased and updated refs/heads/dev.
# We need that last commit, however...
$ git reflog
0d9cae3c1 (HEAD -> dev) HEAD@{0}: rebase (finish): returning to refs/heads/dev
0d9cae3c1 (HEAD -> dev) HEAD@{1}: rebase (start): checkout HEAD~5
ebe1ad46f (origin/dev) HEAD@{2}: commit: Build and Run on MacOS
# Now we can cherry-pick the hash of the needed commit
$ git cherry-pick ebe1ad46f
# Don’t forget to commit!Example 3: Mistakenly deleted branch
$ git checkout master
M .gitignore
M .gitmodules
[...]
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git branch -D dev
Deleted branch dev.
# Oops again.
$ git reflog
3853c23ba (HEAD -> master, origin/master) HEAD@{0}: checkout: moving from dev to master
ebe1ad46f (origin/dev) HEAD@{1}: commit: Build and Run on MacOS
[...]
# We can recreate the branch in whole from the last state we saw that branch in
$ git checkout -b dev ebe1ad46f
M .gitignore
M .gitmodules
[...]
Switched to a new branch 'dev'One Final Note
It is important to know that the reference log does not, by default, hold all history forever. Depending on the situation, entries will expire within 30 or 90 days. This behaviour can be changed via:
# You can also use "never" to keep everything or "now" to not keep a log (strongly discouraged) # You can also make this affect all repos you use with --global $ git config gc.reflogExpire 365.days $ git config gc.reflogExpireUnreachable 90.days
Ultimately, the reference log serves well as a light in the dark, should things go wrong. These are some common examples, but most destructive operations short of deleting the .git folder can be recovered, from bad amends on commits to merges gone awry.
References and Additional Reading:
- https://git-scm.com/docs/git-reflog
- https://git-scm.com/docs/git-gc (for cleanup/pruning of the reference log)
| MT, Software and Platform Engineer Versatile software developer with expertise in programming (C, C++, Python, PHP, JavaScript), databases, cross-platform systems, and debugging, specializing in UNIX/Linux and Windows environments. |

