Skip to content

Instantly share code, notes, and snippets.

@jimrubenstein
Last active September 15, 2023 19:04
Show Gist options
  • Save jimrubenstein/9827718c508b2b7f5bad578bae421bfc to your computer and use it in GitHub Desktop.
Save jimrubenstein/9827718c508b2b7f5bad578bae421bfc to your computer and use it in GitHub Desktop.
Restore files from .git/objects

This script will restore all the objects in your .git/objects folder.

To start, you must identify what the "root object" is. This will be a tree object that lists out the files that are in the root of your git repository.

Once you have that object identified, this script will recursively walk through the tree and restore all the files that git knows/knew about.

background

I started working on a new project and got quite a bit of work done locally before initializing a git repo or creating any commits.

When I decided it was time to commit, I ran git init && git add . && git commit. At this point, I realized that there were some files I wanted to add to the .gitignore, so I cancelled the commit and composed my .gitignore file. At this point, I decided I'd re-add everything (now that I was ignoring stuff I didn't need) and ran git reset --hard.

YIKES

This wiped out all the code I had written.

No attempts to recover would work (git reflog was empty, git fsck couldn't find any objects, git rev-log had nothing). All I had what was left of the .git/objects directory.

After some reading and understanding of how the git object data is stored and written, it became clear that I could recover my data by parsing these files by hand.

The attached script does exactly that.

What a Monday.

#!/usr/local/bin/php
<?php
define('DIR', 'tree');
define('FILE', 'blob');
function main() {
$root_object = 'f3c8ec92f83e2a87c1d00a5681598126e23ab356';
restore_object($root_object, 'restored');
}
function restore_object($object, $dest) {
$type = get_object_type($object);
if ($type == FILE) {
echo 'restoring ' . $dest . "\n";
shell_exec('git cat-file -p ' . $object . ' > ' . $dest);
} else {
echo 'restoring ' . $dest . "\n";
mkdir($dest);
restore_directory($object, $dest);
}
}
function restore_directory($object, $base_dest) {
$files = parse_tree_object($object);
foreach ($files as $file) {
restore_object($file['object'], $base_dest . '/' . $file['name']);
}
}
function parse_tree_object($object) {
if (DIR != get_object_type($object)) {
throw new Exception("Trying to decode TREE object, but BLOB given: {$object}");
}
$contents = read_object($object);
$tree = [];
$lines = explode("\n", trim($contents));
foreach ($lines as $branch) {
$leaves = preg_split("#\s+#", $branch, 4);
$tmp = [
'mode' => $leaves[0],
'type' => $leaves[1],
'object' => $leaves[2],
'name' => $leaves[3],
];
array_map('trim', $tmp);
$tree[] = $tmp;
}
return $tree;
}
function read_object($object) {
$contents = trim(shell_exec('git cat-file -p ' . $object));
return $contents;
}
function get_object_type($object) {
$type = shell_exec("git cat-file -t {$object}");
return trim($type);
}
main();
@3d-illusions
Copy link

Thanks Jim, I've sorted it now though. did an init git repo in empty folder, copy in objects folder from the repoistory where everything has been set to 0kb except the objects folder. Then git fsck, and then merged the sha1 of the dangling commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment