@foss.global/codefeed 2.1.0 → 3.0.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 +19 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.giteasourceinventory.d.ts +18 -6
- package/dist_ts/classes.giteasourceinventory.js +68 -45
- package/dist_ts/forgeinventory.client.d.ts +7 -2
- package/dist_ts/forgeinventory.client.js +9 -6
- package/dist_ts/forgeinventory.transport.d.ts +5 -4
- package/dist_ts/forgeinventory.transport.js +6 -6
- package/dist_ts/giteasourceinventory.git.d.ts +70 -0
- package/dist_ts/giteasourceinventory.git.js +294 -0
- package/dist_ts/plugins.d.ts +4 -1
- package/dist_ts/plugins.js +5 -2
- package/package.json +4 -4
- package/readme.md +7 -5
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.giteasourceinventory.ts +82 -40
- package/ts/forgeinventory.client.ts +12 -5
- package/ts/forgeinventory.transport.ts +10 -9
- package/ts/giteasourceinventory.git.ts +316 -0
- package/ts/plugins.ts +4 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as validation from './forgeinventory.validation.js';
|
|
2
2
|
/**
|
|
3
|
-
* One bounded, credential-scoped JSON
|
|
4
|
-
* response-size budget and a strict JSON
|
|
3
|
+
* One bounded, credential-scoped JSON read at a time, a GET or a POST with a
|
|
4
|
+
* JSON body: a per-request deadline, a response-size budget and a strict JSON
|
|
5
|
+
* content type. Failures surface as
|
|
5
6
|
* `ForgeInventoryError` codes that never contain bodies, URLs or credentials.
|
|
6
7
|
*/
|
|
7
8
|
export class ForgeRequestClient {
|
|
@@ -26,14 +27,16 @@ export class ForgeRequestClient {
|
|
|
26
27
|
let bodyChunks;
|
|
27
28
|
try {
|
|
28
29
|
this.#metrics.requests++;
|
|
29
|
-
const
|
|
30
|
-
const
|
|
30
|
+
const mediaType = requestArg.mediaType ?? 'application/json';
|
|
31
|
+
const requestBody = requestArg.json === undefined ? undefined : new TextEncoder().encode(JSON.stringify(requestArg.json));
|
|
32
|
+
const headers = { accept: mediaType, ...(requestBody ? { 'content-type': mediaType } : {}), ...requestArg.headers, ...this.#options.headers };
|
|
33
|
+
const response = await this.#options.transport(url, headers, controller.signal, requestBody);
|
|
31
34
|
bodyChunks = response.body?.[Symbol.asyncIterator]();
|
|
32
35
|
if (response.status === 429)
|
|
33
36
|
this.#metrics.rateLimitedRequests++;
|
|
34
37
|
if (response.status !== 200)
|
|
35
38
|
throw new validation.ForgeInventoryError(`http-${response.status}`);
|
|
36
|
-
if (response.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !==
|
|
39
|
+
if (response.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== mediaType || !bodyChunks)
|
|
37
40
|
throw new validation.ForgeInventoryError('invalid-content-type');
|
|
38
41
|
const chunks = [];
|
|
39
42
|
let bytes = 0;
|
|
@@ -72,4 +75,4 @@ export class ForgeRequestClient {
|
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yZ2VpbnZlbnRvcnkuY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvZm9yZ2VpbnZlbnRvcnkuY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sS0FBSyxVQUFVLE1BQU0sZ0NBQWdDLENBQUM7QUFpQzdEOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLGtCQUFrQjtJQUNwQixRQUFRLENBQTZCO0lBQ3JDLFFBQVEsR0FBeUIsRUFBRSxRQUFRLEVBQUUsQ0FBQyxFQUFFLGFBQWEsRUFBRSxDQUFDLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQyxFQUFFLENBQUM7SUFFcEcsWUFBWSxVQUFzQztRQUNoRCxJQUFJLENBQUMsUUFBUSxHQUFHLEVBQUUsR0FBRyxVQUFVLEVBQUUsT0FBTyxFQUFFLEVBQUUsR0FBRyxVQUFVLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztJQUN4RSxDQUFDO0lBRUQsSUFBVyxPQUFPLEtBQTJCLE9BQU8sRUFBRSxHQUFHLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFFcEUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFRLEVBQUUsVUFBVSxHQUF5QixFQUFFO1FBQ2xFLE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxVQUFVLENBQUM7UUFDOUIsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDLGdCQUFnQixJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsZ0JBQWdCLENBQUM7UUFDL0UsSUFBSSxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLElBQUksUUFBUSxHQUFHLENBQUM7WUFBRSxNQUFNLElBQUksVUFBVSxDQUFDLG1CQUFtQixDQUFDLHlCQUF5QixDQUFDLENBQUM7UUFDekgsTUFBTSxVQUFVLEdBQUcsSUFBSSxlQUFlLEVBQUUsQ0FBQztRQUN6QyxNQUFNLEtBQUssR0FBRyxHQUFHLEVBQUUsQ0FBQyxVQUFVLENBQUMsS0FBSyxFQUFFLENBQUM7UUFD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
|
|
@@ -6,11 +6,12 @@ export interface IForgeTransportResponse {
|
|
|
6
6
|
body: AsyncIterable<Uint8Array> | null;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Sends one credential-scoped GET
|
|
10
|
-
* A redirect status rejects and is
|
|
11
|
-
* the connection, also while the
|
|
9
|
+
* Sends one credential-scoped GET, or a POST of `body` when one is given, and
|
|
10
|
+
* resolves once response headers arrive. A redirect status rejects and is
|
|
11
|
+
* never followed; aborting `signal` discards the connection, also while the
|
|
12
|
+
* body is still being read.
|
|
12
13
|
*/
|
|
13
|
-
export type TForgeTransport = (url: URL, headers: Record<string, string>, signal: AbortSignal) => Promise<IForgeTransportResponse>;
|
|
14
|
+
export type TForgeTransport = (url: URL, headers: Record<string, string>, signal: AbortSignal, body?: Uint8Array<ArrayBuffer>) => Promise<IForgeTransportResponse>;
|
|
14
15
|
/** Node's global `fetch` with the process-wide default trust store. */
|
|
15
16
|
export declare const globalFetchTransport: TForgeTransport;
|
|
16
17
|
/**
|
|
@@ -8,8 +8,8 @@ const redirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
|
8
8
|
*/
|
|
9
9
|
const idleSocketTimeoutMs = 4_000;
|
|
10
10
|
/** Node's global `fetch` with the process-wide default trust store. */
|
|
11
|
-
export const globalFetchTransport = async (url, headers, signal) => {
|
|
12
|
-
const response = await fetch(url, { method: 'GET', headers, redirect: 'error', credentials: 'omit', signal });
|
|
11
|
+
export const globalFetchTransport = async (url, headers, signal, body) => {
|
|
12
|
+
const response = await fetch(url, { method: body ? 'POST' : 'GET', headers, body, redirect: 'error', credentials: 'omit', signal });
|
|
13
13
|
return { status: response.status, headers: response.headers, body: response.body };
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
@@ -19,11 +19,11 @@ export const globalFetchTransport = async (url, headers, signal) => {
|
|
|
19
19
|
*/
|
|
20
20
|
export const createHttpsTransport = (caCertificates) => {
|
|
21
21
|
const agent = new plugins.https.Agent({ ca: caCertificates, keepAlive: true, timeout: idleSocketTimeoutMs });
|
|
22
|
-
return (url, headers, signal) => new Promise((resolve, reject) => {
|
|
22
|
+
return (url, headers, signal, body) => new Promise((resolve, reject) => {
|
|
23
23
|
if (signal.aborted)
|
|
24
24
|
return reject(signal.reason);
|
|
25
25
|
// Bodies are read as sent, so ask for them without a content coding.
|
|
26
|
-
const request = plugins.https.request(url, { method: 'GET', headers: { ...headers, 'accept-encoding': 'identity' }, agent });
|
|
26
|
+
const request = plugins.https.request(url, { method: body ? 'POST' : 'GET', headers: { ...headers, 'accept-encoding': 'identity' }, agent });
|
|
27
27
|
// Stays attached for the request's lifetime, so a connection failure while
|
|
28
28
|
// the body is read never becomes an unhandled 'error' event.
|
|
29
29
|
request.on('error', reject);
|
|
@@ -52,7 +52,7 @@ export const createHttpsTransport = (caCertificates) => {
|
|
|
52
52
|
request.destroy();
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
|
-
request.end();
|
|
55
|
+
request.end(body);
|
|
56
56
|
});
|
|
57
57
|
};
|
|
58
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
58
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yZ2VpbnZlbnRvcnkudHJhbnNwb3J0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvZm9yZ2VpbnZlbnRvcnkudHJhbnNwb3J0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBa0J4QyxzRkFBc0Y7QUFDdEYsTUFBTSxnQkFBZ0IsR0FBd0IsSUFBSSxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztBQUVqRjs7OztHQUlHO0FBQ0gsTUFBTSxtQkFBbUIsR0FBRyxLQUFLLENBQUM7QUFFbEMsdUVBQXVFO0FBQ3ZFLE1BQU0sQ0FBQyxNQUFNLG9CQUFvQixHQUFvQixLQUFLLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLEVBQUU7SUFDeEYsTUFBTSxRQUFRLEdBQUcsTUFBTSxLQUFLLENBQUMsR0FBRyxFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztJQUNwSSxPQUFPLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLFFBQVEsQ0FBQyxPQUFPLEVBQUUsSUFBSSxFQUFFLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNyRixDQUFDLENBQUM7QUFFRjs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcsQ0FBQyxjQUF3QixFQUFtQixFQUFFO0lBQ2hGLE1BQU0sS0FBSyxHQUFHLElBQUksT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsRUFBRSxFQUFFLEVBQUUsY0FBYyxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLENBQUMsQ0FBQztJQUM3RyxPQUFPLENBQUMsR0FBRyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLE9BQU8sQ0FBMEIsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDOUYsSUFBSSxNQUFNLENBQUMsT0FBTztZQUFFLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNqRCxxRUFBcUU7UUFDckUsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLEVBQUUsR0FBRyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsVUFBVSxFQUFFLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUM3SSwyRUFBMkU7UUFDM0UsNkRBQTZEO1FBQzdELE9BQU8sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQzVCLDZFQUE2RTtRQUM3RSwrRUFBK0U7UUFDL0UsTUFBTSxLQUFLLEdBQUcsR0FBRyxFQUFFO1lBQ2pCLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDdEIsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3BCLENBQUMsQ0FBQztRQUNGLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDeEQsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLG1CQUFtQixDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQ3hFLE9BQU8sQ0FBQyxFQUFFLENBQUMsVUFBVSxFQUFFLENBQUMsUUFBUSxFQUFFLEVBQUU7WUFDbEMsSUFBSSxDQUFDO2dCQUNILE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FBQyxVQUFVLElBQUksQ0FBQyxDQUFDO2dCQUN4QyxJQUFJLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUM7b0JBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO2dCQUN2RSxtRUFBbUU7Z0JBQ25FLE1BQU0sZUFBZSxHQUFHLElBQUksT0FBTyxFQUFFLENBQUM7Z0JBQ3RDLEtBQUssSUFBSSxLQUFLLEdBQUcsQ0FBQyxFQUFFLEtBQUssR0FBRyxRQUFRLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxLQUFLLElBQUksQ0FBQyxFQUFFLENBQUM7b0JBQ25FLGVBQWUsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsRUFBRSxRQUFRLENBQUMsVUFBVSxDQUFDLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUNyRixDQUFDO2dCQUNELE9BQU8sQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsZUFBZSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO1lBQ2hFLENBQUM7WUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO2dCQUNmLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFDZCxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDcEIsQ0FBQztRQUNILENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNwQixDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyJ9
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import type { ForgeRequestClient } from './forgeinventory.client.js';
|
|
3
|
+
type IGitCounts = Omit<plugins.fossInterfaces.data.IForgeSourceGitCounts, 'reportedSizeKiB'>;
|
|
4
|
+
type ILfsCounts = plugins.fossInterfaces.data.IForgeSourceLfsCounts;
|
|
5
|
+
export interface IGiteaMirrorSource {
|
|
6
|
+
/** Instance root without a trailing slash. */
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
owner: string;
|
|
9
|
+
name: string;
|
|
10
|
+
/** The reader's login and token; Gitea accepts a token as the Basic password for Git and LFS. */
|
|
11
|
+
username: string;
|
|
12
|
+
token: string;
|
|
13
|
+
caCertificates?: string[];
|
|
14
|
+
/** Private directory that holds the temporary mirror. */
|
|
15
|
+
workDir: string;
|
|
16
|
+
/** The repository size Gitea reports, which sizes the mirror's scratch reservation. */
|
|
17
|
+
reportedSizeKiB: bigint;
|
|
18
|
+
/** Wall-clock limit of the mirror clone. */
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
/** Sends LFS batch reads with the reader's Basic credentials. */
|
|
21
|
+
lfsClient: ForgeRequestClient;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Scratch space reserved for one mirror: twice the size Gitea reports, because
|
|
26
|
+
* a clone holds received and indexed packs, plus fixed headroom for refs, the
|
|
27
|
+
* index and a size Gitea has not updated yet.
|
|
28
|
+
*/
|
|
29
|
+
export declare const mirrorScratchBytes: (reportedSizeKiBArg: bigint) => bigint;
|
|
30
|
+
/**
|
|
31
|
+
* Admits mirror clones on one file system in arrival order while the scratch
|
|
32
|
+
* they reserve fits its free space. A clone's reservation counts in full until
|
|
33
|
+
* it is released, although part of it is already written, so the check never
|
|
34
|
+
* admits more than fits. A clone that does not fit waits until the clones in
|
|
35
|
+
* flight release their reservations; one that does not fit even alone is
|
|
36
|
+
* refused.
|
|
37
|
+
*/
|
|
38
|
+
export declare class GitScratchBudget {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(freeBytesArg?: (directoryArg: string) => Promise<bigint>);
|
|
41
|
+
/**
|
|
42
|
+
* Reserves `bytesArg` in `directoryArg` and resolves with the function that
|
|
43
|
+
* releases them, or with null when they exceed the free space with no other
|
|
44
|
+
* clone in flight. Rejects when the free space cannot be read or `signalArg`
|
|
45
|
+
* aborts.
|
|
46
|
+
*/
|
|
47
|
+
reserve(directoryArg: string, bytesArg: bigint, signalArg?: AbortSignal): Promise<(() => void) | null>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Measures one repository at the Git level: clones every ref Gitea advertises
|
|
51
|
+
* into a temporary mirror, counts its refs and objects, finds the LFS pointers
|
|
52
|
+
* of every reachable revision and checks through the LFS batch API which of
|
|
53
|
+
* their objects Gitea holds. The mirror is removed afterwards. The clone starts
|
|
54
|
+
* only once its scratch reservation fits the work directory's free space; a
|
|
55
|
+
* repository too large for it fails with `git-workdir-too-small`.
|
|
56
|
+
*/
|
|
57
|
+
export declare const measureGiteaMirror: (sourceArg: IGiteaMirrorSource) => Promise<{
|
|
58
|
+
git: IGitCounts;
|
|
59
|
+
lfs: ILfsCounts;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Asks Gitea's LFS batch API, as a `download`, which referenced objects it
|
|
63
|
+
* holds: a held object answers with a download action, a missing one with
|
|
64
|
+
* error 404. Actions, their URLs and headers are never retained.
|
|
65
|
+
*/
|
|
66
|
+
export declare const giteaLfsPresence: (sourceArg: Pick<IGiteaMirrorSource, 'baseUrl' | 'owner' | 'name' | 'lfsClient' | 'signal'>, pointersArg: Array<{
|
|
67
|
+
oid: string;
|
|
68
|
+
sizeBytes: number;
|
|
69
|
+
}>) => Promise<ILfsCounts>;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import * as validation from './forgeinventory.validation.js';
|
|
3
|
+
/** Objects per LFS batch request. */
|
|
4
|
+
const lfsBatchSize = 100;
|
|
5
|
+
const lfsMediaType = 'application/vnd.git-lfs+json';
|
|
6
|
+
/**
|
|
7
|
+
* Scratch space reserved for one mirror: twice the size Gitea reports, because
|
|
8
|
+
* a clone holds received and indexed packs, plus fixed headroom for refs, the
|
|
9
|
+
* index and a size Gitea has not updated yet.
|
|
10
|
+
*/
|
|
11
|
+
export const mirrorScratchBytes = (reportedSizeKiBArg) => reportedSizeKiBArg * 1024n * 2n + 64n * 1024n * 1024n;
|
|
12
|
+
/** Bytes an unprivileged process may still write to the file system holding `directoryArg`. */
|
|
13
|
+
const statfsFreeBytes = async (directoryArg) => {
|
|
14
|
+
const stats = await plugins.fs.statfs(directoryArg, { bigint: true });
|
|
15
|
+
return stats.bavail * stats.bsize;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Admits mirror clones on one file system in arrival order while the scratch
|
|
19
|
+
* they reserve fits its free space. A clone's reservation counts in full until
|
|
20
|
+
* it is released, although part of it is already written, so the check never
|
|
21
|
+
* admits more than fits. A clone that does not fit waits until the clones in
|
|
22
|
+
* flight release their reservations; one that does not fit even alone is
|
|
23
|
+
* refused.
|
|
24
|
+
*/
|
|
25
|
+
export class GitScratchBudget {
|
|
26
|
+
#freeBytes;
|
|
27
|
+
#reserved = 0n;
|
|
28
|
+
/** Settles when the clones ahead of the next arrival have been admitted or refused. */
|
|
29
|
+
#queue = Promise.resolve();
|
|
30
|
+
#releaseListeners = new Set();
|
|
31
|
+
constructor(freeBytesArg = statfsFreeBytes) {
|
|
32
|
+
this.#freeBytes = freeBytesArg;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reserves `bytesArg` in `directoryArg` and resolves with the function that
|
|
36
|
+
* releases them, or with null when they exceed the free space with no other
|
|
37
|
+
* clone in flight. Rejects when the free space cannot be read or `signalArg`
|
|
38
|
+
* aborts.
|
|
39
|
+
*/
|
|
40
|
+
async reserve(directoryArg, bytesArg, signalArg) {
|
|
41
|
+
const ahead = this.#queue;
|
|
42
|
+
let admitted;
|
|
43
|
+
const turn = new Promise((resolve) => { admitted = resolve; });
|
|
44
|
+
// Followers wait for this turn and every turn ahead of it, even when this one ends early.
|
|
45
|
+
this.#queue = ahead.then(() => turn);
|
|
46
|
+
try {
|
|
47
|
+
await this.#until(ahead, signalArg);
|
|
48
|
+
for (;;) {
|
|
49
|
+
// Listens before reading the free space, so a release while it is read is not missed.
|
|
50
|
+
let listener;
|
|
51
|
+
const released = new Promise((resolve) => { listener = resolve; });
|
|
52
|
+
this.#releaseListeners.add(listener);
|
|
53
|
+
try {
|
|
54
|
+
const free = await this.#freeBytes(directoryArg);
|
|
55
|
+
if (this.#reserved + bytesArg <= free) {
|
|
56
|
+
this.#reserved += bytesArg;
|
|
57
|
+
return this.#releaser(bytesArg);
|
|
58
|
+
}
|
|
59
|
+
if (this.#reserved === 0n)
|
|
60
|
+
return null;
|
|
61
|
+
await this.#until(released, signalArg);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
this.#releaseListeners.delete(listener);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
admitted();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
#releaser(bytesArg) {
|
|
73
|
+
let held = true;
|
|
74
|
+
return () => {
|
|
75
|
+
if (!held)
|
|
76
|
+
return;
|
|
77
|
+
held = false;
|
|
78
|
+
this.#reserved -= bytesArg;
|
|
79
|
+
for (const listener of this.#releaseListeners)
|
|
80
|
+
listener();
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async #until(promiseArg, signalArg) {
|
|
84
|
+
signalArg?.throwIfAborted();
|
|
85
|
+
if (!signalArg)
|
|
86
|
+
return promiseArg;
|
|
87
|
+
let abort;
|
|
88
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
89
|
+
abort = () => reject(signalArg.reason);
|
|
90
|
+
signalArg.addEventListener('abort', abort, { once: true });
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
await Promise.race([promiseArg, aborted]);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
signalArg.removeEventListener('abort', abort);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** One budget per file system, shared by every reader in the process. */
|
|
101
|
+
const scratchBudgets = new Map();
|
|
102
|
+
const scratchBudget = async (workDirArg) => {
|
|
103
|
+
const device = (await plugins.fs.stat(workDirArg, { bigint: true })).dev;
|
|
104
|
+
let budget = scratchBudgets.get(device);
|
|
105
|
+
if (!budget) {
|
|
106
|
+
budget = new GitScratchBudget();
|
|
107
|
+
scratchBudgets.set(device, budget);
|
|
108
|
+
}
|
|
109
|
+
return budget;
|
|
110
|
+
};
|
|
111
|
+
const complete = (valueArg) => ({ status: 'complete', value: BigInt(valueArg).toString() });
|
|
112
|
+
const partial = (observedArg, reasonArg) => ({ status: 'partial', observedValue: BigInt(observedArg).toString(), reason: reasonArg });
|
|
113
|
+
const failedGit = (reasonArg) => {
|
|
114
|
+
const failed = partial(0, reasonArg);
|
|
115
|
+
return { branches: failed, tags: failed, pullRequestRefs: failed, otherRefs: failed, objects: failed, objectBytes: failed };
|
|
116
|
+
};
|
|
117
|
+
const failedLfs = (reasonArg) => {
|
|
118
|
+
const failed = partial(0, reasonArg);
|
|
119
|
+
return { objects: failed, bytes: failed, missingObjects: failed };
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Fixed reason for a failed step. GitManager errors are never passed on: they
|
|
123
|
+
* are redacted, but still remote text.
|
|
124
|
+
*/
|
|
125
|
+
const stepFailure = (errorArg, stepArg, signalArg) => signalArg?.aborted ? 'request-aborted' : errorArg instanceof validation.ForgeInventoryError ? errorArg.code : stepArg;
|
|
126
|
+
/**
|
|
127
|
+
* Measures one repository at the Git level: clones every ref Gitea advertises
|
|
128
|
+
* into a temporary mirror, counts its refs and objects, finds the LFS pointers
|
|
129
|
+
* of every reachable revision and checks through the LFS batch API which of
|
|
130
|
+
* their objects Gitea holds. The mirror is removed afterwards. The clone starts
|
|
131
|
+
* only once its scratch reservation fits the work directory's free space; a
|
|
132
|
+
* repository too large for it fails with `git-workdir-too-small`.
|
|
133
|
+
*/
|
|
134
|
+
export const measureGiteaMirror = async (sourceArg) => {
|
|
135
|
+
const { signal } = sourceArg;
|
|
136
|
+
if (signal?.aborted)
|
|
137
|
+
throw new validation.ForgeInventoryError('request-aborted');
|
|
138
|
+
let release;
|
|
139
|
+
try {
|
|
140
|
+
release = await (await scratchBudget(sourceArg.workDir)).reserve(sourceArg.workDir, mirrorScratchBytes(sourceArg.reportedSizeKiB), signal);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
const failure = signal?.aborted ? 'request-aborted' : 'git-workdir-failed';
|
|
144
|
+
return { git: failedGit(failure), lfs: failedLfs(failure) };
|
|
145
|
+
}
|
|
146
|
+
if (release === null)
|
|
147
|
+
return { git: failedGit('git-workdir-too-small'), lfs: failedLfs('git-workdir-too-small') };
|
|
148
|
+
try {
|
|
149
|
+
return await measureReservedMirror(sourceArg);
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
release();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const measureReservedMirror = async (sourceArg) => {
|
|
156
|
+
const { signal } = sourceArg;
|
|
157
|
+
const { GitManager } = await plugins.getGitManager();
|
|
158
|
+
let directory;
|
|
159
|
+
try {
|
|
160
|
+
directory = await plugins.fs.mkdtemp(plugins.path.join(sourceArg.workDir, 'mirror-'));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return { git: failedGit('git-workdir-failed'), lfs: failedLfs('git-workdir-failed') };
|
|
164
|
+
}
|
|
165
|
+
const manager = new GitManager({ dataDir: directory });
|
|
166
|
+
// Stopping the engine ends the operation in flight.
|
|
167
|
+
const abort = () => { void manager.stop().catch(() => undefined); };
|
|
168
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
169
|
+
const selector = { org: 'source', repo: 'mirror' };
|
|
170
|
+
try {
|
|
171
|
+
try {
|
|
172
|
+
// An abort while the engine module loaded or the directory was created reached no listener.
|
|
173
|
+
signal?.throwIfAborted();
|
|
174
|
+
await manager.start();
|
|
175
|
+
await manager.importMirror({
|
|
176
|
+
...selector,
|
|
177
|
+
remoteUrl: `${sourceArg.baseUrl}/${encodeURIComponent(sourceArg.owner)}/${encodeURIComponent(sourceArg.name)}.git`,
|
|
178
|
+
credential: { kind: 'basic', username: sourceArg.username, password: sourceArg.token },
|
|
179
|
+
caCertificates: sourceArg.caCertificates,
|
|
180
|
+
timeoutMs: sourceArg.timeoutMs,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const failure = stepFailure(error, 'git-mirror-failed', signal);
|
|
185
|
+
return { git: failedGit(failure), lfs: failedLfs(failure) };
|
|
186
|
+
}
|
|
187
|
+
const git = await measureRefsAndObjects(manager, selector, signal);
|
|
188
|
+
let pointers;
|
|
189
|
+
try {
|
|
190
|
+
pointers = await manager.listRepositoryLfsPointers(selector);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
return { git, lfs: failedLfs(stepFailure(error, 'lfs-pointer-scan-failed', signal)) };
|
|
194
|
+
}
|
|
195
|
+
return { git, lfs: await giteaLfsPresence(sourceArg, pointers) };
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
signal?.removeEventListener('abort', abort);
|
|
199
|
+
await manager.stop().catch(() => undefined);
|
|
200
|
+
await plugins.fs.rm(directory, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const measureRefsAndObjects = async (managerArg, selectorArg, signalArg) => {
|
|
204
|
+
const counts = { branches: 0, tags: 0, pullRequestRefs: 0, otherRefs: 0 };
|
|
205
|
+
let refsFailure = null;
|
|
206
|
+
try {
|
|
207
|
+
let cursor;
|
|
208
|
+
do {
|
|
209
|
+
const page = await managerArg.listRefs({ ...selectorArg, cursor });
|
|
210
|
+
// Nothing writes to the private mirror, so a changed inventory is a failure.
|
|
211
|
+
if (page.status === 'stale')
|
|
212
|
+
throw new validation.ForgeInventoryError('git-refs-changed');
|
|
213
|
+
for (const ref of page.refs) {
|
|
214
|
+
if (ref.name.startsWith('refs/heads/'))
|
|
215
|
+
counts.branches++;
|
|
216
|
+
else if (ref.name.startsWith('refs/tags/'))
|
|
217
|
+
counts.tags++;
|
|
218
|
+
else if (ref.name.startsWith('refs/pull/'))
|
|
219
|
+
counts.pullRequestRefs++;
|
|
220
|
+
else
|
|
221
|
+
counts.otherRefs++;
|
|
222
|
+
}
|
|
223
|
+
cursor = page.pagination.status === 'more' ? page.pagination.cursor : undefined;
|
|
224
|
+
} while (cursor !== undefined);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
refsFailure = stepFailure(error, 'git-refs-failed', signalArg);
|
|
228
|
+
}
|
|
229
|
+
const refs = (valueArg) => refsFailure === null ? complete(valueArg) : partial(0, refsFailure);
|
|
230
|
+
let objects;
|
|
231
|
+
let objectBytes;
|
|
232
|
+
try {
|
|
233
|
+
const size = await managerArg.getRepositorySize(selectorArg);
|
|
234
|
+
objects = complete(size.objectCount);
|
|
235
|
+
objectBytes = complete(size.objectBytes);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
objects = objectBytes = partial(0, stepFailure(error, 'git-size-failed', signalArg));
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
branches: refs(counts.branches), tags: refs(counts.tags), pullRequestRefs: refs(counts.pullRequestRefs), otherRefs: refs(counts.otherRefs),
|
|
242
|
+
objects, objectBytes,
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Asks Gitea's LFS batch API, as a `download`, which referenced objects it
|
|
247
|
+
* holds: a held object answers with a download action, a missing one with
|
|
248
|
+
* error 404. Actions, their URLs and headers are never retained.
|
|
249
|
+
*/
|
|
250
|
+
export const giteaLfsPresence = async (sourceArg, pointersArg) => {
|
|
251
|
+
const url = new URL(`${sourceArg.baseUrl}/${encodeURIComponent(sourceArg.owner)}/${encodeURIComponent(sourceArg.name)}.git/info/lfs/objects/batch`);
|
|
252
|
+
let held = 0n;
|
|
253
|
+
let heldBytes = 0n;
|
|
254
|
+
let missing = 0n;
|
|
255
|
+
for (let index = 0; index < pointersArg.length; index += lfsBatchSize) {
|
|
256
|
+
const chunk = pointersArg.slice(index, index + lfsBatchSize);
|
|
257
|
+
try {
|
|
258
|
+
const response = await sourceArg.lfsClient.request(url, {
|
|
259
|
+
signal: sourceArg.signal,
|
|
260
|
+
mediaType: lfsMediaType,
|
|
261
|
+
json: { operation: 'download', transfers: ['basic'], objects: chunk.map((pointerArg) => ({ oid: pointerArg.oid, size: pointerArg.sizeBytes })) },
|
|
262
|
+
});
|
|
263
|
+
const objects = validation.record(response.body).objects;
|
|
264
|
+
if (!Array.isArray(objects) || objects.length !== chunk.length)
|
|
265
|
+
throw new validation.ForgeInventoryError('invalid-lfs-batch');
|
|
266
|
+
const requested = new Map(chunk.map((pointerArg) => [pointerArg.oid, pointerArg.sizeBytes]));
|
|
267
|
+
for (const objectArg of objects) {
|
|
268
|
+
const object = validation.record(objectArg);
|
|
269
|
+
const size = typeof object.oid === 'string' ? requested.get(object.oid) : undefined;
|
|
270
|
+
if (size === undefined || object.size !== size)
|
|
271
|
+
throw new validation.ForgeInventoryError('invalid-lfs-batch');
|
|
272
|
+
requested.delete(object.oid);
|
|
273
|
+
const download = object.actions === undefined || object.actions === null ? undefined : validation.record(object.actions).download;
|
|
274
|
+
if (download !== undefined && download !== null && object.error === undefined) {
|
|
275
|
+
held++;
|
|
276
|
+
heldBytes += BigInt(size);
|
|
277
|
+
}
|
|
278
|
+
else if (object.error !== undefined && validation.record(object.error).code === 404) {
|
|
279
|
+
missing++;
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
// A size that disagrees with the stored object answers 422.
|
|
283
|
+
throw new validation.ForgeInventoryError('lfs-object-unavailable');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
const failure = stepFailure(error, 'lfs-batch-failed', sourceArg.signal);
|
|
289
|
+
return { objects: partial(held, failure), bytes: partial(heldBytes, failure), missingObjects: partial(missing, failure) };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { objects: complete(held), bytes: complete(heldBytes), missingObjects: complete(missing) };
|
|
293
|
+
};
|
|
294
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2l0ZWFzb3VyY2VpbnZlbnRvcnkuZ2l0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvZ2l0ZWFzb3VyY2VpbnZlbnRvcnkuZ2l0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxVQUFVLE1BQU0sZ0NBQWdDLENBQUM7QUEyQjdELHFDQUFxQztBQUNyQyxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUM7QUFDekIsTUFBTSxZQUFZLEdBQUcsOEJBQThCLENBQUM7QUFFcEQ7Ozs7R0FJRztBQUNILE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLENBQUMsa0JBQTBCLEVBQVUsRUFBRSxDQUFDLGtCQUFrQixHQUFHLEtBQUssR0FBRyxFQUFFLEdBQUcsR0FBRyxHQUFHLEtBQUssR0FBRyxLQUFLLENBQUM7QUFFaEksK0ZBQStGO0FBQy9GLE1BQU0sZUFBZSxHQUFHLEtBQUssRUFBRSxZQUFvQixFQUFtQixFQUFFO0lBQ3RFLE1BQU0sS0FBSyxHQUFHLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsWUFBWSxFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFDdEUsT0FBTyxLQUFLLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUM7QUFDcEMsQ0FBQyxDQUFDO0FBRUY7Ozs7Ozs7R0FPRztBQUNILE1BQU0sT0FBTyxnQkFBZ0I7SUFDbEIsVUFBVSxDQUE0QztJQUMvRCxTQUFTLEdBQUcsRUFBRSxDQUFDO0lBQ2YsdUZBQXVGO0lBQ3ZGLE1BQU0sR0FBa0IsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2pDLGlCQUFpQixHQUFHLElBQUksR0FBRyxFQUFjLENBQUM7SUFFbkQsWUFBWSxZQUFZLEdBQThDLGVBQWU7UUFDbkYsSUFBSSxDQUFDLFVBQVUsR0FBRyxZQUFZLENBQUM7SUFDakMsQ0FBQztJ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
|
package/dist_ts/plugins.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type * as fossInterfaces from '@foss.global/interfaces';
|
|
2
2
|
import * as crypto from 'node:crypto';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
3
4
|
import * as https from 'node:https';
|
|
4
|
-
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
export { crypto, fs, https, path };
|
|
5
7
|
import * as interfaces from './interfaces/index.js';
|
|
6
8
|
export { interfaces, };
|
|
7
9
|
import * as lik from '@push.rocks/lik';
|
|
@@ -15,4 +17,5 @@ export type TGitManagerTreeEntry = import('@foss.global/gitmanager').IGitManager
|
|
|
15
17
|
export type TGitManagerTag = import('@foss.global/gitmanager').IGitManagerTag;
|
|
16
18
|
export type TGitManagerDiff = import('@foss.global/gitmanager').IGitManagerDiff;
|
|
17
19
|
export declare const getSmartnpm: () => Promise<typeof import("@push.rocks/smartnpm")>;
|
|
20
|
+
export declare const getGitManager: () => Promise<typeof import("@foss.global/gitmanager")>;
|
|
18
21
|
export { smarttime, lik, };
|
package/dist_ts/plugins.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
2
3
|
import * as https from 'node:https';
|
|
3
|
-
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
export { crypto, fs, https, path };
|
|
4
6
|
// module
|
|
5
7
|
import * as interfaces from './interfaces/index.js';
|
|
6
8
|
export { interfaces, };
|
|
@@ -8,5 +10,6 @@ export { interfaces, };
|
|
|
8
10
|
import * as lik from '@push.rocks/lik';
|
|
9
11
|
import * as smarttime from '@push.rocks/smarttime';
|
|
10
12
|
export const getSmartnpm = async () => import('@push.rocks/smartnpm');
|
|
13
|
+
export const getGitManager = async () => import('@foss.global/gitmanager');
|
|
11
14
|
export { smarttime, lik, };
|
|
12
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
15
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxLQUFLLE1BQU0sTUFBTSxhQUFhLENBQUM7QUFDdEMsT0FBTyxLQUFLLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUN2QyxPQUFPLEtBQUssS0FBSyxNQUFNLFlBQVksQ0FBQztBQUNwQyxPQUFPLEtBQUssSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUNsQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFFbkMsU0FBUztBQUNULE9BQU8sS0FBSyxVQUFVLE1BQU0sdUJBQXVCLENBQUM7QUFFcEQsT0FBTyxFQUNMLFVBQVUsR0FDWCxDQUFBO0FBRUQsY0FBYztBQUNkLE9BQU8sS0FBSyxHQUFHLE1BQU0saUJBQWlCLENBQUM7QUFDdkMsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQVduRCxNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsS0FBSyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsc0JBQXNCLENBQUMsQ0FBQztBQUN0RSxNQUFNLENBQUMsTUFBTSxhQUFhLEdBQUcsS0FBSyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUUzRSxPQUFPLEVBQ0wsU0FBUyxFQUNULEdBQUcsR0FDSixDQUFBIn0=
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foss.global/codefeed",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"author": "Task Venture Capital GmbH",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@foss.global/forgefixtures": "^0.3.
|
|
14
|
+
"@foss.global/forgefixtures": "^0.3.1",
|
|
15
15
|
"@git.zone/tsbuild": "^6.0.2",
|
|
16
16
|
"@git.zone/tsbundle": "^3.2.1",
|
|
17
17
|
"@git.zone/tsrun": "^3.0.3",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"@types/node": "^26.6.4"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@foss.global/gitmanager": "^2.
|
|
24
|
-
"@foss.global/interfaces": "^
|
|
23
|
+
"@foss.global/gitmanager": "^2.3.0",
|
|
24
|
+
"@foss.global/interfaces": "^2.0.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
|
@@ -80,7 +80,7 @@ The implementation has synthetic protocol fixtures based on the [Gitea 1.24.7 AP
|
|
|
80
80
|
|
|
81
81
|
### Authenticated Gitea source inventory
|
|
82
82
|
|
|
83
|
-
`GiteaSourceInventory` measures a whole Gitea instance before its one-time migration, read with a site administrator's token. It returns the `IForgeSourceInventory` contract from `@foss.global/interfaces`: per-instance, per-owner and per-repository counts with totals, where every measure is `complete`, `partial` (attempted, did not finish) or `notMeasured` (not attempted). The inventory is complete only when discovery finished and every measure is complete; otherwise `inventoryComplete` is false and `incompleteReasons` names each gap. It
|
|
83
|
+
`GiteaSourceInventory` measures a whole Gitea instance before its one-time migration, read with a site administrator's token. It returns the `IForgeSourceInventory` contract from `@foss.global/interfaces`: per-instance, per-owner and per-repository counts with totals, where every measure is `complete`, `partial` (attempted, did not finish) or `notMeasured` (not attempted). The inventory is complete only when discovery finished and every measure is complete; otherwise `inventoryComplete` is false and `incompleteReasons` names each gap. It never writes to the forge: it sends REST GET requests, clones each repository with Git, and asks the LFS batch API, as a `download`, which objects Gitea holds.
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
86
|
import { GiteaSourceInventory, composeGiteaSourceInventory } from '@foss.global/codefeed';
|
|
@@ -89,7 +89,8 @@ const reader = new GiteaSourceInventory({
|
|
|
89
89
|
instanceId: 'code-foss-global',
|
|
90
90
|
baseUrl: 'https://gitea.example.com', // HTTPS only
|
|
91
91
|
token: siteAdminToken, // read it from a file; never pass it on a command line
|
|
92
|
-
|
|
92
|
+
gitWorkDir: '/var/lib/inventory/mirrors', // absolute, private; holds one temporary mirror per repository in flight
|
|
93
|
+
// caCertificates: [privateCaPem], gitTimeoutMs: 30 * 60 * 1000, pageSize: 50, maxPages: 10_000, requestTimeoutMs: 60_000, maxResponseBytes: 16 * 1024 * 1024,
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
const discovery = await reader.discover(); // rejects with `reader-not-site-admin` for other tokens
|
|
@@ -102,12 +103,13 @@ const inventory = composeGiteaSourceInventory(discovery, results, [{ package: '@
|
|
|
102
103
|
|
|
103
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.
|
|
104
105
|
|
|
105
|
-
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, 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.
|
|
106
|
+
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.
|
|
106
107
|
|
|
107
108
|
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.
|
|
108
109
|
|
|
110
|
+
Git-level measures come from a mirror clone of every ref Gitea advertises, `refs/pull/*` included, made with `@foss.global/gitmanager` in a fresh directory below `gitWorkDir` and removed afterwards. The reader's login and token travel as Basic credentials outside the URL, the Git command line and Git configuration; the private CA list applies to the clone as well. The mirror's distinct LFS pointers, from every reachable revision, are checked against Gitea in batches of 100: an object with a download action counts as held, one answered with error 404 as missing, and any other answer makes the LFS measures `partial`. A clone that fails or exceeds `gitTimeoutMs` makes every Git and LFS measure of that repository `partial`; only the API-reported size stays complete. A clone starts only once its scratch reservation, twice the reported size plus 64 MiB, fits the free space of the file system that holds `gitWorkDir`, counting the reservations of the clones already in flight from every reader in the process. A clone that does not fit waits for them, in arrival order; one that does not fit even alone makes every Git and LFS measure of its repository `partial` with `git-workdir-too-small`, so the inventory stays incomplete. Size `gitWorkDir` for the largest repositories measured at once.
|
|
111
|
+
|
|
109
112
|
Known gaps that keep an inventory incomplete:
|
|
110
|
-
- LFS objects are `notMeasured`: Gitea's API does not list them.
|
|
111
113
|
- 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.
|
|
112
114
|
|
|
113
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.
|
|
@@ -271,7 +273,7 @@ The repo contains:
|
|
|
271
273
|
- A mocked pagination test that does not require network.
|
|
272
274
|
- A GitManager provider mapping test using a local test stub.
|
|
273
275
|
- A default-deny GitManager public-client mapping test using a local test stub.
|
|
274
|
-
- A mocked `GiteaSourceInventory` test, and an opt-in one against a Docker Gitea from `@foss.global/forgefixtures`, run with `FORGEFIXTURES=gitea pnpm test` (Docker required).
|
|
276
|
+
- A mocked `GiteaSourceInventory` test, a Git-level one against a local HTTPS Git, LFS batch and REST server (needs `openssl` and `git http-backend`), and an opt-in one against a Docker Gitea from `@foss.global/forgefixtures`, run with `FORGEFIXTURES=gitea pnpm test` (Docker required).
|
|
275
277
|
|
|
276
278
|
Run tests:
|
|
277
279
|
|