You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fetch tags with a forced refspec, so the bare's tags are prunable and a moved tag stops wedging the fetch.
One word, measured on #453, and it fixes a live production bug as well as a straggler. In Git::fetch_all (rust/devlaunch-core/src/clients/git.rs:612-626), replace --tags with an explicit +refs/tags/*:refs/tags/*.
--tags is a non-forced refs/tags/*:refs/tags/*, and git fetch --prune prunes per refspec. Two consequences, both measured:
Tags are never pruned.refs/tags is monotone in every tag the remote has ever advertised, and those refs pin objects. A 20,971,520 B blob reachable only from an upstream-deleted tag survived git gc --prune=now at 0 B reclaimed; with the forced refspec, the tag went and 20,975,616 B came back, taking the probe repository from 21,094,400 B to 118,784 B.
A tag force-moved upstream wedges the fetch permanently.! [rejected] moving -> moving (would clobber existing tag), exit 1, repeatably, which RepoManager::fetch_repo maps to FetchRepoError::Refused. So freshness fetches stay broken for that repository until a human deletes the local tag by hand. With the forced refspec it is t [tag update], exit 0.
A test per effect: a tag deleted upstream is pruned locally, a tag force-moved upstream updates instead of failing, and the fetch's argv is pinned so the refspec cannot silently regress to --tags.
One principle-1 call to make explicitly rather than by implication, and to state in the resolution: pruning a tag deletes a ref. The reading that licenses it is that a tag in the bare is a copy of an upstream ref holding no unique work, which is the same argument that already licenses pruning heads. If that reading is accepted, say so; if not, this becomes a flagged path rather than automatic.
Also in scope because #453 found it in the same place: --prune removes refs/heads/main when main disappears upstream, leaving the bare's HEAD symref dangling, which Git::symbolic_ref reads for default-branch detection. Either handle it or record it as a known consequence with the symptom named.
breadcrumb: claimed. #471 is merged (e55239a), so main now carries the widened unpushed probe. Note the interaction: that probe asks --all --not --remotes, which spans refs/tags, and #453 measured that the bare keeps tags upstream has deleted. Forcing the tag refspec changes what a bare holds, so check whether it moves the false positive #471 took deliberately.
breadcrumb: seams, and red first. The behavioural pair is tested at the repository-manager fetch, against a real remote on a local path, because that is the level the wedge is reported at (Refused) rather than merely caused at; the refspec itself is pinned as argv at the git client, where every other verb already is. Red quotes git: ! [rejected] release -> release (would clobber existing tag), and the head pushed alongside it did not arrive either, which is worth saying out loud because a rejected tag fails the whole fetch, not just the tag.
Principle 1, stated rather than implied: a tag in the bare is a copy of an upstream ref and holds no work that exists nowhere else, so pruning it is licensed by the same reading that already licenses pruning heads. Not a flagged path.
breadcrumb: built, PR #476. Git::fetch_all now asks for both refspecs spelled out and both forced, so a moved tag is t [tag update] instead of a permanently Refused fetch, and a retracted tag is pruned like a head. Three argv pins moved with it, and the pin now carries its reason so --tags cannot come back quietly.
The #471 interaction, measured rather than reasoned about: this narrows the false positive and cannot widen it. Built the case both ways. Under the old refspec, a tag on a branch the remote later deleted survives in the bare, and the probe in a clone cut from that bare reports a phantom unpushed commit, in that clone and in every later one. Under the forced refspec the tag is pruned and the probe is silent. What survives is only the half docs/cleanup.md already describes, a tag the remote still carries which no remote branch reaches, so the page reads correctly before and after. Widening is unreachable from here: forcing only moves a local tag onto what upstream advertises and pruning only removes what it no longer advertises, so no commit becomes clone-only that was not already. #471's trade is deliberately untouched; it is decided on a class this change does not reach, and reversing it is #475's.
The HEAD-dangling edge #453 found in the same place is recorded, not handled, with the symptom named where the refspec is: the symref still answers a branch name, so rev-parse is what fails and the failure surfaces later as a launch aimed at a branch the cache no longer has. Handling it means deciding what a bare should point HEAD at when upstream has no default branch, which is a decision rather than a fix.
breadcrumb: gate passed. PR #476, head 13072989041e0a1a7aa3dd100317c253ae2a803a, 15 checks green and MERGEABLE. Handing to a fresh reviewer who did not write it, with the tag-prune deletion (a ref removed automatically, unflagged) as the thing to press hardest on.
breadcrumb: reviewed, Approve, nothing blocking. Two-axis report on #476. Principle 1 re-measured from scratch: one production caller pointing at the bare alone, no tag verb anywhere in devlaunch, and an existing clone keeps the overwritten tag's objects through a bare gc --prune=now. #471's false positive narrows as claimed, with one correction for the record: "cannot widen" should read "narrows in every reachable non-wedged state" (a tag force-moved onto a commit upstream then orphans widens it, but only relative to a permanently failing fetch, and in the keep-the-clone direction). Two comment-only findings: repo_manager.rs:3551's "principle 1" resolves to nothing in-tree, and git.rs:566's rev-parse HEAD exits 0 (it is --verify that fails).
Activity
blooop commentedon Aug 25, 2026
breadcrumb: claimed. #471 is merged (
e55239a), somainnow carries the widened unpushed probe. Note the interaction: that probe asks--all --not --remotes, which spansrefs/tags, and #453 measured that the bare keeps tags upstream has deleted. Forcing the tag refspec changes what a bare holds, so check whether it moves the false positive #471 took deliberately.blooop commentedon Aug 25, 2026
breadcrumb: seams, and red first. The behavioural pair is tested at the repository-manager fetch, against a real remote on a local path, because that is the level the wedge is reported at (
Refused) rather than merely caused at; the refspec itself is pinned as argv at the git client, where every other verb already is. Red quotes git:! [rejected] release -> release (would clobber existing tag), and the head pushed alongside it did not arrive either, which is worth saying out loud because a rejected tag fails the whole fetch, not just the tag.Principle 1, stated rather than implied: a tag in the bare is a copy of an upstream ref and holds no work that exists nowhere else, so pruning it is licensed by the same reading that already licenses pruning heads. Not a flagged path.
blooop commentedon Aug 25, 2026
breadcrumb: built, PR #476.
Git::fetch_allnow asks for both refspecs spelled out and both forced, so a moved tag ist [tag update]instead of a permanentlyRefusedfetch, and a retracted tag is pruned like a head. Three argv pins moved with it, and the pin now carries its reason so--tagscannot come back quietly.The #471 interaction, measured rather than reasoned about: this narrows the false positive and cannot widen it. Built the case both ways. Under the old refspec, a tag on a branch the remote later deleted survives in the bare, and the probe in a clone cut from that bare reports a phantom unpushed commit, in that clone and in every later one. Under the forced refspec the tag is pruned and the probe is silent. What survives is only the half
docs/cleanup.mdalready describes, a tag the remote still carries which no remote branch reaches, so the page reads correctly before and after. Widening is unreachable from here: forcing only moves a local tag onto what upstream advertises and pruning only removes what it no longer advertises, so no commit becomes clone-only that was not already. #471's trade is deliberately untouched; it is decided on a class this change does not reach, and reversing it is #475's.The
HEAD-dangling edge #453 found in the same place is recorded, not handled, with the symptom named where the refspec is: the symref still answers a branch name, sorev-parseis what fails and the failure surfaces later as a launch aimed at a branch the cache no longer has. Handling it means deciding what a bare should pointHEADat when upstream has no default branch, which is a decision rather than a fix.blooop commentedon Aug 25, 2026
breadcrumb: gate passed. PR #476, head
13072989041e0a1a7aa3dd100317c253ae2a803a, 15 checks green and MERGEABLE. Handing to a fresh reviewer who did not write it, with the tag-prune deletion (a ref removed automatically, unflagged) as the thing to press hardest on.blooop commentedon Aug 25, 2026
breadcrumb: reviewed, Approve, nothing blocking. Two-axis report on #476. Principle 1 re-measured from scratch: one production caller pointing at the bare alone, no
tagverb anywhere in devlaunch, and an existing clone keeps the overwritten tag's objects through a baregc --prune=now. #471's false positive narrows as claimed, with one correction for the record: "cannot widen" should read "narrows in every reachable non-wedged state" (a tag force-moved onto a commit upstream then orphans widens it, but only relative to a permanently failing fetch, and in the keep-the-clone direction). Two comment-only findings:repo_manager.rs:3551's "principle 1" resolves to nothing in-tree, andgit.rs:566'srev-parse HEADexits 0 (it is--verifythat fails).