How to Recover a Deleted File in Git – Revert Changes After a Hard Reset
As a full-stack developer, you‘re likely using Git every day to manage your code and collaborate with your team. Git is a powerful version control system that tracks changes to files over time, allowing you to revert to previous versions and undo mistakes.
But what happens when you accidentally delete an important file from your Git repository? Don‘t panic! In most cases, you can recover deleted files in Git, even after performing a hard reset. In this in-depth tutorial, we‘ll explore exactly how Git stores your data and the techniques you can use to get your lost files back.
How Git Version Control Works Under the Hood
Before we dive into file recovery methods, it‘s important to understand a bit about how Git works under the hood.
When you make changes to files in your working directory, Git doesn‘t automatically track those changes. Instead, you need to explicitly tell Git which changes you want to stage (with git add
) and then commit (with git commit
).
When you stage a change, Git creates a blob object that stores the file contents and a tree object that stores the directory structure and filenames. These objects are stored in Git‘s object database, which lives in the .git/objects
directory in your repo.
When you commit staged changes, Git creates a new commit object that contains a reference to the top-level tree of your repo at that point in time, as well as author, committer, and message metadata. Commits are the fundamental unit of history in Git. Each commit has a unique 40-character SHA-1 hash ID that identifies it.
As you continue making new commits, Git builds a directed acyclic graph (DAG) that represents the history of your project, with each commit pointing to its parent commit(s). Git branches are simply pointers to particular commits. The HEAD
ref points to the current branch pointer.
Common Ways Files Get Deleted in Git
Now that you have a basic mental model of how Git works, let‘s look at some common scenarios that can lead to accidental file deletion:
- You manually deleted a file in your working directory and committed that change
- You used
git rm
to delete a file and committed that change - You amended a previous commit and accidentally deleted a file
- You performed an interactive rebase and deleted a commit that contained a file
- You did a hard reset (e.g.
git reset --hard HEAD~1
) that removed commits
In scenarios 1-4, your deleted file is still present in a previous commit, so it‘s fairly straightforward to recover. Scenario 5 is trickier because a hard reset actually rewrites the commit history of your branch. But even then, your "lost" commits may still be accessible if you know where to look. Let‘s start with the easiest cases first.
Recovering Deleted Files That Were Committed
If your deleted file was previously committed to your Git repository, it still exists in Git‘s object database even if it‘s no longer present in the current HEAD
commit. You have two main options for recovering it:
Option 1: Use `git checkout`
The git checkout
command allows you to restore old versions of files by referencing a specific commit hash or branch name.
Let‘s say you accidentally deleted a file called important.txt
and committed that change. To recover the file, you can simply check out the version from the previous commit:
$ git checkout HEAD~1 important.txt
This will restore important.txt
to its state in the second-most-recent commit (HEAD~1
). You can then commit the restored file to integrate it back into your project history.
If you don‘t know the exact commit that last contained the deleted file, you can use git log
or git reflog
to search through the history. We‘ll cover git reflog
in more detail in the next section.
Option 2: Use `git reflog`
Every action you take in Git, such as committing, branching, merging, and resetting, creates a new entry in the reflog
. The reflog is a rolling log of all reference updates (e.g. HEAD
, branches) in your local repository.
You can view the reflog with:
$ git reflog
This will output a list of all recent actions along with their commit hashes:
a1b2c3d (HEAD -> main) HEAD@{0}: commit: Add new feature
b2c3d4e HEAD@{1}: reset: moving to HEAD~1
c3d4e5f HEAD@{2}: commit: Implement user login
d4e5f6g HEAD@{3}: commit: Initial commit
To recover a deleted file, simply find the commit hash of the commit where the file last existed and check it out with git checkout
:
$ git checkout c3d4e5f important.txt
This will restore important.txt
to its state in commit c3d4e5f
. You can then commit the restored file to reintegrate it into your project.
The git reflog
command is invaluable for recovering lost commits after a rebase or reset. However, note that the reflog only tracks updates to references, not individual file changes. Commits that were never referenced by a branch or tag will eventually be garbage collected and pruned from the reflog after an expiration period (90 days by default).
Recovering Staged but Uncommitted Files
What if you staged some changes with git add
but then did a hard reset before committing them? In this case, your changes still exist in Git‘s object database as "dangling blobs" but they‘re no longer referenced by any commit or tree.
To recover dangling blobs, you can use the git fsck
command:
$ git fsck --lost-found
This will output a list of all unreachable objects in your repository:
dangling blob f24facc98b387a375a50ba6d19193626cbfe7d45
dangling blob 2e85026a49c6b968de5432c5a17c4abe5b2e16ac
You can then use git show
to view the contents of a specific dangling blob:
$ git show f24facc98b387a375a50ba6d19193626cbfe7d45
If the blob contains the changes you‘re looking for, you can redirect the output to a new file:
$ git show f24facc98b387a375a50ba6d19193626cbfe7d45 > restored_file.txt
You can then stage and commit the restored file to reintegrate it into your project history.
While git fsck
can be helpful for recovering lost staged changes, it‘s important to note that it can also return a lot of noise in the form of unreachable objects that aren‘t actually valuable. Sifting through the output to find your lost changes can be tedious.
If Files Were Never Staged or Committed
In the unfortunate case where your deleted file was never staged or committed in Git, Git may not be able to help you recover it. Since the file was never added to Git‘s index or object database, there‘s no record of its contents for Git to restore.
However, all hope is not lost! Your file may still exist in temporary backups, application caches, or undo history. Here are a few places to check:
- Your IDE or text editor‘s local history and undo features
- Dropbox, Google Drive, or other cloud storage services that may have synced a copy
- Your operating system‘s trash or recycle bin
- File recovery software that can scan your disk for recently deleted files
In general, the sooner you attempt to recover an unstaged file after deleting it, the better your chances of success. Stop using the disk to avoid overwriting the deleted data.
Best Practices for Avoiding Data Loss with Git
While it‘s good to know how to recover lost files in Git, it‘s even better to proactively prevent accidental deletions in the first place! Here are some best practices to follow:
-
Commit early and often. Make frequent, granular commits to create a detailed project history that‘s easy to navigate and revert.
-
Use branches liberally. Create topic branches for each new feature or bug fix to avoid conflicts and accidental deletions on your main branch.
-
Perform a dry run before destructive operations like rebasing, force pushing, and resetting. Use
git log
andgit diff
to double check which commits will be affected. -
Enable safety features like
git config --global push.default current
to avoid accidentally pushing the wrong branch, andgit config --global rebase.missingCommitsCheck error
to prevent accidental data loss during interactive rebases. -
Back up your repository regularly to a remote server or cloud hosting service. In the event of catastrophic local data loss, you‘ll have an off-site copy to restore from.
Conclusion
Accidentally deleting files is a common mistake that every developer makes from time to time. Fortunately, Git provides several techniques for recovering lost data, whether your files were fully committed, staged but not committed, or even if no trace remains in your repository.
By understanding how Git manages data under the hood and proactively adopting best practices, you can keep your work safe and avoid panicked scrambles to recover lost files. Remember, Git is a powerful tool – but with great power comes great responsibility to use it wisely!
I hope this in-depth guide to recovering deleted files in Git has been helpful. For more Git tips and best practices, check out my other articles. Happy coding!