Skip to content

feat: version registry that keeps release lists between runs - #119

Open
roxblnfk wants to merge 2 commits into
1.xfrom
feat/version-registry
Open

feat: version registry that keeps release lists between runs#119
roxblnfk wants to merge 2 commits into
1.xfrom
feat/version-registry

Conversation

@roxblnfk

Copy link
Copy Markdown
Member

🔍 What was changed

DLoad now keeps a local version registry: a provider-neutral database of the releases and assets of every known repository, one readable JSON file per repository under the per-user cache directory (cache-dir / DLOAD_CACHE_DIR to move it, cache-ttl=0 to disable).

  • Versions never expire. Only the last check of a repository has a TTL (cache-ttl, default 600 s); within it dload get makes no API request at all.
  • Release pages stay lazy: the first run fetches only the pages the requested version needs, older releases are loaded on demand later, and a stale check fetches only the pages with releases newer than the stored ones.
  • New dload get --refresh ignores the TTL once; new dload cache:clear [software...] drops the whole registry or the repositories a software package is served from.
  • A release deleted upstream after it was stored no longer breaks the run: its assets answer with a clear AssetNotFoundException instead of "repository not found, check your token", the release is dropped from the registry, and the list is fetched again in the same run before giving up.
  • Every releases page is now requested once with per_page=100, and destroy() no longer iterates the lazy collection, which used to load every remaining page after each download.

How it works

  • Check: when the last check is older than the TTL (or --refresh), the newest pages are fetched until one contains a stored release; the first page always overwrites what is stored, so assets attached later are picked up.
  • Serve: stored releases are yielded without a request; a failed check with stored releases falls back to them.
  • Extend: when the consumer runs past the stored releases and the list is not marked complete, older pages are fetched by offset and appended to the record.

Why?

Every run asked GitHub or GitLab for the release list, and every request counted against the API rate limit; unauthenticated CI matrices hit the 60 requests/hour limit quickly, and nothing was remembered between runs. Caching raw HTTP responses (#118) would have kept credentials-bound answers keyed by URL; storing release metadata keeps only tags, names and download links, so the directory can be shared between machines or a CI cache freely.

Checklist

  • Supersedes fix: request every releases page once and cache release listings #118
  • How was this tested:
    • Tested manually: against dolthub/dolt (644 releases) the first run makes one API request, the second run none, dolt:~1.20.0 loads the tail on demand, and a registry entry with poisoned asset links is dropped and the next release is downloaded
    • Unit tests added
    • composer test, composer psalm, composer cs:diff pass locally

Documentation

README (and the ru/es/zh translations) gained a "Version Registry" section; dload.xsd documents cache-dir and cache-ttl.

@github-actions github-actions Bot added enhancement New feature or request tests labels Sep 12, 2026
@codecov

codecov Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.74747% with 100 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...e/Repository/Internal/GitHub/Api/RepositoryApi.php 0.00% 29 Missing ⚠️
...e/Repository/Internal/GitLab/Api/RepositoryApi.php 0.00% 28 Missing ⚠️
...itory/Internal/GitHub/Api/Response/ReleaseInfo.php 0.00% 6 Missing ⚠️
...itory/Internal/GitLab/Api/Response/ReleaseInfo.php 0.00% 6 Missing ⚠️
...odule/Repository/Internal/GitHub/GitHubRelease.php 0.00% 5 Missing ⚠️
...odule/Repository/Internal/GitLab/GitLabRelease.php 0.00% 5 Missing ⚠️
...c/Module/Registry/Internal/FileRegistryStorage.php 90.47% 4 Missing ⚠️
...c/Module/Registry/Internal/PassThroughRegistry.php 0.00% 4 Missing ⚠️
src/Module/Registry/Record/RepositoryRecord.php 94.82% 3 Missing ⚠️
src/Module/Registry/RepositoryId.php 50.00% 3 Missing ⚠️
... and 6 more
Files with missing lines Coverage Δ
src/Bootstrap.php 92.15% <100.00%> (ø)
src/Module/Downloader/Exception/ReleaseGone.php 0.00% <ø> (ø)
src/Module/Registry/Record/AssetRecord.php 94.11% <100.00%> (ø)
src/Module/Registry/Record/ReleaseRecord.php 95.00% <100.00%> (ø)
...le/Repository/Exception/AssetNotFoundException.php 0.00% <ø> (ø)
...pository/Internal/GitHub/Api/ResponseValidator.php 69.23% <100.00%> (-12.59%) ⬇️
...Repository/Internal/GitHub/GitHubReleaseSource.php 88.88% <100.00%> (ø)
...le/Repository/Internal/GitHub/GitHubRepository.php 67.56% <100.00%> (ø)
...pository/Internal/GitLab/Api/ResponseValidator.php 85.71% <100.00%> (-5.96%) ⬇️
src/Module/Repository/Internal/GitLab/Factory.php 91.66% <100.00%> (-8.34%) ⬇️
... and 19 more

... and 58 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

roxblnfk and others added 2 commits September 13, 2026 00:16
fix: request every releases page once with `per_page=100`
fix: stop `destroy()` from loading the remaining release pages

Every run asked GitHub or GitLab for the release list and spent the API rate limit on it. Releases are now kept in a local version registry: a provider-neutral database of the releases and assets of every known repository, one JSON file per repository, on by default in the per-user cache directory.

Versions never expire; only the last check of a repository has a TTL (`cache-ttl`, default 600 s), within which `dload get` costs no API request. A stale check fetches only the newest pages until a stored release is reached. Pages stay lazy: the first run loads what the requested version needs, older releases are fetched on demand and appended. A failed check falls back to the stored releases. `dload get --refresh` ignores the TTL once, `dload cache:clear [software...]` drops records.

The page loader used to build a paginator per page and probe the next one, so every page but the first was requested twice; `destroy()` iterated the whole lazy collection and loaded every remaining page after each download.

Co-Authored-By: Dmitriy Derepko <xepozz@list.ru>
Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
…them

A 404 for a release asset was reported as a missing repository, with advice about tokens and addresses, and the deleted release stayed in the registry until a later check happened to overwrite it. Asset URLs now raise `AssetNotFoundException`; when every matching asset of a release is gone, the downloader drops the release from the registry, marks the repository for a check, and fetches the list once more before giving up.

Assisted-By: Claude Fable 5.1 <noreply@anthropic.com>
@roxblnfk
roxblnfk force-pushed the feat/version-registry branch from e85e96b to ea36d7e Compare September 12, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant