Repository navigation
Expand file tree
/
Copy pathGitLabRepository.php
More file actions
148 lines (124 loc) · 5.14 KB
/
Copy pathGitLabRepository.php
File metadata and controls
148 lines (124 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<?php
declare(strict_types=1);
namespace Internal\DLoad\Module\Repository\Internal\GitLab;
use Internal\Destroy\Destroyable;
use Internal\DLoad\Module\Registry\RepositoryId;
use Internal\DLoad\Module\Registry\VersionRegistry;
use Internal\DLoad\Module\Repository\Collection\ReleasesCollection;
use Internal\DLoad\Module\Repository\Exception\RateLimitException;
use Internal\DLoad\Module\Repository\Internal\GitLab\Api\RepositoryApi;
use Internal\DLoad\Module\Repository\Internal\PagedReleaseSource;
use Internal\DLoad\Module\Repository\Internal\Paginator;
use Internal\DLoad\Module\Repository\Repository;
use Internal\DLoad\Service\Logger;
/**
* GitLab Repository class representing a GitLab repository.
*
* @internal
* @psalm-internal Internal\DLoad\Module\Repository\Internal\GitLab
*/
final class GitLabRepository implements Repository, Destroyable
{
/** Repository type identifier in the version registry. */
public const TYPE = 'gitlab';
private ?ReleasesCollection $releases = null;
/**
* Package name in format "owner/repository"
*
* @var non-empty-string
*/
private readonly string $name;
/**
* @param non-empty-string $projectPath
* @param non-empty-string|null $server Host and port of a self-hosted GitLab, null for the public GitLab.
*/
public function __construct(
private readonly RepositoryApi $api,
string $projectPath,
private readonly Logger $logger,
private readonly VersionRegistry $registry,
private readonly string $tagPrefix = '',
private readonly ?string $server = null,
) {
$this->name = $projectPath;
}
/**
* Returns a lazily loaded collection of repository releases.
*
* Releases come from the version registry, which serves stored ones without a request and
* asks the API only for what it does not know yet. Pages are loaded only when needed during
* iteration or filtering.
*/
public function getReleases(): ReleasesCollection
{
if ($this->releases !== null) {
return $this->releases;
}
// Create a generator function for lazy loading release pages
$pageLoader = function (): \Generator {
// to avoid first eager loading because of generator
yield [];
$pages = $this->registry->releases(
$this->getId(),
new PagedReleaseSource($this->api->releasePages(...), RepositoryApi::RELEASES_PER_PAGE),
);
$anyPageLoaded = false;
while (true) {
try {
// Advancing the generator is what requests the next page
$anyPageLoaded ? $pages->next() : $pages->rewind();
if (!$pages->valid()) {
return;
}
$toYield = [];
foreach ($pages->current() ?? [] as $record) {
// Releases of other components sharing the repository
if (!\str_starts_with($record->tag, $this->tagPrefix)) {
continue;
}
try {
$toYield[] = GitLabRelease::fromRecord($this->api, $this, $record, $this->tagPrefix);
} catch (\Throwable) {
// Skip invalid releases
continue;
}
}
$anyPageLoaded = true;
yield $toYield;
} catch (\Throwable $e) {
# The first page is mandatory: when it fails, there is nothing to download and the reason
# (invalid token, rate limit, missing project, etc.) must reach the user.
$anyPageLoaded or throw $e;
# A rate limit leaves the release list incomplete: hiding it would produce a report
# that claims the repository has nothing more, so it must reach the user as well.
$e instanceof RateLimitException and throw $e;
# Already loaded releases are enough to continue, so a failure of a subsequent page
# only stops the pagination.
$this->logger->exception($e, important: false);
return;
}
}
};
// Create paginator
$paginator = Paginator::createFromGenerator($pageLoader(), null);
// Create a collection with the paginator
$this->releases = ReleasesCollection::create($paginator);
return $this->releases;
}
public function getName(): string
{
return $this->name;
}
public function getId(): RepositoryId
{
return new RepositoryId(self::TYPE, $this->name, $this->server);
}
public function destroy(): void
{
// Only what was loaded is released: iterating the collection would request the remaining pages
foreach ($this->releases?->loaded() ?? [] as $release) {
$release instanceof Destroyable and $release->destroy();
}
unset($this->releases);
}
}