How-to Dig Out of git reset "git hole"
I want to share a git
hole situation I had a hard time digging
myself out of.
I show how I dug myself into the situation by resetting changes more
than I wanted and show to easily fix the problem by using git
reflog
.
If you are in or run into this situation, you won’t have to agonize
how to dig yourself out of the git
hole and can elegantly put the
repository into a desired state.
This article will take you about six minutes to read.
Introduction
Ever get yourself into a “git hole”, where the repository’s state is not what you want by running the wrong command?
Most of the time, just “pulling down the repository again” will fix it (as with almost all problems related to software).
What if there’s actually work you want to keep or worse, other people’s commits you want to save?
What Happened?!
In my situation, a team member fat-fingered a command and put the repository into weird state. A state he didn’t want it to be in.
This article, I share the same situation using a practice repository.
If you want to follow along code in this article:
- Clone repository:
git clone https://github.com/a-leung/tdd_indepth_callbacks.git
- Change to branch:
git checkout fix/update_rails_1
Starting Out
I was looking at recent git history, say before pushing up changes and making sure I have a clean history when pushing up changes:
$ git log
commit 78323e0a89d12e486310ffffb3d90d65699fd2bd
Author: Andrew Leung <git commit email address>
Date: Sat Jul 4 00:02:05 2020 +0000
FIX: running bin/rails app:update
commit eaa73c18028ef149083921b517dc539b79630498
Author: Andrew Leung <git commit email address>
Date: Fri Jul 3 23:57:58 2020 +0000
FIX: Update Rails to 6.0.3.2
...
commit cfa3b5b90f93780404a033f16374c2d9e9c7e428
Author: Andrew Leung <git commit email address>
Date: Fri Jan 18 23:15:08 2019 +0000
Code: Order is received - only after create
commit 5bd384ba4152e1284523b0aec4cf12023131c83c
Author: Andrew Leung <git commit email address>
Date: Fri Jan 18 23:11:39 2019 +0000
Code: Order is received - simple
commit 5bcbbe5742fd84a37751b79979ca533b9cba0002
Author: Andrew Leung <git commit email address>
Date: Fri Jan 18 23:10:06 2019 +0000
Test: Order is received
Well, the running bin/rails app:update
can be part of the Update
Rails to 6.0.3.2
commit because they’re the same process.
Clean up History
In this case, the easy thing to do: reset the last commit and amend it into the commit before that.
Step 1, run: git reset HEAD~1
$ git reset HEAD~11 Unstaged changes after reset: M Gemfile M Gemfile.lock M app/models/order.rb M bin/rails M bin/rake M bin/setup M config/cable.yml M config/environments/development.rb M config/environments/production.rb M config/environments/test.rb M config/initializers/content_security_policy.rb M config/locales/en.yml M config/puma.rb M config/routes.rb M config/spring.rb M db/schema.rb M spec/models/order_spec.rb
What the?! What did I do???
I typed: git reset HEAD~11
instead of git reset HEAD~1
!? I jumped
back 11 commits instead of 1!!!!
AUGH!
What’s the git status
???
Is this situation true??? Let’s confirm using git status
$ git status
On branch fix/upgrade_rails_1
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: Gemfile
modified: Gemfile.lock
modified: app/models/order.rb
modified: bin/rails
modified: bin/rake
modified: bin/setup
modified: config/cable.yml
modified: config/environments/development.rb
modified: config/environments/production.rb
modified: config/environments/test.rb
modified: config/initializers/content_security_policy.rb
modified: config/locales/en.yml
modified: config/puma.rb
modified: config/routes.rb
modified: config/spring.rb
modified: db/schema.rb
modified: spec/models/order_spec.rb
Untracked files:
(use "git add <file>..." to include in what will be committed)
config/initializers/new_framework_defaults_6_0.rb
db/migrate/20200704000022_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb
no changes added to commit (use "git add" and/or "git commit -a")
Yeah, there are files that are yet to be commited that are not part of the commit I want to squash. Crap. Did I just lose commit history too?!
What does git log
say??
There’s gotta be a mistake, what’s the current log???
$ git log
commit 5bd384ba4152e1284523b0aec4cf12023131c83c
Author: Andrew Leung <git commit email address>
Date: Fri Jan 18 23:11:39 2019 +0000
Code: Order is received - simple
commit 5bcbbe5742fd84a37751b79979ca533b9cba0002
Author: Andrew Leung <git commit email address>
Date: Fri Jan 18 23:10:06 2019 +0000
Test: Order is received
commit bb5c31e9b78bb0ecf88b46471ff367591b8be481
Author: Andrew Leung <git commit email address>
Date: Fri Jan 18 23:09:19 2019 +0000
Code: Order is pending - callback
commit db84373577f704e18a971b40089c66b60ce544f3
Author: Andrew Leung <git commit email address>
Date: Fri Jan 18 23:08:47 2019 +0000
Code: Order is pending - simple
The first entry of git log is from January 18, not July 3, which is what I wanted.
There’s definitely commits missing. In this case, they’re only my commits. If I had commits from team members lost, it would be even worse.
How do I fix this?!
After catching my breath and realizing: well, at least I didn’t lose actual work. That would be even worse. I’ll take the extra “git blame” on those changes. Recreating and extra 10 commits of work, oh boy. Things can definitely be worse. :-)
Enter: git reflog
git has a tool that tracks all the changes of the current HEAD, just
run: git reflog
. When I run it in my case:
$ git reflog
5bd384b HEAD@{0}: reset: moving to HEAD~11
78323e0 HEAD@{1}: checkout: moving from fix/upgrade_rails_2 to fix/upgrade_rails_1
595a6f1 HEAD@{2}: checkout: moving from master to fix/upgrade_rails_2
95769b4 HEAD@{3}: checkout: moving from fix/upgrade_rails_2 to master
595a6f1 HEAD@{4}: commit: FIX: Upgrade to Rails 6.0.3
95769b4 HEAD@{5}: checkout: moving from master to fix/upgrade_rails_2
95769b4 HEAD@{6}: checkout: moving from fix/upgrade_rails_1 to master
78323e0 HEAD@{7}: commit: FIX: running bin/rails app:update
This is the last set of actions I took on the repository. git
tracks
all changes made to the HEAD reference of the repository, stating the
SHA value of the change, the HEAD history from 0, and a description of
the action.
How can I use this??
To fix the problem of git reset HEAD~11
, which is in the reflog as:
5bd384b HEAD@{0}: reset: moving to HEAD~11
The solution is the entry just before it:
78323e0 HEAD@{1}: checkout: moving from fix/upgrade_rails_2 to fix/upgrade_rails_1
To have git change HEAD to that entry, just do a git reset
to the
HEAD by running command:
$ git reset HEAD@{1}
Did that work?!
Let’s check the repository status now, what’s staged?
$ git status
On branch fix/upgrade_rails_1
nothing to commit, working directory clean
All clean… how about the git history?? Let’s check that using git
log
:
$ git log
commit 78323e0a89d12e486310ffffb3d90d65699fd2bd
Author: Andrew Leung <git commit email address>
Date: Sat Jul 4 00:02:05 2020 +0000
FIX: running bin/rails app:update
commit eaa73c18028ef149083921b517dc539b79630498
Author: Andrew Leung <git commit email address>
Date: Fri Jul 3 23:57:58 2020 +0000
FIX: Update Rails to 6.0.3.2
commit 8d3db208eba4de995d6b632b1766617403c3a709
Author: Andrew Leung <git commit email address>
Date: Fri Jul 3 01:14:36 2020 +0000
FIX: Update Ruby to 2.6.0
Everything’s there again, just like it was at the beginning. All work committed with the right commit history.
More information
If you want to find out more, the best place to look is the official documentation on reflog.
How it works
Although git presents a linear time line of work on the tracked items, under the hood, git is tracking every change in a non-linear manner. This is why you can even recover squashed commits.
The reflog
is another entry to git’s guts without exposing all the
details.
Why it’s important to know
If you want to be better at your craft, knowing your tools better improves your craft.
git is an essential tool for software development as it’s one of the most used code versioning systems today.
Another Solution
Given this knowledge of using the reflog
- there is another way of
solving the same problem without using the reflog:
If you notice, the entry:
78323e0 HEAD@{1}: checkout: moving from fix/upgrade_rails_2 to fix/upgrade_rails_1
has the same SHA as:
78323e0 HEAD@{7}: commit: FIX: running bin/rails app:update
Which refers to this commit:
commit 78323e0a89d12e486310ffffb3d90d65699fd2bd
Author: Andrew Leung <git commit email address>
Date: Sat Jul 4 00:02:05 2020 +0000
FIX: running bin/rails app:update
The original commit I wanted to “reset” to.
This means running, just running git reset <sha>
would have solved
the same problem:
$ git reset 7832
Will have the same effect.
reflog
won’t be necessary if you had the commit SHA you want to
return to. Either in your screen’s buffer or even on the upstream
repository.
Conclusion
I never knew the reflog
was there and now that I know it, I couldn’t
believe why didn’t I know it sooner!
It would have saved me a lot of stress earlier in my development career and awkward conversations.
With reflog
, I see it’s a clean way into the guts of git as it’s how
git is tracking what to point to inside it’s own database of stuff.