Git: How do I recover the most recent version of every single file ever removed in a branch?

Viewed 65

I have a Git repo with one branch where, over the course of more than a year, thousands of files have gone through a lifecycle where each has been initially added, then modified a number of times, and then finally removed.

I want to retrieve the most recent version of every single file ever to exist in the repo before it was deleted, in order to archive every single one to a separate location. I don't need to restore those old versions in Git, all I need is to dump out the newest version of every single file (respecting its corresponding relative path) to some directory external to the repo.

What would be a good way of accomplishing this?

(The other Q&A's I've seen deal with how to recover single files or subdirectories removed by a single commit or a few commits, not how to do this for thousands of files over thousands of commits.)

1 Answers

I might start with, perhaps:

 git log --format='' --full-history --name-status --diff-filter=D

This should list all the deleted files. It does not capture the commit hash, although it could if integrated with a smarter script. Then get the commit the file was deleted in — as the file is deleted it is assumed it was deleted in the last commit that touched the path — and use git show deleted_in^:filename to get the file contents prior to deletion.

This bash snippet should restore the deleted files in "tmp". Alter as relevant to cover what "in a branch" means. I recommend adding a -n 100 or similar for testing. YMMV.

 git log --format='' --full-history --name-status --diff-filter=D |
 cut -f2 |
 (while read file; do (
     c=$(git rev-list head --max-count=1 -- "$file");
     echo "restoring '$file' deleted in $c";
     mkdir -p "tmp/$(dirname \\"$file\\")";
     git show $c^:"$file" > "tmp/$file";
   ); done)

A more intelligent script might capture the commit the file was deleted in and the filename to avoid the secondary git rev-list use above. Such a script would then to account to ensure it still used the last commit the file was modified in, an issue that is not present in the script above as the rev-list usage only returns the last commit.

Related