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();
@annamalaiarunachalam
Copy link

I strongly believe the .git/objects/folder has all my files
image

@annamalaiarunachalam
Copy link

there are 258 folders in objects, with several sub-folders. the properties of 'object' folder shows 950 files.
git bash tool's commit operation has encrypted/compressing all my files and moved them to object folder. but whatever command I have tried so far doesnt seem to decrypt/decompress and restore it back into main workspace. besides, it doesnt push it to github either. all my files are stuck in the objects folder. its quite baffling, how this entire git tool is designed. can you help me with the decompress/decrypt of my files?

@3d-illusions
Copy link

how do you identify the root object, and how do you modify the php script accordingly please?

@jimrubenstein
Copy link
Author

@3d-illusions

You can use this bash script to output a list of the objects that are trees (directories) and the files they point to:

#!/bin/bash

for f in .git/objects/*/*; 
  do prefix=$(echo $f | cut -d / -f 3); 
  obj=$(echo $f | cut -d / -f 4); 
  type=$(git cat-file -t $prefix$obj); 

  if [[ $type == 'tree' ]]; 
  then 
    echo $prefix$obj; 
    git cat-file -p $prefix$obj; 
  fi; 
done;

this will output (potentially) a lot of stuff. You'll want to look through it to find the object that contains the files on the root of the directory.

Here's an example of one of mine:

1e9fd13ba91a602176ec2ed1b7616465d93c9abb
100644 blob 1671c9b9d94ae80b2d39c6b6a64d154b0ac6cb65	.editorconfig
100644 blob 44853cd59a7f52746b03239cbdac581d529e7c03	.env.example
100644 blob 967315dd3d16d50942fa7abd383dfb95ec685491	.gitattributes
100644 blob e45547285c62b5c739341255efca090152ddcdd6	.gitignore
100644 blob 877ea701dbf43c9a22cd57f70d876e69a3cb42c4	.styleci.yml
100644 blob 49c38be935606513ed8118401a1707a588dadebd	README.md
040000 tree be46bffe506f151d04b6a007875c426ec4a34a15	app
100755 blob 67a3329b183e042b14516122b5d470bc337a5a90	artisan
040000 tree fa579600b150dfe96277f923c509bc473517b32a	bootstrap
100644 blob 5e3dc6aaca598d6e750d8938b8ca3bfb93a63e33	composer.json
100644 blob f98cd7596615f0125e6d525203163cad4a8f5504	composer.lock
040000 tree 98261ed53ce2c6f11fb8554bfdaa803302c1020d	config
040000 tree caeda1d15bea1531e6c3dbb07444c37dc73b5aab	database
100644 blob 1447c30508375c3b5e106b0684ae22b7619cb57f	package-lock.json
100644 blob 304e26707c6a2794546a517a52d73c02e5a68105	package.json
100644 blob 4ae4d979d1ecc9bb45aabf0ff7071ce0e63bd4e2	phpunit.xml
040000 tree f82e1c58e9e62d5c241538ef419ec18f467d3a0b	public
040000 tree ed583017fccdfc4ce370762756a4c41481eced40	resources
040000 tree 131b325669c6284caa0111245124c5eaff8eab46	routes
100644 blob 5fb6379e71f275a1784e6638adc96420aaa5c5b2	server.php
040000 tree a9b549189653697bdcc2597e2a81e93fae10cea6	storage
100644 blob cbf5b8313a92e74c4b679b6fa519b38e0a5c2513	tailwind.config.js
040000 tree 4e9c4c557d2a1efe665c8b303db5c704da6294be	tests
100644 blob 71d071d37d860b3d993fbb246696dd91bac27a7e	webpack.mix.js

The root object identifier in this case is: 1e9fd13ba91a602176ec2ed1b7616465d93c9abb

Worth noting, the directories listed in ./git/objects are the prefix for each object within those directories. So, in the case of the root object, above, it's actually located in: .git/objects/1e/9fd13ba91a602176ec2ed1b7616465d93c9abb. This is worth making note of, if you try to run the git cat-file commands directly.

Hope this helps.

@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