Author Topic: Git: Restoring Orphaned Commits with git reflog  (Read 1109 times)

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4853
Git: Restoring Orphaned Commits with git reflog
« on: March 20, 2019, 06:15:45 AM »
Here's an example of restoring orphaned commits in a local repository using git reflog.

First, let's create a test repository, add some data to it, wipe out some data leaving an orphaned commit, and then restore it on a new branch using git reflog.

Creating some data:
Code: [Select]
git init
echo "Initial File" > file.txt
git add .
git commit -m "Add initial file"
echo "@41.7266831,-81.2882074,13z 12345" > file.txt
git commit -am "Cat walked across my keyboard, oops, sorry"

Unfortunately, during work, a cat walked across the keyboard, making a random series of changes, which went unnoticed and were rolled into a commit.

Let's take a quick look at the current project history:
Code: [Select]
git log -p
commit f3fa43be38be2988b35b25336d84735a8bcd611c (HEAD -> master)
Author: Hooman <Hooman>
Date:   Wed Mar 20 17:57:04 2019 +0700

    Cat walked across my keyboard, oops, sorry

diff --git a/file.txt b/file.txt
index f1ec229..50ad6e0 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-Initial File
+@41.7266831,-81.2882074,13z 12345

commit 6ba95e9ee8b289b19ca31bd4ee062fb31d372efc
Author: Hooman <Hooman>
Date:   Wed Mar 20 17:57:04 2019 +0700

    Add initial file

diff --git a/file.txt b/file.txt
new file mode 100644
index 0000000..f1ec229
--- /dev/null
+++ b/file.txt
@@ -0,0 +1 @@
+Initial File

Realizing the mistake, the programmer quickly removed the commit, and reset the master branch to the commit before, leaving no trace of the accidental changes on master.

Code: [Select]
git reset --hard HEAD^

The history of the project is now:
Code: [Select]
git log -p
commit 6ba95e9ee8b289b19ca31bd4ee062fb31d372efc (HEAD -> master)
Author: Hooman <Hooman>
Date:   Wed Mar 20 17:57:04 2019 +0700

    Add initial file

diff --git a/file.txt b/file.txt
new file mode 100644
index 0000000..f1ec229
--- /dev/null
+++ b/file.txt
@@ -0,0 +1 @@
+Initial File


But things are not as they seem. That commit was no cat accident! No, the programmer was actually an evil mastermind, with a penchant for stroking feline friends. He has in reality embezzled meeeelions of dollars from the company, and used half the money to buy nuclear weapons to hold the world hostage. The accidental addition was actually an encoded message with the coordinates and combination to a safe containing the other half of the embezzled money, and the nuclear launch codes. We need to get that information back.

Fortunately, Git tracks all moves to the current HEAD. Each time you switch branches, or make a commit, you update the position of HEAD. The previous location of HEAD is stored in the git reflog. You can use the syntax HEAD@{n} to access what HEAD was n moves ago. We check the reflog with:
Code: [Select]
git reflog
6ba95e9 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
f3fa43b HEAD@{1}: commit: Cat walked across my keyboard, oops, sorry
6ba95e9 (HEAD -> master) HEAD@{2}: commit (initial): Add initial file

Aha, the commit with the secret message is plainesly (typo?) visible. ;)

We restore the deleted commit to a new branch as follows:
Code: [Select]
git checkout HEAD@{1}
git checkout -b newBranchWithRestoredCommit

At this point, we can pause and check the current state of the repository:
Code: [Select]
git branch --all
  master
* newBranchWithRestoredCommit

The history of newBranchWithRestoredCommit:
Code: [Select]
git log -p
commit f3fa43be38be2988b35b25336d84735a8bcd611c (HEAD -> newBranchWithRestoredCommit)
Author: Hooman <Hooman>
Date:   Wed Mar 20 17:57:04 2019 +0700

    Cat walked across my keyboard, oops, sorry

diff --git a/file.txt b/file.txt
index f1ec229..50ad6e0 100644
--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-Initial File
+@41.7266831,-81.2882074,13z 12345

commit 6ba95e9ee8b289b19ca31bd4ee062fb31d372efc (master)
Author: Hooman <Hooman>
Date:   Wed Mar 20 17:57:04 2019 +0700

    Add initial file

diff --git a/file.txt b/file.txt
new file mode 100644
index 0000000..f1ec229
--- /dev/null
+++ b/file.txt
@@ -0,0 +1 @@
+Initial File

The history of master:
Code: [Select]
git log -p master
commit 6ba95e9ee8b289b19ca31bd4ee062fb31d372efc (master)
Author: Hooman <Hooman>
Date:   Wed Mar 20 17:57:04 2019 +0700

    Add initial file

diff --git a/file.txt b/file.txt
new file mode 100644
index 0000000..f1ec229
--- /dev/null
+++ b/file.txt
@@ -0,0 +1 @@
+Initial File

And for good measure, since we were just creating and switching branches, here's what the current reflog now looks like:
Code: [Select]
git reflog
f3fa43b (HEAD -> newBranchWithRestoredCommit) HEAD@{0}: checkout: moving from f3fa43be38be2988b35b25336d84735a8bcd611c to newBranchWithRestoredCommit
f3fa43b (HEAD -> newBranchWithRestoredCommit) HEAD@{1}: checkout: moving from master to HEAD@{1}
6ba95e9 (master) HEAD@{2}: reset: moving to HEAD^
f3fa43b (HEAD -> newBranchWithRestoredCommit) HEAD@{3}: commit: Cat walked across my keyboard, oops, sorry
6ba95e9 (master) HEAD@{4}: commit (initial): Add initial file

Offline TechCor

  • Jr. Member
  • **
  • Posts: 83
Re: Git: Restoring Orphaned Commits with git reflog
« Reply #1 on: March 20, 2019, 12:33:03 PM »
Interesting.

So basically the only way to permanently delete a commit is to delete the repo and start over? Seems like a terrible thing to have to do if "Government Employee Shmee" commits the nuclear launch codes to a repo where Russian operatives reflog their way to the Apocalypse.

Sorry guys, we gotta blow the whole thing (the repo, not the nukes). Hope you didn't make any mistakes in that nuclear sub targeting code, 'cause we gotta reset history (the repo, not the planet).

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4853
Re: Git: Restoring Orphaned Commits with git reflog
« Reply #2 on: March 20, 2019, 12:49:57 PM »
Lol

You could force a garbage collection with the expiry time set to now.

Of course, that restore work needs access to the reflog, which is a local thing. You can't do that against a remote repository. I recently tried against a GitHub repo, and it didn't work out. Not sufficient access to download a local copy of the commit, nor tag it remotely. I could see the diff online though. Oddly enough that was allowed, but downloading the commit through Git wasn't.

Offline TechCor

  • Jr. Member
  • **
  • Posts: 83
Re: Git: Restoring Orphaned Commits with git reflog
« Reply #3 on: March 20, 2019, 01:43:36 PM »
I've never heard of reflog before now, so I read up on it a bit more.

I would describe it as a recycle bin for commits. The default expiry is 30 days.

This is drastically different from what I thought was "forever", which could be a problem in some situations.


So guys, don't wait too long to recover those nuke codes! Tell the boss the 157 pages of paperwork you have to fill out to restore a commit ( + waiting period + board review + waiting for the ink for the rubber stamp) is going to have to wait.

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4853
Re: Git: Restoring Orphaned Commits with git reflog
« Reply #4 on: March 21, 2019, 08:19:22 AM »
That's a pretty accurate description.

Perhaps thankfully, it's also different from what I expected of immediately gone right away.

Git tends to stop you from doing overly stupid things. Like simply deleting stuff without warning. If you try to delete an unmerged branch, it warns you. If you rewrite history, there is no warning, but then, the new history is still there, so presumably the work still exists. Plus the reflog and garbage collection process means the old history is still there too, you just have to learn how to access it.

Even with Git's powerful history rewriting capabilities, it still remains quite safe. I've never lost code because of it. Maybe occasionally worried myself, due to not understanding what's going on at the time, but I've never lost anything.