Skip to content

fix: request every releases page once and cache release listings - #118

Open
xepozz wants to merge 3 commits into
1.xfrom
fix/releases
Open

fix: request every releases page once and cache release listings#118
xepozz wants to merge 3 commits into
1.xfrom
fix/releases

Conversation

@xepozz

@xepozz xepozz commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Problem

The release page loader built a new Paginator for every page:

$paginator = $this->api->getReleases(++$page);
$releases = $paginator->getPageItems();
// ...
$hasMorePages = $paginator->getNextPage() !== null;

getNextPage() performs the request for the next page, and the next iteration then built a fresh
paginator for that same page and requested it again. Every page but the first was fetched twice,
and each duplicate costs a GitHub or GitLab API rate limit unit.

Changes

  • Every page is requested once. The loader keeps the paginator and asks it for the next page,
    because asking for the next page IS the request for it. Applies to both GitHubRepository and
    GitLabRepository.
  • per_page=100. GitHub serves 30 releases per page by default and GitLab 20; both allow 100.
    A repository with 100 releases now costs one request instead of four (GitHub) or five (GitLab).
  • Release listings can be cached between runs. A new ResponseCache wraps the listing request
    only — asset downloads share the same client and must never be written to the cache directory.
    Caching is off until cache-dir / DLOAD_CACHE_DIR is set; cache-ttl / DLOAD_CACHE_TTL
    defaults to 600 seconds and 0 disables it.

Only successful responses are stored, so a rate limit answer is not replayed after the limit is
gone. Entry age comes from the stored timestamp and not the file mtime, because a restored CI cache
writes the files with a fresh mtime and mtime-based entries would never expire. Entries are written
aside and renamed into place, so an interrupted or parallel run cannot leave a half-written file.
Any cache failure is logged and swallowed: the cache is an optimisation and must not turn a working
download into a failed one.

Tests

  • GitHubRepositoryTest / GitLabRepositoryTest assert the exact pages requested ([1, 2, 3], not
    [1, 2, 2, 3, 3]), that pages stay lazy, that per_page=100 is sent, and that a second run with
    a carried-over cache directory sends no requests at all.
  • FileResponseCacheTest covers the link header surviving the round trip (pagination depends on
    it), TTL expiry, mtime independence, unsuccessful responses not being stored, and corrupted
    entries being ignored.
  • ResponseCacheBindingTest resolves the binding through a real bootstrap for the XML attribute,
    the environment variable, and the ttl=0 case.

composer test — 517 passed, 2 skipped. composer psalm — no errors. composer cs:diff — clean.

The page loader built a new paginator per page, so every page but the
first was requested twice. Ask the existing paginator for the next page
instead.

Releases are now requested 100 per page, and listings can be cached in a
directory between runs through the cache-dir and cache-ttl settings.
@github-actions github-actions Bot added bug Something isn't working tests labels Sep 12, 2026
@codecov

codecov Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.80220% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/Module/Cache/Internal/FileResponseCache.php 98.18% 1 Missing ⚠️
src/Module/Cache/Internal/NullResponseCache.php 50.00% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/Bootstrap.php 89.74% <100.00%> (ø)
src/Module/Cache/ResponseCache.php 0.00% <ø> (ø)
...e/Repository/Internal/GitHub/Api/RepositoryApi.php 52.45% <100.00%> (ø)
src/Module/Repository/Internal/GitHub/Factory.php 93.75% <100.00%> (ø)
...le/Repository/Internal/GitHub/GitHubRepository.php 57.14% <100.00%> (ø)
...e/Repository/Internal/GitLab/Api/RepositoryApi.php 51.61% <100.00%> (ø)
src/Module/Repository/Internal/GitLab/Factory.php 91.66% <100.00%> (-8.34%) ⬇️
...le/Repository/Internal/GitLab/GitLabRepository.php 55.88% <100.00%> (ø)
src/Module/Cache/Internal/FileResponseCache.php 96.42% <98.18%> (ø)
src/Module/Cache/Internal/NullResponseCache.php 33.33% <50.00%> (ø)

... and 60 files with indirect coverage changes

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

Codecov reported 80% patch coverage. The store failure paths were never
run, and the release listing requests were attributed to no covering
class, so RepositoryApi showed 0%.

Add tests for an unreadable entry and for each of the three store
failures, declare RepositoryApi as covered by the repository tests, and
add the GitHub factory test that its GitLab counterpart already had.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant