How could I delete all stashes older than X days?
I use git stash
in my everyday workflow, which means I end up with months of old stashes.
git reflog expire
manuallyThe underlying mechanism for "pushed" stashes—i.e., any stash whose name is not just stash
or stash@{0}
, but rather stash@{1}
, stash@{2}
, and so on—is Git's reflogs. The reflog records the previous value of a reference, whenever the reference changes. (I'll define reference below, and complete the definition of the reflog there.)
Most reflog entries expire on their own, through the configurable gc.reflogExpire
and gc.reflogExpireUnreachable
settings (see both the git config
documentation and the git reflog
documentation). The refs/stash
reference, however, is special: by default, its entries never expire, rather than expiring after 30 or 90 days.
You can run git reflog expire
manually and override any of these. For instance:
git reflog expire --expire-unreachable=40.days refs/stash
tells Git to expire any stash entries, all of which are always unreachable (see below for details), that are at least 40 days old. Add --dry-run
to the options to see which ones would expire, without actually expiring them ... though there is a minor flaw here: it does not print their numbers, i.e., it never says that it's going to toss, say, stash@{17}
.
Git's references are a generalization of branch names, tag names, remote-tracking branch names, and all those other names that Git has. A reference simply translates a name—something like master
or v1.2
or stash
—to one of Git's internal hash IDs.
A branch name is just a reference whose full name starts with refs/heads/
. A tag name is just a reference whose full name starts with refs/tags/
. A remote-tracking branch name is a reference whose full name starts with refs/remotes/
(and then has the name of the remote and another slash).
Some reference names, particularly branches, change quite often. For instance, your master
branch, which is really refs/heads/master
, changes every time you add a new commit to master
. Whenever Git replaces the stored hash value for a reference, it may—depending on whether reflogs are turned on—save the previous hash value. These saved entries are your reflogs.
Each reflog entry has a time stamp. Reflog entries eventually age out and get expired, so that you do not wind up with thousands or millions of reflog entries.
refs/stash
Branch names normally move in a forward fashion, e.g., one commit at a time. That is, we add new commits to a branch, and all the old commits that were on that branch are still on the branch. Sometimes we will pick up commits en-masse and add them all at once, in a "fast" forward fashion: all the new commits are on the branch, and all the old commits are also still on the branch. The branch name points to the tip commit on the branch, the right-most o
in this diagram:
...--o--o--o <-- branch
We add more commits, and the branch name still points to the tip:
...--o--o--o--o--o--o <-- branch
but since each commit points back to its parent commit, all these commits are reachable.
Sometimes, though, we deliberately remove a commit, and replace it with another. For instance, if we have commits we have not pushed, we can use git commit --amend
or git rebase -i
to make some changes. When we do that, the old ones don't go away just get. Instead, they get shoved to the side:
...--o--o--X <-- branch
becomes:
X ...... branch@{1}
/
...--o--o--Y <-- branch
where Y
is our replacement for X
.
Note that the parent commit of Y
is not X
, but rather X
's parent. This means commit X
is not reachable from Y
.
The two separate "reflogExpire" configuration items, for reachable and unreachable objects, refer to commits that can be found by starting at the current value of the reference—for our branch named branch
, that's refs/heads/branch
—and working backwards. Commit Y
is reachable, and so are the o
s, but X
is not. If there is a reflog entry pointing to one of the earlier o
s, it's reachable, but branch@{1}
points to X
which is unreachable.
Git's designers essentially believe that unreachable commits are less worthy, so their reflog entries should expire sooner. Thus, the default is 30 days for unreachable entries, and 90 days for the reachable ones.
The refs/stash
reference, though, does not advance normally at all. Instead, it points to the current stash, which is a sort of bag-on-the-side: a commit—actually at least two commits and sometimes more—that is not on any branch (I call this a "stash bag"; see How to recover from "git stash save --all"?). This, in turn, means that no previous stash@{n}
is reachable from the current refs/stash
, ever! Every stash reflog entry is therefore unreachable at all times.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With