So let's just say, hypothetically, you rewrote your git history or created a fixup commit that deleted a file, force pushed to github, deleted the repo locally, then realized you accidentally deleted one file too many.
I know, is it possible to commit so many sins at once?
This did not happen with code for me, it was just some dotfiles stuff. I was moving things around and removed a sensitive file from the commit history, then accidentally deleted the repo, only to realize I hadn't backed up the sensitive file I still needed.
First Step, Don't Panic
If you did not delete the repo, you're good. You can search how to use git reflog
to recover it.
Otherwise github should keep the commits for 90 days.
Now I found the partial answer to how to do this here.
The gist of it is we can use the github api to find the commit and extract a patch. Problem is with a private repo this answer won't work. Additionally if we committed multiple files we don't want to have to extract each patch as suggested by the answer. We just want the full patch.
Get the Github CLI
If you don't have it already you will need the github cli. Authenticate with gh auth
, and now we can use the gh api
function.
Find Commit to Recover
First, if we need to find which commit we need to recover we can search through the events:
gh api repos/{OWNER}/{REPO}/events
We can stick the result in a temp file for easier searching.
gh api repos/{OWNER}/{REPO}/events > temp
And we can then make it easier to find the commit by filtering the response with jq.
cat temp | jq ".[].payload.commits.[] | {url: .url, author: .author.name, message: .message}"
Once we find our commit we can query the url (just the end bit see here):
gh api repos/{OWNER}/{REPO}/commits/{SHA}
This will give us a json response which is not what we want but we can use it if we want to further find if it contains the correct changes we're looking for.
gh api repos/{OWNER}/{REPO}/commits/{SHA} | jq ".files[] | {status:.status, filename:.filename, patch: .patch}"
Get the Patch
To actually get the patch we will need to change a header.
# save the patch somewhere not in the repo
gh api -H "Accept: application/vnd.github.patch" repos/{OWNER}/{REPO}/commits/{SHA} > patch
Apply the patch.
We can then apply it (be sure to have committed any changes beforehand). How to apply it can depend, but the following should work even if the repo doesn't have the files anymore, you can then recover what you need:
git apply /path/to/patch --3way --ignore-space-change --ignore-whitespace
Further Notes
Note that if the patch contains binary file changes, applying them might bork your terminal. You can try sending reset
one or two times, or try with a different terminal (Konsole worked for me).