@foss.global/codefeed 3.2.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +24 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.giteasourceinventory.d.ts +32 -7
- package/dist_ts/classes.giteasourceinventory.js +65 -17
- package/dist_ts/classes.giteasourcetransfer.d.ts +26 -10
- package/dist_ts/classes.giteasourcetransfer.js +108 -46
- package/package.json +2 -2
- package/readme.md +11 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.giteasourceinventory.ts +64 -16
- package/ts/classes.giteasourcetransfer.ts +107 -44
|
@@ -52,16 +52,24 @@ export class GiteaSourceTransfer {
|
|
|
52
52
|
* the GitManager's data directory, in the process-wide `GitScratchBudget`,
|
|
53
53
|
* and the reservation is released once the clone has ended. While it runs
|
|
54
54
|
* the import holds `target`: writes to it through the GitManager fail there
|
|
55
|
-
* with `repositoryBusy`.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* `
|
|
60
|
-
*
|
|
61
|
-
*
|
|
55
|
+
* with `repositoryBusy`. Git clones by name, so the repository `id` is
|
|
56
|
+
* read by id both before the clone and after it, and must still be named
|
|
57
|
+
* `owner/name` (compared without case, as Gitea looks names up): a rename,
|
|
58
|
+
* a transfer or a rename and re-create rejects with
|
|
59
|
+
* `repository-identity-changed`, a deleted repository with `http-404`, also
|
|
60
|
+
* when the clone itself failed. A rejection from the read after the clone
|
|
61
|
+
* leaves the clone at `target` for the caller to discard. Rejects with a
|
|
62
|
+
* fixed `ForgeInventoryError` code: `invalid-repository`, `invalid-target`,
|
|
63
|
+
* `gitmanager-not-running`, a REST code such as `http-404`,
|
|
64
|
+
* `repository-identity-changed`, `git-workdir-failed`,
|
|
65
|
+
* `git-workdir-too-small`, `git-mirror-failed` (also when GitManager refuses
|
|
66
|
+
* `target`, such as with `repositoryBusy` while another transfer holds it)
|
|
67
|
+
* or `request-aborted` (an abort, or a GitManager stop that cancelled the
|
|
68
|
+
* clone). GitManager error text is never passed on.
|
|
62
69
|
*/
|
|
63
70
|
async importMirror(repositoryArg, gitManagerArg, optionsArg) {
|
|
64
|
-
const
|
|
71
|
+
const repository = this.#repository(repositoryArg);
|
|
72
|
+
const { owner, name } = repository;
|
|
65
73
|
const target = optionsArg?.target;
|
|
66
74
|
if (typeof target?.org !== 'string' || typeof target?.repo !== 'string')
|
|
67
75
|
throw new validation.ForgeInventoryError('invalid-target');
|
|
@@ -70,16 +78,7 @@ export class GiteaSourceTransfer {
|
|
|
70
78
|
throw new validation.ForgeInventoryError('request-aborted');
|
|
71
79
|
if (!gitManagerArg.running)
|
|
72
80
|
throw new validation.ForgeInventoryError('gitmanager-not-running');
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const repository = validation.record((await this.#client.request(new URL(`${this.#options.baseUrl}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`), { signal })).body);
|
|
76
|
-
reportedSizeKiB = BigInt(validation.decimal(repository.size));
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
if (signal?.aborted)
|
|
80
|
-
throw new validation.ForgeInventoryError('request-aborted');
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
81
|
+
const reportedSizeKiB = BigInt(validation.decimal((await this.#identified(repository, signal)).size));
|
|
83
82
|
let dataDir;
|
|
84
83
|
try {
|
|
85
84
|
dataDir = (await gitManagerArg.getStatus()).dataDir;
|
|
@@ -88,27 +87,33 @@ export class GiteaSourceTransfer {
|
|
|
88
87
|
throw new validation.ForgeInventoryError('git-workdir-failed');
|
|
89
88
|
}
|
|
90
89
|
const release = await reserveMirrorScratch(dataDir, reportedSizeKiB, signal);
|
|
90
|
+
let imported;
|
|
91
91
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
try {
|
|
93
|
+
imported = await gitManagerArg.importMirror({
|
|
94
|
+
org: target.org,
|
|
95
|
+
repo: target.repo,
|
|
96
|
+
remoteUrl: giteaCloneUrl(this.#options.baseUrl, owner, name),
|
|
97
|
+
credential: { kind: 'basic', username: this.#options.login, password: this.#options.token },
|
|
98
|
+
caCertificates: this.#options.caCertificates,
|
|
99
|
+
timeoutMs: this.#options.gitTimeoutMs,
|
|
100
|
+
}, { signal });
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
// The import settles only after Git has exited, so the scratch is free again.
|
|
104
|
+
release();
|
|
105
|
+
}
|
|
101
106
|
}
|
|
102
107
|
catch (error) {
|
|
103
108
|
const { GitManagerError } = await plugins.getGitManager();
|
|
104
|
-
|
|
109
|
+
if (signal?.aborted || (error instanceof GitManagerError && error.code === 'transferAborted')) {
|
|
110
|
+
throw new validation.ForgeInventoryError('request-aborted');
|
|
111
|
+
}
|
|
105
112
|
// GitManager errors are redacted, but still remote text.
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
// The import settles only after Git has exited, so the scratch is free again.
|
|
110
|
-
release();
|
|
113
|
+
return this.#explained(repository, new validation.ForgeInventoryError('git-mirror-failed'), signal);
|
|
111
114
|
}
|
|
115
|
+
await this.#identified(repository, signal);
|
|
116
|
+
return { repository: imported, reportedSizeKiB: reportedSizeKiB.toString() };
|
|
112
117
|
}
|
|
113
118
|
/**
|
|
114
119
|
* Downloads one LFS object of the repository: a batch `download` request
|
|
@@ -116,11 +121,16 @@ export class GiteaSourceTransfer {
|
|
|
116
121
|
* the action's headers only. Resolves with `{ status: 'missing' }` when
|
|
117
122
|
* Gitea answers error 404, otherwise with a stream of the object verified
|
|
118
123
|
* against `oid` and `size` as described for `LfsBatchClient.download`.
|
|
119
|
-
*
|
|
120
|
-
*
|
|
124
|
+
* LFS reaches the repository by name, so once Gitea has answered, the
|
|
125
|
+
* repository `id` is read by id as for `importMirror`; when it is no longer
|
|
126
|
+
* named `owner/name` the download is discarded with
|
|
127
|
+
* `repository-identity-changed`, when it is deleted with `http-404`, also
|
|
128
|
+
* when the download itself failed. Rejects with a fixed code, such as
|
|
129
|
+
* `lfs-object-error-422` for a size Gitea disagrees with.
|
|
121
130
|
*/
|
|
122
131
|
async downloadLfsObject(repositoryArg, oidArg, sizeArg, optionsArg = {}) {
|
|
123
|
-
const
|
|
132
|
+
const repository = this.#repository(repositoryArg);
|
|
133
|
+
const { owner, name } = repository;
|
|
124
134
|
const client = new LfsBatchClient({
|
|
125
135
|
endpoint: `${giteaCloneUrl(this.#options.baseUrl, owner, name)}/info/lfs`,
|
|
126
136
|
// Gitea's LFS routes take the token as a Basic password, not as a `token` authorization.
|
|
@@ -131,20 +141,69 @@ export class GiteaSourceTransfer {
|
|
|
131
141
|
maxResponseBytes: this.#options.maxResponseBytes,
|
|
132
142
|
batchSize: 1,
|
|
133
143
|
});
|
|
144
|
+
let download;
|
|
134
145
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
download = await client.download({ oid: oidArg, size: sizeArg }, optionsArg);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
client.close();
|
|
150
|
+
if (optionsArg.signal?.aborted)
|
|
151
|
+
throw error;
|
|
152
|
+
return this.#explained(repository, error, optionsArg.signal);
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
// The batch answer and the object request have both reached Gitea by now; neither can still go to another repository.
|
|
156
|
+
await this.#identified(repository, optionsArg.signal);
|
|
143
157
|
}
|
|
144
158
|
catch (error) {
|
|
159
|
+
if (download.status === 'available')
|
|
160
|
+
download.stream.destroy();
|
|
145
161
|
client.close();
|
|
146
162
|
throw error;
|
|
147
163
|
}
|
|
164
|
+
if (download.status === 'available') {
|
|
165
|
+
download.stream.once('close', () => client.close());
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
client.close();
|
|
169
|
+
}
|
|
170
|
+
return download;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Reads the repository by its id and checks that Gitea still names it
|
|
174
|
+
* `owner/name`, without case as Gitea looks names up: other names reject
|
|
175
|
+
* with `repository-identity-changed`, a deleted repository with `http-404`.
|
|
176
|
+
*/
|
|
177
|
+
async #identified(repositoryArg, signalArg) {
|
|
178
|
+
let record;
|
|
179
|
+
try {
|
|
180
|
+
record = validation.record((await this.#client.request(new URL(`${this.#options.baseUrl}/api/v1/repositories/${repositoryArg.id}`), { signal: signalArg })).body);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
if (signalArg?.aborted)
|
|
184
|
+
throw new validation.ForgeInventoryError('request-aborted');
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
const owner = validation.record(record.owner).login;
|
|
188
|
+
if (validation.decimal(record.id) !== repositoryArg.id || typeof owner !== 'string' || typeof record.name !== 'string'
|
|
189
|
+
|| owner.toLowerCase() !== repositoryArg.owner.toLowerCase() || record.name.toLowerCase() !== repositoryArg.name.toLowerCase()) {
|
|
190
|
+
throw new validation.ForgeInventoryError('repository-identity-changed');
|
|
191
|
+
}
|
|
192
|
+
return record;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Rejects with `errorArg`, a failed read by name, unless the identity read
|
|
196
|
+
* explains it: then with `repository-identity-changed` or `http-404`.
|
|
197
|
+
*/
|
|
198
|
+
async #explained(repositoryArg, errorArg, signalArg) {
|
|
199
|
+
try {
|
|
200
|
+
await this.#identified(repositoryArg, signalArg);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (error instanceof validation.ForgeInventoryError && (error.code === 'repository-identity-changed' || error.code === 'http-404'))
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
throw errorArg;
|
|
148
207
|
}
|
|
149
208
|
#repository(repositoryArg) {
|
|
150
209
|
const single = (valueArg) => {
|
|
@@ -153,12 +212,15 @@ export class GiteaSourceTransfer {
|
|
|
153
212
|
throw new validation.ForgeInventoryError('invalid-repository');
|
|
154
213
|
return segments[0];
|
|
155
214
|
};
|
|
215
|
+
const id = repositoryArg?.id;
|
|
216
|
+
if (typeof id !== 'string')
|
|
217
|
+
throw new validation.ForgeInventoryError('invalid-repository');
|
|
156
218
|
try {
|
|
157
|
-
return { owner: single(repositoryArg?.owner), name: single(repositoryArg?.name) };
|
|
219
|
+
return { owner: single(repositoryArg?.owner), name: single(repositoryArg?.name), id: validation.decimal(id) };
|
|
158
220
|
}
|
|
159
221
|
catch {
|
|
160
222
|
throw new validation.ForgeInventoryError('invalid-repository');
|
|
161
223
|
}
|
|
162
224
|
}
|
|
163
225
|
}
|
|
164
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5naXRlYXNvdXJjZXRyYW5zZmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5naXRlYXNvdXJjZXRyYW5zZmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxTQUFTLE1BQU0sK0JBQStCLENBQUM7QUFDM0QsT0FBTyxLQUFLLFVBQVUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUM3RCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUNoRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFFN0QsT0FBTyxFQUFFLGFBQWEsRUFBRSxvQkFBb0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBaURwRjs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sbUJBQW1CO0lBQ3JCLFFBQVEsQ0FHZjtJQUNPLE9BQU8sQ0FBcUI7SUFFckMsWUFBWSxVQUF1QztRQUNqRCxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN2RCxNQUFNLE1BQU0sR0FBRyxDQUFDLFFBQWlCLEVBQUUsRUFBRSxDQUFDLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxDQUFDLFFBQVEsSUFBSSx5QkFBeUIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDNUgsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLElBQUksTUFBTSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxJQUFJLFVBQVUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDeEksTUFBTSxJQUFJLFVBQVUsQ0FBQyxtQkFBbUIsQ0FBQyxp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
|
|
226
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5naXRlYXNvdXJjZXRyYW5zZmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5naXRlYXNvdXJjZXRyYW5zZmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxTQUFTLE1BQU0sK0JBQStCLENBQUM7QUFDM0QsT0FBTyxLQUFLLFVBQVUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUM3RCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUNoRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFFN0QsT0FBTyxFQUFFLGFBQWEsRUFBRSxvQkFBb0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBc0RwRjs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sbUJBQW1CO0lBQ3JCLFFBQVEsQ0FHZjtJQUNPLE9BQU8sQ0FBcUI7SUFFckMsWUFBWSxVQUF1QztRQUNqRCxNQUFNLE9BQU8sR0FBRyxVQUFVLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN2RCxNQUFNLE1BQU0sR0FBRyxDQUFDLFFBQWlCLEVBQUUsRUFBRSxDQUFDLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxDQUFDLFFBQVEsSUFBSSx5QkFBeUIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDNUgsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLElBQUksTUFBTSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxJQUFJLFVBQVUsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDeEksTUFBTSxJQUFJLFVBQVUsQ0FBQyxtQkFBbUIsQ0FBQyxp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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foss.global/codefeed",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The @foss.global/codefeed module generates commit/activity feeds from forge providers including Gitea and GitManager.",
|
|
6
6
|
"exports": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@foss.global/gitmanager": "^2.4.0",
|
|
24
|
-
"@foss.global/interfaces": "^2.
|
|
24
|
+
"@foss.global/interfaces": "^2.6.0",
|
|
25
25
|
"@push.rocks/lik": "^6.4.4",
|
|
26
26
|
"@push.rocks/smartnpm": "^2.1.3",
|
|
27
27
|
"@push.rocks/smarttime": "^5.0.1"
|
package/readme.md
CHANGED
|
@@ -103,6 +103,10 @@ const inventory = composeGiteaSourceInventory(discovery, results, [{ package: '@
|
|
|
103
103
|
|
|
104
104
|
A run is split into units: the instance, each owner and each repository. `measureUnit` never rejects; a unit whose primary record cannot be read becomes `failed` with a sanitized reason. A resumed run measures failed units, and units holding a partial measure, again and passes all results to `composeGiteaSourceInventory`, where the last result per unit wins. The composed document is deterministic: it carries no timestamps, sorts owners, repositories, packages and reasons, and does not depend on the order of the results. An aborted signal or a rejected `onResult` aborts the units in flight, hands over no further result, and rejects `measureUnits` once every worker has stopped.
|
|
105
105
|
|
|
106
|
+
Discovery records each repository's Gitea `id` as a canonical decimal string, and `units()` passes it into the repository unit. A unit with an `id` fails with `repository-identity-changed` when its name answers with another id, as after a rename and a new repository under the old name; a unit without one, from a discovery stored by an earlier version, is checked by name only. `composeGiteaSourceInventory` counts a repository result for another id than discovery recorded as a failed unit (`repository: unit failed: repository-identity-changed`), and adds `repository: duplicate-id` when two measured repositories share one id, which a discovery without ids cannot rule out.
|
|
107
|
+
|
|
108
|
+
`lookupRepository(id)` reads one repository by its id through `GET /api/v1/repositories/{id}`, which keeps answering after a rename or transfer. It resolves `{ status: 'present', id, owner, name }` with the name Gitea gives the repository now, or `{ status: 'absent', id }` only when Gitea answers 404, which a site administrator gets only for a deleted repository. Every other failed read rejects with its code, so a timeout or a 500 never counts as a deletion; an id that is not a canonical decimal rejects with `invalid-repository-id` without a request.
|
|
109
|
+
|
|
106
110
|
What is measured: users with their account flags, two-factor and SSH/GPG keys; organizations, members, teams, team memberships and repository grants; repositories with their flags, Git refs split into branches, tags, pull request refs and other refs, Git objects and their uncompressed bytes, LFS objects referenced anywhere in history with the bytes Gitea holds and the number it lacks, issues, pull requests, comments, reviews, review comments, labels, milestones, reactions and attachments; releases including drafts, assets and asset bytes; wiki pages; collaborators, team grants, deploy keys, webhooks, branch and tag protections and push mirrors; packages per type with versions, files and bytes; system webhooks, Actions runners, workflows, runs, artifacts and the number of Actions secrets and variables; unadopted repositories.
|
|
107
111
|
|
|
108
112
|
Secrets are counted, never named: the report carries no tokens, Actions secret or variable values or names, webhook secrets or mirror remote URLs. Reasons are adapter codes or fixed text, never response bodies or URLs. Gitea has no read that lists user-scoped Actions secrets; that is listed in `limitations`, since they are reissued. User-scoped webhooks, variables and runners are read as that user through the `Sudo` header.
|
|
@@ -112,7 +116,7 @@ Git-level measures come from a mirror clone of every ref Gitea advertises, `refs
|
|
|
112
116
|
Known gaps that keep an inventory incomplete:
|
|
113
117
|
- A repository with its issues or pull requests unit disabled hides both through the API, so its issues, pull requests, comments, reviews, reactions and attachments are `notMeasured`. A disabled wiki hides its pages the same way. Labels, milestones, releases and Actions stay readable while their units are disabled.
|
|
114
118
|
|
|
115
|
-
Listings page until an empty page or the `x-total-count` total is reached. A total that changes mid-listing or disagrees with the rows read, a repeated page, a malformed page, a failed request or the page limit makes that measure `partial` and keeps the rows already read; only a listing that does not exist, such as the wiki of a repository without one, counts as empty.
|
|
119
|
+
Listings page until an empty page or the `x-total-count` total is reached. A total that changes mid-listing or disagrees with the rows read, a repeated page, a malformed page, a failed request or the page limit makes that measure `partial` and keeps the rows already read; only a listing that does not exist, such as the wiki of a repository without one, counts as empty. A repository search that lists one repository id twice has shifted under its pages and may have skipped a repository: discovery keeps the first entry and adds `discovery: repositories: duplicate-id` to `incompleteReasons`.
|
|
116
120
|
|
|
117
121
|
### Gitea source transfer
|
|
118
122
|
|
|
@@ -132,12 +136,12 @@ const source = new GiteaSourceTransfer({
|
|
|
132
136
|
// A started GitManager rooted in the caller's own scratch space; codefeed creates no scratch roots.
|
|
133
137
|
const scratch = new GitManager({ dataDir: scratchDir });
|
|
134
138
|
await scratch.start();
|
|
135
|
-
const { repository, reportedSizeKiB } = await source.importMirror({ owner: 'team', name: 'app' }, scratch, {
|
|
139
|
+
const { repository, reportedSizeKiB } = await source.importMirror({ owner: 'team', name: 'app', id: '11' }, scratch, {
|
|
136
140
|
target: { org: 'team', repo: 'app' },
|
|
137
141
|
signal,
|
|
138
142
|
});
|
|
139
143
|
|
|
140
|
-
const download = await source.downloadLfsObject({ owner: 'team', name: 'app' }, oid, size, { signal });
|
|
144
|
+
const download = await source.downloadLfsObject({ owner: 'team', name: 'app', id: '11' }, oid, size, { signal });
|
|
141
145
|
if (download.status === 'missing') {
|
|
142
146
|
// Gitea answered error 404: the object is not held at the source.
|
|
143
147
|
} else {
|
|
@@ -145,11 +149,13 @@ if (download.status === 'missing') {
|
|
|
145
149
|
}
|
|
146
150
|
```
|
|
147
151
|
|
|
148
|
-
|
|
152
|
+
A source repository is named by `owner`, `name` and its Gitea repository `id`, a canonical decimal string as the inventory records it; anything else rejects with `invalid-repository`. Git and the LFS API reach a repository only by name, and a name can move to another repository between two reads: Gitea answers a renamed repository's old path with a redirect, and a new repository can take the old name. Every source read is therefore bracketed by an identity read of `GET /api/v1/repositories/{id}`, which must still name `owner/name` (compared case-insensitively, as Gitea does). A repository that was renamed, or whose name now belongs to another repository, rejects with `repository-identity-changed`; a deleted one rejects with `http-404`. A transfer that fails is explained the same way when the identity read shows one of those two causes.
|
|
153
|
+
|
|
154
|
+
`importMirror` reads the repository's size from the REST API by id, reserves its scratch in the process-wide `GitScratchBudget` of the file system holding the GitManager's data directory (twice the reported size plus 64 MiB, admitted in arrival order), and then clones every ref into `target` with `importMirror` of `@foss.global/gitmanager` and reads the identity again. When that last read rejects, the clone has finished and stays at `target`; the caller discards it. The clone URL is formed from `baseUrl` and the repository's owner and name; the reader's login and token reach Git as a Basic credential only through GIT_CONFIG_* entries in the child process environment, never in the URL, the command line or any file. A repository that does not fit even alone rejects with `git-workdir-too-small` without cloning. The reservation is released when the clone ends. Failures reject with a fixed `ForgeInventoryError` code (`invalid-repository`, `invalid-target`, `gitmanager-not-running`, `repository-identity-changed`, a REST code such as `http-404`, `git-workdir-failed`, `git-workdir-too-small`, `git-mirror-failed`, `request-aborted`); GitManager error text is never passed on. An abort rejects with `request-aborted`. While the clone runs, the abort reaches the GitManager import through its own `signal` (`@foss.global/gitmanager` 2.4.0 or later): Git is ended, the call settles once it has exited, no repository is left at `target`, and the reservation is released then. The GitManager keeps serving its other work, so one engine can serve several imports and other commands at once; a `stop()` of it during the clone also rejects the import with `request-aborted`.
|
|
149
155
|
|
|
150
156
|
While the import runs, GitManager holds `target`: writes to it, such as `initBareRepository`, `restoreRepositoryBundle`, `setDefaultBranch` or a Smart HTTP push, fail with a `GitManagerError` whose code is `repositoryBusy`, and a second import into the same `target` is refused at once (codefeed rejects it with `git-mirror-failed`). Reads, of `target` as of other repositories, proceed.
|
|
151
157
|
|
|
152
|
-
`downloadLfsObject` sends one batch `download` request with the reader's Basic credentials, then a GET of the action's href with the action's headers only. A missing object resolves as `{ status: 'missing' }`, so a caller can list the missing oids; any other object error rejects with `lfs-object-error-<code>`, such as `lfs-object-error-422` for a size Gitea disagrees with. The returned stream never holds the whole object: it counts and hashes the bytes as they pass and holds back the last chunk until the body ended with exactly `size` bytes whose SHA-256 is `oid`. Otherwise it errors with `lfs-size-overrun`, `lfs-short-body` or `lfs-digest-mismatch`; a `content-length` other than `size` rejects with `lfs-size-mismatch` before streaming. An abort errors it with `request-aborted`, a transfer longer than `lfsTransferTimeoutMs` or idle for `requestTimeoutMs` with `lfs-transfer-timed-out`. Destroying the stream ends the download.
|
|
158
|
+
`downloadLfsObject` sends one batch `download` request with the reader's Basic credentials, then a GET of the action's href with the action's headers only, and then reads the identity; when that read rejects, the stream is destroyed and the call rejects with its code. A missing object resolves as `{ status: 'missing' }`, so a caller can list the missing oids; any other object error rejects with `lfs-object-error-<code>`, such as `lfs-object-error-422` for a size Gitea disagrees with. The returned stream never holds the whole object: it counts and hashes the bytes as they pass and holds back the last chunk until the body ended with exactly `size` bytes whose SHA-256 is `oid`. Otherwise it errors with `lfs-size-overrun`, `lfs-short-body` or `lfs-digest-mismatch`; a `content-length` other than `size` rejects with `lfs-size-mismatch` before streaming. An abort errors it with `request-aborted`, a transfer longer than `lfsTransferTimeoutMs` or idle for `requestTimeoutMs` with `lfs-transfer-timed-out`. Destroying the stream ends the download.
|
|
153
159
|
|
|
154
160
|
### Git LFS batch client
|
|
155
161
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -52,6 +52,11 @@ export interface IGiteaSourceMeasureOptions {
|
|
|
52
52
|
onResult?: (resultArg: TUnitResult) => void | Promise<void>;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/** A repository read by its id: the name Gitea gives it now, or `absent` once it is deleted. */
|
|
56
|
+
export type TGiteaSourceRepositoryLookup =
|
|
57
|
+
| { status: 'present'; id: string; owner: string; name: string }
|
|
58
|
+
| { status: 'absent'; id: string };
|
|
59
|
+
|
|
55
60
|
interface IListing {
|
|
56
61
|
items: unknown[];
|
|
57
62
|
/** A sanitized reason when the listing did not finish. */
|
|
@@ -217,9 +222,10 @@ export class GiteaSourceInventory {
|
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
/**
|
|
220
|
-
* Identifies the source and its reader and lists every owner and repository
|
|
221
|
-
* Rejects unless the token belongs to a site administrator; a listing that
|
|
222
|
-
* does not finish is reported in
|
|
225
|
+
* Identifies the source and its reader and lists every owner and repository
|
|
226
|
+
* with its id. Rejects unless the token belongs to a site administrator; a listing that
|
|
227
|
+
* does not finish, or that lists a repository id twice, is reported in
|
|
228
|
+
* `incompleteReasons`.
|
|
223
229
|
*/
|
|
224
230
|
public async discover(signalArg?: AbortSignal): Promise<IDiscovery> {
|
|
225
231
|
const reader = validation.record((await this.#get('/user', { signal: signalArg })).body);
|
|
@@ -246,10 +252,18 @@ export class GiteaSourceInventory {
|
|
|
246
252
|
...organizations.items.map((itemArg) => ({ kind: 'organization' as const, name: segment(validation.record(itemArg).name) })),
|
|
247
253
|
...users.items.map((itemArg) => ({ kind: 'user' as const, name: segment(validation.record(itemArg).login) })),
|
|
248
254
|
].sort((aArg, bArg) => compareText(aArg.kind, bArg.kind) || compareText(aArg.name, bArg.name));
|
|
249
|
-
|
|
255
|
+
// An id listed twice means the listing shifted under its pages, so it may also have skipped a repository.
|
|
256
|
+
const ids = new Set<string>();
|
|
257
|
+
const discovered: IDiscovery['repositories'] = [];
|
|
258
|
+
for (const itemArg of repositories.items) {
|
|
250
259
|
const item = validation.record(itemArg);
|
|
251
|
-
|
|
252
|
-
|
|
260
|
+
const id = validation.decimal(item.id);
|
|
261
|
+
if (ids.has(id)) continue;
|
|
262
|
+
ids.add(id);
|
|
263
|
+
discovered.push({ owner: segment(validation.record(item.owner).login), name: segment(item.name), id });
|
|
264
|
+
}
|
|
265
|
+
if (ids.size !== repositories.items.length) incompleteReasons.push('discovery: repositories: duplicate-id');
|
|
266
|
+
discovered.sort((aArg, bArg) => compareText(aArg.owner, bArg.owner) || compareText(aArg.name, bArg.name));
|
|
253
267
|
return {
|
|
254
268
|
source: { instanceId: this.#options.instanceId, kind: 'gitea', baseUrl: this.#options.baseUrl, version },
|
|
255
269
|
reader: { login: segment(reader.login), siteAdmin: true },
|
|
@@ -264,18 +278,45 @@ export class GiteaSourceInventory {
|
|
|
264
278
|
return [
|
|
265
279
|
{ kind: 'instance' },
|
|
266
280
|
...discoveryArg.owners.map((ownerArg): TUnit => ({ kind: 'owner', ownerKind: ownerArg.kind, name: ownerArg.name })),
|
|
267
|
-
...discoveryArg.repositories.map((repositoryArg): TUnit => ({
|
|
281
|
+
...discoveryArg.repositories.map((repositoryArg): TUnit => ({
|
|
282
|
+
kind: 'repository', owner: repositoryArg.owner, name: repositoryArg.name, ...repositoryArg.id === undefined ? {} : { id: repositoryArg.id },
|
|
283
|
+
})),
|
|
268
284
|
];
|
|
269
285
|
}
|
|
270
286
|
|
|
271
|
-
/**
|
|
287
|
+
/**
|
|
288
|
+
* Reads a repository by its id through `GET /repositories/{id}`, which
|
|
289
|
+
* keeps answering after a rename or transfer: `present` with the name Gitea
|
|
290
|
+
* gives it now, or `absent` when Gitea answers 404, which a site
|
|
291
|
+
* administrator only gets for a deleted repository. Any other failed read
|
|
292
|
+
* rejects with its error, so it never counts as a deletion; an id that is not
|
|
293
|
+
* a canonical decimal rejects with `invalid-repository-id`.
|
|
294
|
+
*/
|
|
295
|
+
public async lookupRepository(idArg: string, signalArg?: AbortSignal): Promise<TGiteaSourceRepositoryLookup> {
|
|
296
|
+
if (typeof idArg !== 'string' || !/^[1-9][0-9]{0,99}$/.test(idArg)) throw new validation.ForgeInventoryError('invalid-repository-id');
|
|
297
|
+
let item: Record<string, unknown>;
|
|
298
|
+
try {
|
|
299
|
+
item = validation.record((await this.#get(`/repositories/${idArg}`, { signal: signalArg })).body);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (error instanceof validation.ForgeInventoryError && error.code === 'http-404') return { status: 'absent', id: idArg };
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
if (validation.decimal(item.id) !== idArg) throw new validation.ForgeInventoryError('repository-identity-changed');
|
|
305
|
+
return { status: 'present', id: idArg, owner: segment(validation.record(item.owner).login), name: segment(item.name) };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Measures one unit. Never rejects: a unit whose primary record cannot be
|
|
310
|
+
* read is `failed`, and so is a repository unit carrying an id that its name
|
|
311
|
+
* no longer answers with (`repository-identity-changed`).
|
|
312
|
+
*/
|
|
272
313
|
public async measureUnit(unitArg: TUnit, signalArg?: AbortSignal): Promise<TUnitResult> {
|
|
273
314
|
try {
|
|
274
315
|
if (unitArg.kind === 'instance') return { kind: 'instance', instance: await this.#instance(signalArg) };
|
|
275
316
|
if (unitArg.kind === 'owner') {
|
|
276
317
|
return { kind: 'owner', owner: unitArg.ownerKind === 'organization' ? await this.#organization(unitArg.name, signalArg) : await this.#user(unitArg.name, signalArg) };
|
|
277
318
|
}
|
|
278
|
-
return { kind: 'repository', repository: await this.#repository(unitArg.owner, unitArg.name, signalArg) };
|
|
319
|
+
return { kind: 'repository', repository: await this.#repository(unitArg.owner, unitArg.name, unitArg.id, signalArg) };
|
|
279
320
|
} catch (error) {
|
|
280
321
|
return { kind: 'failed', unit: unitArg, reason: reason(error) };
|
|
281
322
|
}
|
|
@@ -451,11 +492,14 @@ export class GiteaSourceInventory {
|
|
|
451
492
|
return counts;
|
|
452
493
|
}
|
|
453
494
|
|
|
454
|
-
async #repository(ownerArg: string, nameArg: string, signalArg?: AbortSignal): Promise<IRepository> {
|
|
495
|
+
async #repository(ownerArg: string, nameArg: string, idArg: string | undefined, signalArg?: AbortSignal): Promise<IRepository> {
|
|
455
496
|
const path = `/repos/${encodeURIComponent(ownerArg)}/${encodeURIComponent(nameArg)}`;
|
|
456
497
|
const item = validation.record((await this.#get(path, { signal: signalArg })).body);
|
|
457
498
|
const owner = validation.record(item.owner);
|
|
458
|
-
|
|
499
|
+
// A name re-created after a rename answers with another id; without a discovered id only the name is checked.
|
|
500
|
+
if (segment(owner.login) !== ownerArg || segment(item.name) !== nameArg || (idArg !== undefined && validation.decimal(item.id) !== idArg)) {
|
|
501
|
+
throw new validation.ForgeInventoryError('repository-identity-changed');
|
|
502
|
+
}
|
|
459
503
|
const visibility = validation.giteaRepositoryVisibility(item.private, item.internal, owner.visibility);
|
|
460
504
|
if (visibility === null) throw new validation.ForgeInventoryError('invalid-visibility');
|
|
461
505
|
const empty = requiredBoolean(item.empty);
|
|
@@ -775,9 +819,10 @@ const measuresOf = (valueArg: unknown, pathArg: string, outArg: Array<{ path: st
|
|
|
775
819
|
/**
|
|
776
820
|
* Composes the deterministic inventory from a discovery and its unit results.
|
|
777
821
|
* Repeated results for one unit keep the last. Units without a successful
|
|
778
|
-
* result,
|
|
779
|
-
*
|
|
780
|
-
*
|
|
822
|
+
* result, a repository result for another id than discovery recorded, two
|
|
823
|
+
* repositories measured with one id, owner repository counts that disagree
|
|
824
|
+
* with discovery, and every partial or unmeasured value make the inventory
|
|
825
|
+
* incomplete, with reasons aggregated per subject kind, path and reason.
|
|
781
826
|
*/
|
|
782
827
|
export const composeGiteaSourceInventory = (
|
|
783
828
|
discoveryArg: IDiscovery,
|
|
@@ -821,12 +866,15 @@ export const composeGiteaSourceInventory = (
|
|
|
821
866
|
const failedRepositories: string[] = [];
|
|
822
867
|
for (const repository of discoveryArg.repositories) {
|
|
823
868
|
const result = results.get(unitKey({ kind: 'repository', ...repository }));
|
|
824
|
-
|
|
869
|
+
// A result for another id under the discovered name measured the repository that took the name.
|
|
870
|
+
if (result?.kind === 'repository' && (repository.id === undefined || result.repository.id === repository.id)) repositories.push(result.repository);
|
|
825
871
|
else {
|
|
826
872
|
failedRepositories.push(`${repository.owner}/${repository.name}`);
|
|
827
|
-
addReason(`repository: ${result?.kind === 'failed' ? `unit failed: ${result.reason}` : 'unit not measured'}`);
|
|
873
|
+
addReason(`repository: ${result?.kind === 'failed' ? `unit failed: ${result.reason}` : result?.kind === 'repository' ? 'unit failed: repository-identity-changed' : 'unit not measured'}`);
|
|
828
874
|
}
|
|
829
875
|
}
|
|
876
|
+
// Without discovered ids, two names measured as one id show a rename between their units.
|
|
877
|
+
if (new Set(repositories.map((repositoryArg) => repositoryArg.id)).size !== repositories.length) addReason('repository: duplicate-id');
|
|
830
878
|
owners.sort((aArg, bArg) => compareText(aArg.kind, bArg.kind) || compareText(aArg.name, bArg.name));
|
|
831
879
|
repositories.sort((aArg, bArg) => compareText(aArg.owner, bArg.owner) || compareText(aArg.name, bArg.name));
|
|
832
880
|
|