Did you know we can use Git reference logs to restore Git commit history(?) Often we developers experience challenges when working with Git. We can accidentally force-push commits to the remote main branch, complete a rebase that went bad, delete a branch we wanted to keep or hard reset to the wrong commit.
In this post, we will look at git reflog
and git reset
. We will review some common scenarios on restoring local commits, restoring a deleted branch, and reverting force-pushed changes due to a rebase.
Git reflog
git reflog is a tool that can help us recover almost anything - at least everything we have committed. The tool keeps track of commits that have been created but also discarded ones in a log called the reference log. The reference log is kept on our local computer under the .git
directory in a local repository. It contains a list of commits that HEAD
has pointed to and keeps track of the entire history of a commit. Besides the reference log for branches, we have a separate reference log for stash.
(Records of the history on a branch will expire after 30 days by default. We can change the expiration days, by running git reflog —expire
in our terminal, if we want to keep the history for a longer time.)
It's important to know that reference logs do not track in the remote repository, like pushes, fetches and clones; it is purely local and current to the working environment. Below is two screencaps, the first from Github Codespaces and the second from a local filesystem that initially created all the commits of the main branch in the same repository:
Git log vs reflog
git log is a command that shows a list of commits related to a branch. git reflog
is similar to git log
but shows a list of snapshots and times when HEAD
was changed. reference logs also reference other types such as stash, branches, rebase, merge and tags. A reference is known as refs, and the syntax for accessing these is name@{qualifier}
. Running git reflog
is the most basic use case and essentially the equivalent of writing git reflog show HEAD
. The output will look like this:
=> git reflog
47efaa2 HEAD@{0}: commit: refactor: remove post schema
f243f12 HEAD@{1}: commit: refactor: change author fields
4783ba0 HEAD@{2}: commit: chore: add .env to gitignore
a19d142 HEAD@{3}: commit: chore: update readme
f5ade9a HEAD@{4}: commit: chore: update yarn.lock
854c08f HEAD@{5}: commit (initial): feat: bootstrap sanity studio
Git reset
git reset is a command we can use to move back to a previous commit, discarding or undoing previous changes. We can run git reset --hard <commit_sha>
to move our branch back in the commit history.
A hard reset (git reset --hard
) will completely destroy any changes and remove them from your local repository.
A soft reset (git reset --soft
) will keep your changes, and stage them back automatically.
A mixed reset (git reset --mixed
) which is the default, will keep you changes but they will also become unstaged. Even tho it is flexible, you will have to stage the changes manually.
Restore local commits
Scenario
We have made some commits that haven’t been pushed to the remote repository yet. We do a git reset —-hard <commit_sha>
to a previous commit, and our local branch is set back to an earlier point and discarded all history after the chosen commit. We then run git log
and see that the commits are gone. We cannot pull the changes from the remote repository since we never synced the changes.
How to restore local commits
When running git reflog we can see that our changes still are tracked by the reference log; we even have a separate commit for the hard reset that we did earlier - take a look at commit f243f12
on HEAD@{0}
:
f243f12 HEAD@{0}: reset: moving to f243f12
eec6f27 HEAD@{1}: commit: refactor: author should not have image
47efaa2 HEAD@{2}: commit: refactor: remove post schema
f243f12 HEAD@{3}: commit: refactor: change author fields
With this information, we can reset our branch back to the state we discarded by using one of the references in the reflog. We can move our branch to the selected commit by running git reset --hard <commit_sha>
:
=> git reset --hard f243f12
=> git reflog
f243f12 HEAD@{0}: reset: moving to eec6f27
f243f12 HEAD@{1}: reset: moving to f243f12
eec6f27 HEAD@{2}: commit: refactor: author should not have image
47efaa2 HEAD@{3}: commit: refactor: remove post schema
f243f12 HEAD@{4}: commit: refactor: change author fields
Restore a deleted branch
We can recover deleted branches using SHA’s in the reference logs.
Scenario
Not uncommonly, we might checkout a new branch in our local environment. We could have a branch called chore/update-packages
and commit a change that adds dayjs as a package:
=> git checkout -b chore/add-dayjs-package
=> yarn add dayjs
=> git add .
=> git commit -m 'chore: add dayjs package'
5a75042 HEAD@{0}: commit: chore: add dayjs package
At a later point, we checkout the main branch and deletes the chore/add-dayjs-package
branch — maybe we did not need it anymore for some reason:
=> git checkout main
47efaa2 HEAD@{0}: moving from chore/add-dayjs-package to main
=> git branch -D chore/add-dayjs-package
Deleted branch chore/add-dayjs-package (was 5a75042)
We will get a confirmation message that git deleted the branch. After a while, we discover that we could need the changes after all, so how do we get them back?
How to restore a deleted branch
We can undo these changes with git reflog
and git checkout
. If we run git reflog
we won't see any references to the deleted branch. We could use the commit SHA provided in the delete message (5a75042
) to revert the latest commit on that branch. But if we have lost the message, we need to use the reference log. And if you named your commits well, it should be easy to find the missing commit take a look at commit 5a75042
on HEAD@{1}
:
=> git reflog
// deletion of a branch is not logged to the reference log
47efaa2 HEAD@{0}: moving from chore/add-dayjs-package to main
5a75042 HEAD@{1}: commit: chore: add dayjs package
47efaa2 HEAD@{2}: moving from main to chore/add-dayjs-package
We can now checkout the deleted commit SHA and create a new branch from that commit running git checkout -b <branch_name>
:
=> git checkout 5a75042
=> git checkout -b chore/add-dayjs-package
Revert force-pushed changes
Scenario
We reset our branch using git reset --hard to an earlier commit and run git push - force to override any changes that we otherwise could pull from the remote repository. But at some point, we want those changes back.
=> git reset --hard <commit_sha>
=> git push --force
How to revert force-pushed changes
We can use git reflog and git reset to restore force-pushed changes:
=> git reflog
f243f12 HEAD@{0}: reset: a19d142 to f243f12
By reading the log we can see that the message on HEAD@{0}
is reset: a19d142 to f243f12
, which means that commit with hash a19d142
was force-pushed by f243f12
. That also gives us information about which commit we need to restore, the one with hash a19d142
. We can simply do the same procedure as initially to move back to the overriden commit:
git reset --hard a19d142
git push --force
Finally, we force-push the new changes to the remote repository. Always be careful and check that you are on the correct branch before doing a force push. Especially to prevent that production code is not being affected or team member code is not being overwritten.
Thanks for reading! (This article was originally posted on Medium )
Code foh shizzle
Top comments (0)