diff --git a/README.md b/README.md index 4b55110..dff0b21 100644 --- a/README.md +++ b/README.md @@ -278,15 +278,29 @@ the obvious order, because a snapshot is hardlinked to everything cloned from it, so removing one frees almost no real bytes while costing every future PR its warm start. -**A branch is dead in two ways, and the second is the one that reclaims -anything here.** Gitea keeps a PR's branch after the merge unless the repo -opts into delete-on-merge, so `git ls-remote` reports merged branches forever -and "gone from origin" fires for almost nothing. The second signal is +**A branch is dead in two ways, and neither signal makes the other +redundant.** The first is that the branch is gone from origin. The second is ancestry: a branch still on origin whose tip is an ancestor of a protected branch's tip holds no commit that branch does not, so its cache will never be -read again and goes in the same pass. On zemyna's volume that is the -difference between reclaiming nothing and reclaiming a 40 GB directory per -merged PR (gitdan-actions#20). +read again and goes in the same pass. + +**Turn delete-on-merge on** (`default_delete_branch_after_merge`, per repo) — +it is the setting this scheme is cheapest under, because a deleted branch is +decidable from `ls-remote` alone, with no checkout, no objects and no walk. +The ancestry signal is what covers the rest, and the rest is not a corner: + +- **Every branch merged before the setting was turned on.** They stay on + origin forever; nothing retroactively deletes them. zemyna carried 48 of + them at the time the setting was enabled, and ancestry is the only thing + that reclaims a cache dir belonging to any of them. +- **Every merge the deletion declines or fails.** Gitea's delete is + best-effort and silent: it declines for a protected branch and for one + another open PR still uses, and an API merge that omits the flag — which + `tea pulls merge` does — simply never asks. +- **Repos that have not enabled it**, which is the default. + +That is the difference between reclaiming nothing and reclaiming a 40 GB +directory per merged PR on a full volume (issue 20). Ancestry is answered from the commits in the job's own checkout, so it is only answered where they are there to answer it — and "cannot tell" is never folded diff --git a/scripts/prune-cache-selftest.sh b/scripts/prune-cache-selftest.sh index ff5cca8..e83e239 100755 --- a/scripts/prune-cache-selftest.sh +++ b/scripts/prune-cache-selftest.sh @@ -46,10 +46,10 @@ # empty a tree its owner may still restore under a live cache name. Its # fixture is an OLD directory renamed a moment ago — production's shape, # and what lets it tell the two timestamps apart. -# 15. A MERGED-BUT-UNDELETED BRANCH IS DEAD TOO. This forge keeps branches -# after merge, so `ls-remote` reports them forever and scenario 1's -# signal never fires for them — which is how three 40 GB caches sat on a -# full volume until somebody removed them by hand (gitdan-actions#20). A +# 15. A MERGED-BUT-UNDELETED BRANCH IS DEAD TOO. A branch the forge did not +# delete at merge stays on `ls-remote` forever, so scenario 1's signal +# never fires for it — which is how three 40 GB caches sat on a full +# volume until somebody removed them by hand (gitdan-actions#20). A # branch whose tip is an ancestor of a protected branch's tip is pruned # like a deleted one; an unmerged branch beside it is not. # 16. AND "CANNOT TELL" IS STILL NOT DEATH, at both granularities: a branch @@ -314,7 +314,7 @@ assert_log "may still be evicting it" "the deferral gives its actual reason" echo echo "=== 15: a merged-but-undeleted branch is dead too ===" -# This forge keeps a PR's branch after the merge, so `ls-remote` reports it +# A branch the forge did not delete at merge, which `ls-remote` then reports # forever. Built the way that happens: a branch merged into dev with a merge # commit, still pushed, beside one branched at the same point and NOT merged. git="git -c user.email=t@t -c user.name=t -c commit.gpgsign=false" diff --git a/scripts/prune-cache.sh b/scripts/prune-cache.sh index 7e1827c..bf20f4e 100755 --- a/scripts/prune-cache.sh +++ b/scripts/prune-cache.sh @@ -28,8 +28,9 @@ # removed UNCONDITIONALLY, not gated on free space. A directory for a # branch nothing will build again is pure loss; waiting for disk pressure # to notice means paying for it until then. Two signals make a branch -# dead, and the second exists because the first alone is inert on a forge -# that keeps branches after merge (gitdan-actions#20): +# dead, and the second exists because the first alone is inert wherever +# a merged branch stays on origin — the default, and still the outcome +# whenever delete-on-merge declines or is not asked (gitdan-actions#20): # # DELETED — the branch is no longer on origin at all. # MERGED — the branch is still on origin, but its tip is an ancestor @@ -434,8 +435,11 @@ fi # # True when the branch this directory belongs to is still on origin but every # commit it holds is already on a protected branch — a merged PR whose branch -# the forge did not delete, which is the case zemyna hits on every merge and -# which the deleted-branch signal above can never see. +# the forge did not delete, which the deleted-branch signal above can never +# see. Enabling delete-on-merge narrows this to the branches merged before it +# was enabled, the ones its deletion declines (protected, or used by another +# open PR), and the merges that never ask (an API merge without the flag); +# see README's eviction section. # # Memoised per tip because target- and snapshot- share one branch, # and because the "cannot tell" warning belongs to the branch rather than to