Skip to content

Housekeeping: remove the old dist/ binaries from git history (3 steps, spread over ~2 releases) #173

Description

@mario4tier

Why

Until 2026-08-05 every nightly committed the built packages (.deb / .msi / .zip /
.tar.gz) into dist/. They compress to ~0%, so each nightly added permanently unique
bytes to the history.

Measured on the full object graph:

size blobs
dist/*.{deb,msi,zip,tar.gz} 1.1 GiB — 93.4% 925
all other files in the whole history 82 MiB 43,492

274 commits touch them, spanning 2024-11-16 to 2026-08-05. The packed repo is 1.2 GiB, so
a clone is ~12x bigger than the project actually is. That is now a real cost: any workflow
using fetch-depth: 0 clones the whole thing (website.yml, dist-pool-gc.yml).

Going forward this is already fixed — the globs are in .gitignore and the packages
live in the ci-build-pool release-asset pool instead. This issue is only about the
binaries already recorded in history.

Why we can't just delete them all at once

Stripping a file from a commit changes that commit's SHA, which changes every descendant,
which moves the tags. That matters because vcpkg pins the SHA512 of GitHub's
auto-generated tag archive
:

# microsoft/vcpkg ports/talib/portfile.cmake   (note: the port is "talib", not "ta-lib")
vcpkg_from_github(REPO "TA-Lib/ta-lib" REF "v${VERSION}"
                  SHA512 c8b9daf922cc98119e96a5bdb54187669e2f26be…)

That hash is of https://github.com/TA-Lib/ta-lib/archive/v0.7.1.tar.gz, fetched live from
GitHub. And GitHub embeds the commit SHA in the tarball's pax header:

v0.7.1 archive contains:  comment=2247d599bddf37ed37e3a709371517e46efc66f6

So the archive hash changes if the commit SHA changes — even when the file tree is
byte-for-byte identical. Move the v0.7.1 tag and every vcpkg install talib fails with
File does not have the expected hash, immediately, on all baselines, until a PR lands in
microsoft/vcpkg. That outage is measured in days-to-weeks of their review queue, not ours.

vcpkg's version database is append-only, so historical entries (0.6.4) can never be
repaired at all.

What each downstream packager pins, checked 2026-08-06:

packager pins affected by a tag move?
vcpkg 0.7.1 GitHub tag archive yes — fixable by a PR
vcpkg 0.6.4 GitHub tag archive yes — not fixable, ever
vcpkg 0.4.0 SourceForge ta-lib-0.4.0-msvc.zip no
Homebrew release asset ta-lib-0.7.1-src.tar.gz no
conda-forge tracks ta-lib-python, different repo no

Homebrew is immune precisely because it downloads an uploaded release asset rather than a
generated tag archive. That is the fix for step 2.

The plan

Step 1 — strip only the history after v0.7.1 ✅ DONE 2026-08-06

Rewrite the 569 commits on dev (568 on main) that are descendants of
v0.7.1 (2247d599bddf37ed37e3a709371517e46efc66f6, 2026-07-03). Everything reachable
from v0.7.1 keeps its exact SHA.

The direction is forced, not a preference: a commit's SHA covers its ancestry, so stripping
anything below v0.7.1 would move v0.7.1 itself — which is the outage we're avoiding.

Because v0.7.1's ancestry subsumes every older tag, this leaves all of them untouched:

  • v0.4.0v0.6.4, reference-pre-cutover (0d6b0e649, 2026-06-28) — all ancestors of
    v0.7.1, no retagging, no downstream impact
  • the two frozen-oracle worktrees keep working as-is, same SHAs:
    ta-lib-064 @ 43f9d5042 (in v0.6.4) and ta-lib-ref @ 0d6b0e649
  • ci-build-pool @ d5593923f (2026-08-05) is the only tag that moves. It is the
    internal asset-pool pre-release, it moves weekly anyway, and dist-pool-gc.yml reads
    origin/dev + origin/main rather than the tag's tree, so retagging it changes nothing
    functionally. Moving a tag does not delete the release, so no watcher notification.

Recovers 782 MiB. Repo goes 1.2 GiB → ~430 MiB.

Every commit, message, author and date is preserved — only the binary blobs leave.

Step 2 — at the next release, get vcpkg off the tag archive

Submit a microsoft/vcpkg PR switching ports/talib/portfile.cmake from vcpkg_from_github
to vcpkg_download_distfile against the uploaded release asset
(releases/download/vX.Y.Z/ta-lib-X.Y.Z-src.tar.gz) — the same thing Homebrew already does.

Worth doing on its own merits: it permanently removes GitHub's tag-archive instability from
the release path. GitHub has changed archive generation before and forced mass re-hashing
across vcpkg ports.

Step 3 — much later, strip the remaining 344 MiB

Once ~2 releases have shipped with a vcpkg port that does not pin a tag archive, rewrite
the old tags too and recover the last 344 MiB (repo → ~100 MiB).

By then the only people who could notice are those pinned to a vcpkg baseline old enough to
still resolve 0.6.4 — which by that point is years stale. Deliberately deferred so the blast
radius is as close to zero as we can make it.

Do not start step 3 before step 2 has been live for two releases. That wait is the whole
point of splitting this up.

Server-side gc: one request, at the end of step 3

A local rewrite does not shrink GitHub. Old objects stay reachable there, and clearing them
needs a request to GitHub Support asking them to run gc — that is their documented route,
not a workaround. Deliberately deferred to after step 3: step 3 produces a second batch of
unreachable objects, so one request at the end covers both. Nothing is lost by waiting —
clone size, which is what CI and contributors actually feel, is already fixed by step 1.

The payoff is capped, whenever it is filed. refs/pull/* refs are permanent and GitHub
does not delete them, so every commit that was ever a PR head stays reachable forever.
Measured 2026-08-06: 39 PR refs, and 11 of the 19 whose objects are still available locally
carry dist/ binaries in their trees. refs/pull/90/head is a worked example — the
docs/rust-crate-no-std branch was deleted during step 1 and the commit is already pruned
locally, yet GitHub still pins it.

So expect GitHub's stored size to land somewhere above the local figure even after step 3
plus a successful gc. Ten of those PR heads sit inside v0.7.1's ancestry, which step 3
strips from the branches but cannot strip from the PR refs. That is a known, accepted
ceiling — not a sign the rewrite failed.

Do not try to reduce the PR refs — it is not possible and not worth effort. They are
server-maintained and read-only: git push cannot delete them, closing a PR does not remove
one, and neither does deleting the source branch (PR #90 again). 29 of the 39 come from
contributor forks, which share an object store with upstream in GitHub's fork network, so
those commits survive through the fork regardless.

And it costs us nothing: git clone does not fetch refs/pull/* — only refs/heads/*
and refs/tags/*. Measured during step 1: 20 of the 39 PR heads are absent from a fresh
local clone. PR refs are invisible to contributors and to CI; they exist only in GitHub's
internal storage accounting. Worth one sentence in the Support ticket (Support can purge
them, unlike us) and no effort beyond that.

Progress

  • Step 1 — strip v0.7.1..dev and v0.7.1..main — done 2026-08-06,
    1.20 GiB → 423.79 MiB. dev 20560551b, main 671b5089a.
  • Step 2 — vcpkg port onto the release asset — done 2026-09-16, merged in the
    0.8.1 port update ([talib] update to 0.8.1 microsoft/vcpkg#53896). talib 0.8.1 installs from
    ta-lib-0.8.1-src.tar.gz, so no published version pins a tag archive from here on.
  • Step 3 — strip the remaining old tags (after 2 releases on step 2) ⬅ next, earliest after 0.8.3

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions