git: recover squashed commit
I’m working on a lunch & learn presentation on git for work and have been digging around git’s internals.
Currently, I am comfortable enough with squashing commits in my workflow that I can’t think of an immediate need.
When first working with squashing commits, I definitely did the wrong
thing and had to “redo” work lost to git rebase -i
…
After doing research into git, I realize it is possible to recover squashed commits!
If you know the squashed commit you want to recover:
-
run:
$ git show <squashed commit's SHA>
, which will display the commit details, with individual blobs (that’s the git term for a file) -
from step 1, extract out the SHA for the change you want to recover and run:
$ git cat-file -p <file change SHA> > new_file.txt
, which will put the contents of the file change intonew_file.txt
Example
Let’s say a git repository (that has no downstreams!) has the following log:
vagrant@ubuntu-xenial:~/git_recover_squash$ git log
commit bc6cbe67f9e142d077f1f27c6d74c0bd44369d3e (HEAD -> master)
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:26:33 2019 +0000
FIX: update contents
commit 0b7047ea011ad53533d6dd62eff5254e2fd7f31f
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:25:44 2019 +0000
FIX: update contents
commit ad9295675ecf44e808f79f31bc64264f52af3340
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:22:30 2019 +0000
FEATURE: second file
commit b0880368ff63068b4054278d9006deae21f228ae
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:21:54 2019 +0000
FEATURE: first file
commit a35f41e8831edd47183081c1f1dee17528b6cc24
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:21:12 2019 +0000
start of the universe
And to keep the repository history clean, I squash the last two
commits, bc6c
& 0b70
into the feature change: ad92
.
I would run the following command:
$ git rebase -i HEAD~3
...
and then the log would look like:
vagrant@ubuntu-xenial:~/git_recover_squash$ git log
commit f736917fc30c853b096f1417eceb85d00feefd01 (HEAD -> master)
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:22:30 2019 +0000
FEATURE: second file
commit b0880368ff63068b4054278d9006deae21f228ae
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:21:54 2019 +0000
FEATURE: first file
commit a35f41e8831edd47183081c1f1dee17528b6cc24
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:21:12 2019 +0000
start of the universe
Sweet, nice and clean, ready for PR, right?
Pushing up the branch and the continuous integration server throws an error!
Looks like there’s important work in a squashed commit!
Normally, I’d just redo the work and swear I would never rebase again
and force everyone to accept all my commits, even my WIP
. If
anyone complains about my commits, I’d just stand my ground and not
rebase, ever.
I didn’t do that and doubled down on squashing my commits.
And today, there’s a way to recover the squashed commit.
Let’s say the important work is in the 0b70
commit. It’s squashed,
so it’s lost to the ether, right?
Well, no. git maintains all the commits, even when squashing. The blob is still in your commit history, accessing it is just trickier.
To see all the commits, run: $ find .git/objects -type f
vagrant@ubuntu-xenial:~/git_recover_squash$ find .git/objects/ -type f
.git/objects/c7/068a966d283167b96a5d651dffa60695560adb
.git/objects/ac/1bd6c4a88b3483ec16d73651138b6c741dd5a3
.git/objects/0b/7047ea011ad53533d6dd62eff5254e2fd7f31f
.git/objects/f7/36917fc30c853b096f1417eceb85d00feefd01
.git/objects/ad/9295675ecf44e808f79f31bc64264f52af3340
.git/objects/ee/2ae2f16aef8dac88d5021702c35f38b752c4d1
.git/objects/b0/880368ff63068b4054278d9006deae21f228ae
.git/objects/25/d28f069ab4504af450c2070c4383d7368cb95f
.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
.git/objects/20/77dd08466c2d544aa981d4e6d579d4b1a9aeb0
.git/objects/a3/5f41e8831edd47183081c1f1dee17528b6cc24
.git/objects/bc/6cbe67f9e142d077f1f27c6d74c0bd44369d3e
.git/objects/85/519348cc2825306675f39de4539ed6d1efb3e5
.git/objects/8b/fe55b7c90f27ba6739e0ba5b1bb5067204db63
.git/objects/59/1654ce7549e3ddfa389700e41a2329d56565c1
.git/objects/5b/efeb31ceb7ac81be0df1bebc28972d7db48286
See, the 0b70
is still there, even when we told git to squash!
The object, 0b70
is a commit object in git, to check, run: $ git
cat-file -t <SHA>
vagrant@ubuntu-xenial:~/git_recover_squash$ git cat-file -t 0b70
commit
You can’t recover a commit, but you can recover a blob… so using $
git show <commit sha>
reveals:
vagrant@ubuntu-xenial:~/git_recover_squash$ git show 0b70
commit 0b7047ea011ad53533d6dd62eff5254e2fd7f31f
Author: Andrew Leung <[email protected]>
Date: Sat May 4 00:25:44 2019 +0000
FIX: update contents
diff --git a/second_file.txt b/second_file.txt
index ac1bd6c..2077dd0 100644
--- a/second_file.txt
+++ b/second_file.txt
@@ -1 +1,2 @@
second file contents
+more contents to second file
And tada, the commit shows the file changes involved. The important
part is locating what changes are on the second_file.txt
, which is
the only change in the commit.
The change to second_file.txt
in this commit has SHA of:
2077dd0
. Checking the git object type using $ git cat-file -t <SHA>
reveals:
vagrant@ubuntu-xenial:~/git_recover_squash$ git cat-file -t 2077dd0
blob
So, to recover the change, use: $ git cat-file -p <SHA> >
recover_file.txt
, which will recover the file from the commit into
recover_file.txt
vagrant@ubuntu-xenial:~/git_recover_squash$ git cat-file -p 2077dd0 > recover_file.txt
vagrant@ubuntu-xenial:~/git_recover_squash$ cat recover_file.txt
second file contents
more contents to second file
Tada! We have recovered the squashed commit’s file!
Conclusion
Even when a commit is squashed
, it’s still recoverable as the data
is in the commit history.
Once you know the SHA of the blob you want to recover, use: $ git
cat-file -p <SHA> > recover_file.txt
.
Whew, I wish I knew this when I started squashing commits. It would have saved me rewriting work twice!