@foss.global/gitmanager 2.2.0 → 2.4.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 +36 -0
- package/dist_rust/rustgitmanager_linux_amd64 +0 -0
- package/dist_rust/rustgitmanager_linux_amd64.tsrust-build.json +5 -5
- package/dist_rust/rustgitmanager_linux_arm64 +0 -0
- package/dist_rust/rustgitmanager_linux_arm64.tsrust-build.json +5 -5
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.gitmanager.d.ts +92 -3
- package/dist_ts/classes.gitmanager.js +202 -16
- package/dist_ts/classes.gitmanagererror.js +4 -1
- package/dist_ts/classes.repositoryservice.d.ts +9 -0
- package/dist_ts/classes.repositoryservice.js +7 -3
- package/dist_ts/models.d.ts +28 -2
- package/dist_ts/rust-gitmanager-bridge.d.ts +16 -4
- package/dist_ts/rust-gitmanager-bridge.js +69 -9
- package/package.json +3 -3
- package/readme.md +107 -7
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.gitmanager.ts +244 -21
- package/ts/classes.gitmanagererror.ts +3 -0
- package/ts/classes.repositoryservice.ts +13 -0
- package/ts/models.ts +44 -1
- package/ts/rust-gitmanager-bridge.ts +123 -13
package/ts/classes.gitmanager.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import type {
|
|
3
3
|
IGitManagerAncestryRequest,
|
|
4
|
+
IGitManagerAtomicPushRequest,
|
|
5
|
+
IGitManagerAtomicPushResult,
|
|
4
6
|
IGitManagerArchiveInspection,
|
|
5
7
|
IGitManagerArchiveInspectionOptions,
|
|
6
8
|
IGitManagerArchiveInspectionRequest,
|
|
@@ -18,6 +20,8 @@ import type {
|
|
|
18
20
|
IGitManagerMergeResult,
|
|
19
21
|
IGitManagerReadFileRequest,
|
|
20
22
|
IGitManagerRefRequest,
|
|
23
|
+
IGitManagerRefreshMirrorRequest,
|
|
24
|
+
IGitManagerRemoteTransferOptions,
|
|
21
25
|
IGitManagerProtectedRefsApplyResult,
|
|
22
26
|
IGitManagerSmartHttpRepositoryProtectedRefs,
|
|
23
27
|
IGitManagerRepositoryBundle,
|
|
@@ -28,7 +32,9 @@ import type {
|
|
|
28
32
|
IGitManagerRepositoryServiceRequest,
|
|
29
33
|
IGitManagerRepositoryInfo,
|
|
30
34
|
IGitManagerRepositorySelector,
|
|
35
|
+
IGitManagerRepositorySize,
|
|
31
36
|
IGitManagerRestoreBundleRequest,
|
|
37
|
+
IGitManagerSetDefaultBranchRequest,
|
|
32
38
|
IGitManagerSmartHttpRepositoryAccess,
|
|
33
39
|
IGitManagerSmartHttpRepositoryVisibility,
|
|
34
40
|
IGitManagerStatus,
|
|
@@ -36,6 +42,7 @@ import type {
|
|
|
36
42
|
IGitManagerTagRequest,
|
|
37
43
|
IGitManagerTreeEntry,
|
|
38
44
|
IGitManagerTreeRequest,
|
|
45
|
+
IGitManagerTransferControlOptions,
|
|
39
46
|
IGitManagerOptions,
|
|
40
47
|
IGitManagerReadinessOptions,
|
|
41
48
|
IRustGitManagerConfig,
|
|
@@ -49,6 +56,66 @@ import { runArchiveInspection } from './classes.archiveinspection.js';
|
|
|
49
56
|
|
|
50
57
|
const readinessDefaultTimeoutMs = 2_000;
|
|
51
58
|
const readinessMaximumTimeoutMs = 30_000;
|
|
59
|
+
const remoteTransferMaximumTimeoutMs = 21_600_000;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Checks transfer options before they reach the engine, which answers a
|
|
63
|
+
* malformed credential-bearing request with fixed text only. Messages never
|
|
64
|
+
* quote a credential value.
|
|
65
|
+
*/
|
|
66
|
+
const assertRemoteTransferOptions = (optionsArg: IGitManagerRemoteTransferOptions): void => {
|
|
67
|
+
const { credential, caCertificates, timeoutMs } = optionsArg;
|
|
68
|
+
if (credential !== undefined) {
|
|
69
|
+
if (
|
|
70
|
+
credential === null
|
|
71
|
+
|| typeof credential !== 'object'
|
|
72
|
+
|| credential.kind !== 'basic'
|
|
73
|
+
|| typeof credential.username !== 'string'
|
|
74
|
+
|| typeof credential.password !== 'string'
|
|
75
|
+
|| Object.keys(credential).some((key) => !['kind', 'username', 'password'].includes(key))
|
|
76
|
+
) {
|
|
77
|
+
throw new TypeError('credential must be { kind: "basic", username: string, password: string }');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
caCertificates !== undefined
|
|
82
|
+
&& (!Array.isArray(caCertificates) || caCertificates.some((certificate) => typeof certificate !== 'string'))
|
|
83
|
+
) {
|
|
84
|
+
throw new TypeError('caCertificates must be an array of PEM strings');
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
timeoutMs !== undefined
|
|
88
|
+
&& (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > remoteTransferMaximumTimeoutMs)
|
|
89
|
+
) {
|
|
90
|
+
throw new RangeError(`timeoutMs must be an integer between 1 and ${remoteTransferMaximumTimeoutMs}`);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Checks the shape of push updates before they reach the engine, which
|
|
96
|
+
* answers a malformed request with fixed text only. Ref names and object IDs
|
|
97
|
+
* are validated by the engine.
|
|
98
|
+
*/
|
|
99
|
+
const assertAtomicPushUpdates = (updatesArg: unknown): void => {
|
|
100
|
+
const isObjectIdOrNull = (valueArg: unknown): boolean => valueArg === null || typeof valueArg === 'string';
|
|
101
|
+
if (
|
|
102
|
+
!Array.isArray(updatesArg)
|
|
103
|
+
|| updatesArg.some((update: unknown) => {
|
|
104
|
+
if (update === null || typeof update !== 'object') {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const record = update as Record<string, unknown>;
|
|
108
|
+
return typeof record.ref !== 'string'
|
|
109
|
+
|| !isObjectIdOrNull(record.expectedOldObjectId)
|
|
110
|
+
|| !isObjectIdOrNull(record.newObjectId)
|
|
111
|
+
|| Object.keys(record).some((key) => !['ref', 'expectedOldObjectId', 'newObjectId'].includes(key));
|
|
112
|
+
})
|
|
113
|
+
) {
|
|
114
|
+
throw new TypeError(
|
|
115
|
+
'updates must be an array of { ref: string, expectedOldObjectId: string | null, newObjectId: string | null }',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
52
119
|
|
|
53
120
|
type TGitManagerReadinessAbortSource = 'caller' | 'deadline';
|
|
54
121
|
type TGitManagerReadinessProbeOutcome = 'ready' | 'aborted' | 'unavailable';
|
|
@@ -339,14 +406,95 @@ export class GitManager extends plugins.EventEmitter {
|
|
|
339
406
|
return this.bridge.initBareRepository(requestArg);
|
|
340
407
|
}
|
|
341
408
|
|
|
342
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Clones every ref of `remoteUrl` into a new mirror repository whose HEAD
|
|
411
|
+
* points at the source's default branch. Pass a credential as
|
|
412
|
+
* `credential`, never inside the URL: the engine hands it to Git only
|
|
413
|
+
* through the process environment and removes it from errors.
|
|
414
|
+
*
|
|
415
|
+
* The import runs alongside other commands and holds `org/repo` until it
|
|
416
|
+
* ends: another mirror transfer of it or a write to it (such as
|
|
417
|
+
* `initBareRepository` or `restoreRepositoryBundle`) fails with
|
|
418
|
+
* `repositoryBusy`. Aborting `optionsArg.signal`, or `stop()`, terminates
|
|
419
|
+
* the clone and removes the partial repository; the call then rejects with
|
|
420
|
+
* `transferAborted`, and no repository is left behind. An import whose
|
|
421
|
+
* clone already completed when the abort arrived returns its result.
|
|
422
|
+
*/
|
|
423
|
+
public async importMirror(
|
|
424
|
+
requestArg: IGitManagerImportMirrorRequest,
|
|
425
|
+
optionsArg: IGitManagerTransferControlOptions = {},
|
|
426
|
+
): Promise<IGitManagerRepositoryInfo> {
|
|
427
|
+
this.assertRunning();
|
|
428
|
+
assertRemoteTransferOptions(requestArg);
|
|
429
|
+
return this.bridge.importMirror(requestArg, optionsArg);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Fetches every ref of an imported mirror's remote again, with the same
|
|
434
|
+
* transfer options as `importMirror`, and points HEAD at the source's
|
|
435
|
+
* current default branch when the source advertises one.
|
|
436
|
+
*
|
|
437
|
+
* The refresh holds `org/repo` like `importMirror`: writes to it, including
|
|
438
|
+
* Smart HTTP pushes, are refused with `repositoryBusy` while it runs, and it
|
|
439
|
+
* is refused while such a write is in progress; reads proceed. Aborting
|
|
440
|
+
* `optionsArg.signal`, or `stop()`, terminates the fetch and rejects with
|
|
441
|
+
* `transferAborted`. The mirror's refs then stay as they were or, when the
|
|
442
|
+
* abort came after `git fetch --atomic` committed its ref transaction, are
|
|
443
|
+
* fully refreshed; HEAD moves only when the refresh completes.
|
|
444
|
+
*/
|
|
445
|
+
public async refreshMirror(
|
|
446
|
+
requestArg: IGitManagerRefreshMirrorRequest,
|
|
447
|
+
optionsArg: IGitManagerTransferControlOptions = {},
|
|
448
|
+
): Promise<IGitManagerRepositoryInfo> {
|
|
449
|
+
this.assertRunning();
|
|
450
|
+
assertRemoteTransferOptions(requestArg);
|
|
451
|
+
return this.bridge.refreshMirror(requestArg, optionsArg);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Pushes leased ref updates to `remoteUrl` as one atomic transaction: the
|
|
456
|
+
* remote applies all of them or none. The remote must advertise the
|
|
457
|
+
* `atomic` receive-pack capability; otherwise a `GitManagerError` with code
|
|
458
|
+
* `atomicPushUnsupported` is thrown and nothing is pushed. `remoteUrl` must
|
|
459
|
+
* be `https`, or `http` to a loopback IP address (`127.0.0.0/8`, `[::1]`;
|
|
460
|
+
* never a host name such as `localhost`), without user information; the
|
|
461
|
+
* credential travels as for `importMirror`. A ref that already holds its
|
|
462
|
+
* `newObjectId` is reported `ok` without its lease being checked; it is not
|
|
463
|
+
* changed, so `ok` never means the lease matched. Any refusal the remote
|
|
464
|
+
* reports per ref returns status `rejected`, with the refused refs `stale`
|
|
465
|
+
* or `rejected` and the up-to-date ones `ok`; the call throws only when the
|
|
466
|
+
* remote's answer does not say what it did.
|
|
467
|
+
*
|
|
468
|
+
* The push only reads the managed repository, so it runs alongside other
|
|
469
|
+
* commands and other transfers. Aborting `optionsArg.signal`, or `stop()`,
|
|
470
|
+
* rejects with `transferAborted`. An abort before `git push` starts (a
|
|
471
|
+
* signal already aborted, or an abort during the local object check)
|
|
472
|
+
* changes nothing on the remote. An abort while it runs terminates it, and
|
|
473
|
+
* the remote then holds all of the updates or none of them, unknown which:
|
|
474
|
+
* read the remote refs again before retrying.
|
|
475
|
+
*/
|
|
476
|
+
public async pushRefs(
|
|
477
|
+
requestArg: IGitManagerAtomicPushRequest,
|
|
478
|
+
optionsArg: IGitManagerTransferControlOptions = {},
|
|
479
|
+
): Promise<IGitManagerAtomicPushResult> {
|
|
343
480
|
this.assertRunning();
|
|
344
|
-
|
|
481
|
+
assertRemoteTransferOptions(requestArg);
|
|
482
|
+
assertAtomicPushUpdates(requestArg.updates);
|
|
483
|
+
return this.bridge.pushRefs(requestArg, optionsArg);
|
|
345
484
|
}
|
|
346
485
|
|
|
347
|
-
|
|
486
|
+
/** Points the repository's HEAD at an existing branch; a missing branch is refused and HEAD stays unchanged. */
|
|
487
|
+
public async setDefaultBranch(
|
|
488
|
+
requestArg: IGitManagerSetDefaultBranchRequest,
|
|
489
|
+
): Promise<IGitManagerRepositoryInfo> {
|
|
348
490
|
this.assertRunning();
|
|
349
|
-
return this.bridge.
|
|
491
|
+
return this.bridge.setDefaultBranch(requestArg);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Counts the repository's objects and their exact Git bytes. */
|
|
495
|
+
public async getRepositorySize(requestArg: IGitManagerRepositorySelector): Promise<IGitManagerRepositorySize> {
|
|
496
|
+
this.assertRunning();
|
|
497
|
+
return this.bridge.getRepositorySize(requestArg);
|
|
350
498
|
}
|
|
351
499
|
|
|
352
500
|
public async verifyRepository(requestArg: IGitManagerRepositorySelector): Promise<IGitManagerRepositoryInfo> {
|
|
@@ -369,29 +517,64 @@ export class GitManager extends plugins.EventEmitter {
|
|
|
369
517
|
return this.bridge.createRepositoryArchive(requestArg, optionsArg);
|
|
370
518
|
}
|
|
371
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Starts a bounded `git upload-pack` or protected `git receive-pack` for an
|
|
522
|
+
* authenticated ingress such as SSH.
|
|
523
|
+
*
|
|
524
|
+
* A receive-pack holds its repository against mirror transfers: while its
|
|
525
|
+
* `guardTicketId` is held, `importMirror` and `refreshMirror` of the
|
|
526
|
+
* repository fail with `repositoryBusy`, and a receive-pack is refused with
|
|
527
|
+
* `repositoryBusy` while a mirror transfer runs. Release the ticket with
|
|
528
|
+
* `releaseRepositoryService()` in a `finally` once the process has exited
|
|
529
|
+
* and the push is reconciled; `stop()` releases every ticket. Upload-pack
|
|
530
|
+
* takes no ticket.
|
|
531
|
+
*
|
|
532
|
+
* The ticket ID is chosen here, before the engine is asked. When opening a
|
|
533
|
+
* receive-pack fails, including a bridge timeout after the engine already
|
|
534
|
+
* issued the ticket, the ticket is released before the error is thrown. If
|
|
535
|
+
* that release fails as well, the original error is still thrown: the
|
|
536
|
+
* release error becomes its `cause` when it has none, and otherwise both
|
|
537
|
+
* are thrown together as an `AggregateError` (`errors`: the original error,
|
|
538
|
+
* then the release error; `cause`: the original error).
|
|
539
|
+
*/
|
|
372
540
|
public async openRepositoryService(
|
|
373
541
|
requestArg: IGitManagerRepositoryServiceRequest,
|
|
374
542
|
): Promise<GitManagerRepositoryService> {
|
|
375
543
|
this.assertRunning();
|
|
376
544
|
const serviceEpoch = this.lifecycleEpoch;
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
545
|
+
const guardTicketId = requestArg.service === 'receive-pack'
|
|
546
|
+
? plugins.crypto.randomUUID()
|
|
547
|
+
: undefined;
|
|
548
|
+
let service: GitManagerRepositoryService;
|
|
549
|
+
try {
|
|
550
|
+
const descriptor = await this.bridge.prepareRepositoryService({
|
|
551
|
+
org: requestArg.org,
|
|
552
|
+
repo: requestArg.repo,
|
|
553
|
+
service: requestArg.service,
|
|
554
|
+
...(requestArg.protocol ? { protocol: requestArg.protocol } : {}),
|
|
555
|
+
...(guardTicketId === undefined ? {} : { guardTicketId }),
|
|
556
|
+
});
|
|
557
|
+
if (
|
|
558
|
+
this.lifecycleEpoch !== serviceEpoch
|
|
559
|
+
|| this.stopRequested
|
|
560
|
+
|| !this.running
|
|
561
|
+
) {
|
|
562
|
+
throw new Error('GitManager stopped while preparing repository service');
|
|
563
|
+
}
|
|
564
|
+
if (descriptor.guardTicketId !== guardTicketId) {
|
|
565
|
+
throw new Error('GitManager returned an invalid repository service descriptor');
|
|
566
|
+
}
|
|
567
|
+
service = new GitManagerRepositoryService(
|
|
568
|
+
descriptor,
|
|
569
|
+
requestArg,
|
|
570
|
+
this.config.dataDir,
|
|
571
|
+
);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (guardTicketId === undefined) {
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
576
|
+
throw await this.releaseUnusedTicket(guardTicketId, error);
|
|
389
577
|
}
|
|
390
|
-
const service = new GitManagerRepositoryService(
|
|
391
|
-
descriptor,
|
|
392
|
-
requestArg,
|
|
393
|
-
this.config.dataDir,
|
|
394
|
-
);
|
|
395
578
|
this.repositoryServices.add(service);
|
|
396
579
|
service.completed.finally(() => {
|
|
397
580
|
this.repositoryServices.delete(service);
|
|
@@ -399,6 +582,46 @@ export class GitManager extends plugins.EventEmitter {
|
|
|
399
582
|
return service;
|
|
400
583
|
}
|
|
401
584
|
|
|
585
|
+
/**
|
|
586
|
+
* Releases the guard ticket of a receive-pack that failed to open and
|
|
587
|
+
* returns the error to throw: `errorArg`, carrying a failed release as its
|
|
588
|
+
* `cause` when it has none, or else an `AggregateError` of both.
|
|
589
|
+
*/
|
|
590
|
+
private async releaseUnusedTicket(ticketIdArg: string, errorArg: unknown): Promise<unknown> {
|
|
591
|
+
try {
|
|
592
|
+
await this.releaseRepositoryService(ticketIdArg);
|
|
593
|
+
return errorArg;
|
|
594
|
+
} catch (releaseError) {
|
|
595
|
+
if (errorArg instanceof Error && errorArg.cause === undefined) {
|
|
596
|
+
errorArg.cause = releaseError;
|
|
597
|
+
return errorArg;
|
|
598
|
+
}
|
|
599
|
+
return new AggregateError(
|
|
600
|
+
[errorArg, releaseError],
|
|
601
|
+
'Opening the repository service failed, and releasing its guard ticket failed',
|
|
602
|
+
{ cause: errorArg },
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Releases the guard ticket of a receive-pack opened with
|
|
609
|
+
* `openRepositoryService()` (`service.guardTicketId`), so mirror transfers
|
|
610
|
+
* of its repository are admitted again. Call it in a `finally` after the
|
|
611
|
+
* Git process has exited. Releasing is idempotent: an unknown or already
|
|
612
|
+
* released ticket, or a stopped engine, whose tickets ended with it, is a
|
|
613
|
+
* no-op.
|
|
614
|
+
*/
|
|
615
|
+
public async releaseRepositoryService(ticketIdArg: string): Promise<void> {
|
|
616
|
+
if (typeof ticketIdArg !== 'string') {
|
|
617
|
+
throw new TypeError('ticketId must be a string');
|
|
618
|
+
}
|
|
619
|
+
if (!this.bridge.running) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
await this.bridge.releaseRepositoryService(ticketIdArg);
|
|
623
|
+
}
|
|
624
|
+
|
|
402
625
|
public async restoreRepositoryBundle(
|
|
403
626
|
requestArg: IGitManagerRestoreBundleRequest,
|
|
404
627
|
): Promise<IGitManagerRepositoryInfo> {
|
|
@@ -7,6 +7,9 @@ const gitManagerErrorCodes: ReadonlySet<string> = new Set<TGitManagerErrorCode>(
|
|
|
7
7
|
'ancestryBudgetExceeded',
|
|
8
8
|
'archiveLimitExceeded',
|
|
9
9
|
'archiveRejected',
|
|
10
|
+
'atomicPushUnsupported',
|
|
11
|
+
'transferAborted',
|
|
12
|
+
'repositoryBusy',
|
|
10
13
|
]);
|
|
11
14
|
|
|
12
15
|
export const isGitManagerErrorCode = (valueArg: unknown): valueArg is TGitManagerErrorCode =>
|
|
@@ -64,12 +64,15 @@ const validateDescriptor = (
|
|
|
64
64
|
|| !plugins.path.isAbsolute(descriptorArg.hookPath)
|
|
65
65
|
|| !descriptorArg.protectedRefsPolicy
|
|
66
66
|
|| !/^[A-Za-z0-9_-]{1,65536}$/.test(descriptorArg.protectedRefsPolicy)
|
|
67
|
+
|| typeof descriptorArg.guardTicketId !== 'string'
|
|
68
|
+
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(descriptorArg.guardTicketId)
|
|
67
69
|
) {
|
|
68
70
|
throw new Error('GitManager returned an invalid receive-pack descriptor');
|
|
69
71
|
}
|
|
70
72
|
} else if (
|
|
71
73
|
descriptorArg.hookPath !== undefined
|
|
72
74
|
|| descriptorArg.protectedRefsPolicy !== undefined
|
|
75
|
+
|| descriptorArg.guardTicketId !== undefined
|
|
73
76
|
) {
|
|
74
77
|
throw new Error('GitManager returned unexpected upload-pack hook state');
|
|
75
78
|
}
|
|
@@ -80,6 +83,15 @@ export class GitManagerRepositoryService {
|
|
|
80
83
|
public readonly stdout: plugins.stream.Readable;
|
|
81
84
|
public readonly stderr: plugins.stream.Readable;
|
|
82
85
|
public readonly completed: Promise<IGitManagerRepositoryServiceResult>;
|
|
86
|
+
/**
|
|
87
|
+
* Receive-pack only: the guard ticket that holds the repository against
|
|
88
|
+
* mirror transfers (`importMirror`, `refreshMirror`, which fail with
|
|
89
|
+
* `repositoryBusy`) until `GitManager.releaseRepositoryService()` releases
|
|
90
|
+
* it or the engine stops. Release it in a `finally` once the process has
|
|
91
|
+
* exited and the push is reconciled. A UUID that `openRepositoryService()`
|
|
92
|
+
* chose. Null for upload-pack.
|
|
93
|
+
*/
|
|
94
|
+
public readonly guardTicketId: string | null;
|
|
83
95
|
private readonly child: plugins.childProcess.ChildProcessWithoutNullStreams;
|
|
84
96
|
private completionSettled = false;
|
|
85
97
|
private failure: TGitManagerRepositoryServiceFailure | null = null;
|
|
@@ -91,6 +103,7 @@ export class GitManagerRepositoryService {
|
|
|
91
103
|
homeDirectoryArg: string,
|
|
92
104
|
) {
|
|
93
105
|
validateDescriptor(descriptorArg, requestArg);
|
|
106
|
+
this.guardTicketId = descriptorArg.guardTicketId ?? null;
|
|
94
107
|
const args = descriptorArg.service === 'upload-pack'
|
|
95
108
|
? [
|
|
96
109
|
'upload-pack',
|
package/ts/models.ts
CHANGED
|
@@ -79,6 +79,15 @@ export interface IGitManagerRepositoryArchiveOptions {
|
|
|
79
79
|
signal?: AbortSignal;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Call options of the remote transfers `importMirror`, `refreshMirror` and
|
|
84
|
+
* `pushRefs`. Aborting `signal` stops the transfer; the call then rejects
|
|
85
|
+
* with a `GitManagerError` whose code is `transferAborted`.
|
|
86
|
+
*/
|
|
87
|
+
export interface IGitManagerTransferControlOptions {
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
}
|
|
90
|
+
|
|
82
91
|
export interface IGitManagerRepositoryArchive
|
|
83
92
|
extends IGitManagerRepositorySelector {
|
|
84
93
|
objectId: string;
|
|
@@ -145,6 +154,13 @@ export interface IGitManagerArchiveInspection {
|
|
|
145
154
|
* answer is unknown, never `false`.
|
|
146
155
|
* - `archiveLimitExceeded`: the archive exceeds a requested limit.
|
|
147
156
|
* - `archiveRejected`: the archive breaks a format, path, or link rule.
|
|
157
|
+
* - `atomicPushUnsupported`: the push remote does not advertise the `atomic`
|
|
158
|
+
* receive-pack capability; nothing was pushed.
|
|
159
|
+
* - `transferAborted`: a remote transfer was aborted through its `signal` or
|
|
160
|
+
* by `stop()`. An aborted push may or may not have been applied by the
|
|
161
|
+
* remote; read its refs again.
|
|
162
|
+
* - `repositoryBusy`: a mirror transfer holds the repository, or a write to
|
|
163
|
+
* it is in progress when a mirror transfer starts; nothing was changed.
|
|
148
164
|
*/
|
|
149
165
|
export type TGitManagerErrorCode =
|
|
150
166
|
| 'invalidObjectId'
|
|
@@ -152,7 +168,10 @@ export type TGitManagerErrorCode =
|
|
|
152
168
|
| 'objectNotCommit'
|
|
153
169
|
| 'ancestryBudgetExceeded'
|
|
154
170
|
| 'archiveLimitExceeded'
|
|
155
|
-
| 'archiveRejected'
|
|
171
|
+
| 'archiveRejected'
|
|
172
|
+
| 'atomicPushUnsupported'
|
|
173
|
+
| 'transferAborted'
|
|
174
|
+
| 'repositoryBusy';
|
|
156
175
|
|
|
157
176
|
export interface IGitManagerRepositoryLfsPointer {
|
|
158
177
|
oid: string;
|
|
@@ -169,6 +188,17 @@ export interface IGitManagerRepositoryServiceRequest
|
|
|
169
188
|
protocol?: 'version=0' | 'version=1' | 'version=2';
|
|
170
189
|
}
|
|
171
190
|
|
|
191
|
+
/**
|
|
192
|
+
* The engine request that prepares a repository service. A receive-pack
|
|
193
|
+
* carries the guard ticket ID the facade chose, so the facade can release the
|
|
194
|
+
* ticket even when it never receives the engine's answer.
|
|
195
|
+
*/
|
|
196
|
+
export interface IRustGitManagerRepositoryServicePrepareRequest
|
|
197
|
+
extends IGitManagerRepositoryServiceRequest {
|
|
198
|
+
/** Receive-pack only, and required there: a lowercase hyphenated UUID. */
|
|
199
|
+
guardTicketId?: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
172
202
|
export interface IRustGitManagerRepositoryServiceDescriptor
|
|
173
203
|
extends IGitManagerRepositoryServiceRequest {
|
|
174
204
|
repositoryPath: string;
|
|
@@ -178,6 +208,8 @@ export interface IRustGitManagerRepositoryServiceDescriptor
|
|
|
178
208
|
maxResponseBytes: number;
|
|
179
209
|
processTimeoutMs: number;
|
|
180
210
|
idleTimeoutSeconds: number;
|
|
211
|
+
/** Receive-pack only: the requested guard ticket ID, which `releaseRepositoryService` releases. */
|
|
212
|
+
guardTicketId?: string;
|
|
181
213
|
}
|
|
182
214
|
|
|
183
215
|
/**
|
|
@@ -295,6 +327,10 @@ export type TGitManagerReadinessResult =
|
|
|
295
327
|
};
|
|
296
328
|
|
|
297
329
|
export type {
|
|
330
|
+
IGitManagerAtomicPushRefResult,
|
|
331
|
+
IGitManagerAtomicPushRefUpdate,
|
|
332
|
+
IGitManagerAtomicPushRequest,
|
|
333
|
+
IGitManagerAtomicPushResult,
|
|
298
334
|
IGitManagerCommitDetail,
|
|
299
335
|
IGitManagerCommitRequest,
|
|
300
336
|
IGitManagerCommitSummary,
|
|
@@ -317,12 +353,18 @@ export type {
|
|
|
317
353
|
IGitManagerRefInventoryPage,
|
|
318
354
|
IGitManagerRefInventoryStale,
|
|
319
355
|
IGitManagerRefRequest,
|
|
356
|
+
IGitManagerRefreshMirrorRequest,
|
|
357
|
+
IGitManagerRemoteBasicCredential,
|
|
358
|
+
IGitManagerRemoteTransferOptions,
|
|
320
359
|
IGitManagerRepositoryInfo,
|
|
321
360
|
IGitManagerRepositorySelector,
|
|
361
|
+
IGitManagerRepositorySize,
|
|
362
|
+
IGitManagerSetDefaultBranchRequest,
|
|
322
363
|
IGitManagerTag,
|
|
323
364
|
IGitManagerTagRequest,
|
|
324
365
|
IGitManagerTreeEntry,
|
|
325
366
|
IGitManagerTreeRequest,
|
|
367
|
+
TGitManagerAtomicPushRefStatus,
|
|
326
368
|
TGitManagerDiffFileStatus,
|
|
327
369
|
TGitManagerDiffLineKind,
|
|
328
370
|
TGitManagerMergeMethod,
|
|
@@ -330,5 +372,6 @@ export type {
|
|
|
330
372
|
TGitManagerObjectType,
|
|
331
373
|
TGitManagerRefInventoryPagination,
|
|
332
374
|
TGitManagerRefInventoryResult,
|
|
375
|
+
TGitManagerRemoteCredential,
|
|
333
376
|
TGitManagerTreeEntryType,
|
|
334
377
|
} from '@foss.global/interfaces';
|