Deleting an uncommitted file is a rare yet painful event. Especially if we have
spent a couple of hours working on it. It’s useful to know there’s a chance to
recover the change. If the change has been staged we can find it as a dangling blob
with git fsck
1.
Git will create a dangling blog
every time we stage a change. So even if we
remove a file or stage another change on top of the old one theoretically, we’ve
got a chance to recover.
We can get all dangling blobs with this.
git fsck --full --unreachable --lost-found
Checking object directories: 100% (256/256), done.
unreachable blob a65c803815a7ef4d7094507d7f290702dd9b728a
unreachable blob 8b0d18a21094b08039a7c6631b6be2c9467b3d2e
Then check the content of a blob.
git show a65c803815a7ef4d7094507d7f290702dd9b728a
If we try this on a repository with long history. Chances are we’re going to get way too many blobs. So, we won’t be able to get the one we need. We can create a short bash script to help.
touch /usr/local/bin/recover_changes
chmod +x /usr/local/bin/recover_changes
Bear in mind we have to put the scrip in our PATH
. So, we can execute it
from everywhere.
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
has_git_on_system=$(git --version > /dev/null 2>&1 && echo $?);
within_git_repository=$(git rev-parse --is-inside-work-tree 2> /dev/null);
function fail () {
echo "FAIL:" $* >&2;
exit 1;
}
if [[ $has_git_on_system != "0" ]]; then
fail "Cannot access git!"
fi
if [[ ! $within_git_repository ]]; then
fail "This is not a git repository!"
fi
function get_all_unreachable() {
git fsck --full --no-reflogs --unreachable --lost-found 2> /dev/null;
}
function get_only_blobs() {
grep blob;
}
function get_hash_values() {
cut -d\ -f3;
}
function print_changes() {
while read change;
do printf "blob: $change\n"; git cat-file -p $change;
printf "\n----------------------------------------------------------\n";
done
}
get_all_unreachable \
| get_only_blobs \
| get_hash_values \
| print_diffs > recovered_changes.txt;
exit 0;
It’s handy to have an alias in the .gitconfig
file.
[alias]
recover-ch = "!bash ~/bin/recover_changes"
So we can execute the script and create a file recovered_changes.txt
, which
contains all the changes, separated by dashed lines.
git recover-ch