Git stash clear and drop are not really clearing the changes

353 views Asked by At

Have you ever tried these steps with git stash?

touch file1
git add file1
git stash
cat .git/refs/stash # ==> copy the content, which is the latest stashed <sha_1>
git stash clear # ==> we expect that git forget everything about that changes
git stash apply <sha_1>

As you can see, git somehow still knows about my stashed changes!
Is there anyway to remove the changes permanently? Is this a bug?

2

There are 2 answers

1
Marcin Kłopotek On BEST ANSWER

When you use git stash, the changes are stored as objects within the Git object database (much like commits), which is how they can be reapplied later. The stash reference is merely a pointer to these objects. When you clear the stash using git stash clear, the reference is removed but the objects are not immediately deleted. They will eventually be garbage collected when Git performs its periodic housekeeping, but until then they can still be accessed directly if you know their SHA-1 hash, the documentation is clear about that:

git clear

Remove all the stash entries. Note that those entries will then be subject to pruning, and may be impossible to recover [...]

This is not a bug but a feature of Git. Git is designed to be very careful about not losing data. Even when you think you've deleted something (like a commit, a branch, or a stash), Git often still keeps it around just in case you made a mistake. These "deleted" items can often be recovered until Git's garbage collection process eventually cleans them up.

If you want to manually trigger Git's garbage collection process, you can do so with the git gc command. This will clean up unreachable or "orphaned" objects in the database. Be aware, though, that this can be a resource-intensive process if you have a large repository.

Here's how to use it:

git gc --prune=now

The --prune=now option tells Git to prune all orphaned objects immediately. Without this, Git will only prune objects that are older than two weeks by default.

Please remember that once the garbage collection process removes these objects, they are gone permanently and cannot be recovered. So, only use this if you're certain that you won't need to recover any of these orphaned objects.

0
Guildenstern On

Create a stash without clearing it (yet):

cd /tmp
dir=$(mktemp -d)
cd $dir
git init
git commit --allow-empty -mInit
touch file1
git add file1
git stash
sha1=$(cat .git/refs/stash)

Status:

$ git stash list
stash@{0}: WIP on main: 8f0d468 Init

As expected.

Now clear it:

$ git stash clear
$ # empty
$ git stash list
$ git stash apply $sha1
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    new file:   file1

So what happened? The stash dropped it (it’s not in the list) but Git itself (the database) hasn’t forgotten about it.

Once you did:

git stash clear

The commit ($sha1) became unreachable, which means that it would eventually be subject to garbage collection. But garbage collection only happens from time to time and an object has to be unreachable for a good while before it becomes eligible for deletion.

We can find the dropped stash commit by using:

$ git fsck --unreachable | grep $sha1
Checking object directories: 100% (256/256), done.
unreachable commit 389cbdbec3cb3a3ebaaf61cb0eac4725e5c78462

The object exists. Which means that git apply $sha1 will happily accept it, since git apply doesn’t care where the object came from as long as it exists and is valid.