dsh-git-worktree 0.7.5 → 0.8.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 +15 -0
- package/README.en.md +7 -3
- package/README.md +7 -3
- package/docs/i18n.md +63 -0
- package/lib/adapters/files.d.ts +0 -7
- package/lib/adapters/files.js +4 -3
- package/lib/adapters/files.js.map +1 -1
- package/lib/adapters/git.d.ts +0 -10
- package/lib/adapters/git.js +6 -5
- package/lib/adapters/git.js.map +1 -1
- package/lib/adapters/registry.d.ts +0 -7
- package/lib/adapters/registry.js +2 -1
- package/lib/adapters/registry.js.map +1 -1
- package/lib/adapters/safe-file.d.ts +0 -7
- package/lib/adapters/safe-file.js +5 -4
- package/lib/adapters/safe-file.js.map +1 -1
- package/lib/client/WorktreeCreateRow.js +4 -2
- package/lib/client/WorktreeCreateRow.js.map +1 -1
- package/lib/client/WorktreeReviewRow.js +10 -8
- package/lib/client/WorktreeReviewRow.js.map +1 -1
- package/lib/client/actions.d.ts +3 -2
- package/lib/client/actions.js +17 -13
- package/lib/client/actions.js.map +1 -1
- package/lib/client/console-remote/adapter.d.ts +2 -1
- package/lib/client/console-remote/adapter.js +35 -32
- package/lib/client/console-remote/adapter.js.map +1 -1
- package/lib/client/console-remote/index.js +6 -2
- package/lib/client/console-remote/index.js.map +1 -1
- package/lib/client/i18n.d.ts +25 -0
- package/lib/client/i18n.js +43 -0
- package/lib/client/i18n.js.map +1 -0
- package/lib/client/index.d.ts +1 -1
- package/lib/client/index.js +17 -4
- package/lib/client/index.js.map +1 -1
- package/lib/client/model.d.ts +3 -2
- package/lib/client/model.js +8 -8
- package/lib/client/model.js.map +1 -1
- package/lib/client/pre-session/PreSessionWorktreeToggle.js +14 -12
- package/lib/client/pre-session/PreSessionWorktreeToggle.js.map +1 -1
- package/lib/client/pre-session/controller.js +11 -9
- package/lib/client/pre-session/controller.js.map +1 -1
- package/lib/client/pre-session/index.d.ts +12 -1
- package/lib/client/pre-session/index.js +9 -1
- package/lib/client/pre-session/index.js.map +1 -1
- package/lib/client/review-console/DeliveryProof.js +15 -12
- package/lib/client/review-console/DeliveryProof.js.map +1 -1
- package/lib/client/review-console/PreflightStatus.js +14 -8
- package/lib/client/review-console/PreflightStatus.js.map +1 -1
- package/lib/client/review-console/ReviewActions.js +97 -92
- package/lib/client/review-console/ReviewActions.js.map +1 -1
- package/lib/client/review-console/WorktreeReviewPanel.d.ts +1 -1
- package/lib/client/review-console/WorktreeReviewPanel.js +19 -16
- package/lib/client/review-console/WorktreeReviewPanel.js.map +1 -1
- package/lib/client/review-console/recovery-continuation.d.ts +2 -1
- package/lib/client/review-console/recovery-continuation.js +12 -8
- package/lib/client/review-console/recovery-continuation.js.map +1 -1
- package/lib/client/target-console/TargetStatusAction.js +28 -25
- package/lib/client/target-console/TargetStatusAction.js.map +1 -1
- package/lib/client/target-console/WorktreeConsoleView.js +40 -36
- package/lib/client/target-console/WorktreeConsoleView.js.map +1 -1
- package/lib/client/target-console/WorktreeManagerModal.js +3 -1
- package/lib/client/target-console/WorktreeManagerModal.js.map +1 -1
- package/lib/client/target-console/WorktreeReviewStatus.js +14 -12
- package/lib/client/target-console/WorktreeReviewStatus.js.map +1 -1
- package/lib/client/workspace-sidebar/index.js +4 -2
- package/lib/client/workspace-sidebar/index.js.map +1 -1
- package/lib/client/workspace-sidebar/model.d.ts +2 -1
- package/lib/client/workspace-sidebar/model.js +14 -13
- package/lib/client/workspace-sidebar/model.js.map +1 -1
- package/lib/client.js +2173 -477
- package/lib/commands.js +32 -35
- package/lib/commands.js.map +1 -1
- package/lib/console-host/control-plane.js +82 -81
- package/lib/console-host/control-plane.js.map +1 -1
- package/lib/console-host/errors.js +2 -1
- package/lib/console-host/errors.js.map +1 -1
- package/lib/console-host/projection.js +3 -1
- package/lib/console-host/projection.js.map +1 -1
- package/lib/console-host/review-diff.js +6 -5
- package/lib/console-host/review-diff.js.map +1 -1
- package/lib/console-host/service.d.ts +24 -22
- package/lib/console-host/service.js +54 -51
- package/lib/console-host/service.js.map +1 -1
- package/lib/console-remote/descriptors.js +2 -2
- package/lib/console-remote/descriptors.js.map +1 -1
- package/lib/console-remote/remote.d.ts +22 -21
- package/lib/console-remote/remote.js.map +1 -1
- package/lib/console-remote/schemas.d.ts +4 -0
- package/lib/console-remote/schemas.js +1 -0
- package/lib/console-remote/schemas.js.map +1 -1
- package/lib/i18n/client-messages.d.ts +1523 -0
- package/lib/i18n/client-messages.js +1431 -0
- package/lib/i18n/client-messages.js.map +1 -0
- package/lib/i18n/core.d.ts +12 -0
- package/lib/i18n/core.js +10 -0
- package/lib/i18n/core.js.map +1 -0
- package/lib/i18n/host-messages.d.ts +1463 -0
- package/lib/i18n/host-messages.js +1474 -0
- package/lib/i18n/host-messages.js.map +1 -0
- package/lib/i18n/host.d.ts +9 -0
- package/lib/i18n/host.js +34 -0
- package/lib/i18n/host.js.map +1 -0
- package/lib/i18n/transport-messages.d.ts +10 -0
- package/lib/i18n/transport-messages.js +11 -0
- package/lib/i18n/transport-messages.js.map +1 -0
- package/lib/session-checkout-apply.js +93 -92
- package/lib/session-checkout-apply.js.map +1 -1
- package/lib/session-checkout-module.js +232 -229
- package/lib/session-checkout-module.js.map +1 -1
- package/lib/session-target-context.js +2 -1
- package/lib/session-target-context.js.map +1 -1
- package/lib/tools.d.ts +0 -8
- package/lib/tools.js +108 -96
- package/lib/tools.js.map +1 -1
- package/package.json +2 -1
- package/scripts/dev-dsh-lib.mjs +1 -1
- package/scripts/workspace-sidebar-upstream.mjs +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hostMessage } from './i18n/host.js';
|
|
1
2
|
import { spawn } from 'node:child_process';
|
|
2
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
4
|
import { realpathSync } from 'node:fs';
|
|
@@ -8,7 +9,7 @@ class GitCommandFailure extends Error {
|
|
|
8
9
|
args;
|
|
9
10
|
result;
|
|
10
11
|
constructor(args, result) {
|
|
11
|
-
super(result.stderr ||
|
|
12
|
+
super(result.stderr || hostMessage('gitExitedWithCode', { p0: result.exitCode }));
|
|
12
13
|
this.args = args;
|
|
13
14
|
this.result = result;
|
|
14
15
|
}
|
|
@@ -53,7 +54,7 @@ async function runGit(cwd, args, options = {}) {
|
|
|
53
54
|
});
|
|
54
55
|
const timeout = setTimeout(() => {
|
|
55
56
|
child.kill('SIGTERM');
|
|
56
|
-
finish(new Error(
|
|
57
|
+
finish(new Error(hostMessage('gitCommandTimedOut', { p0: args[0] ?? 'unknown' })));
|
|
57
58
|
}, GIT_TIMEOUT_MS);
|
|
58
59
|
if (options.input !== undefined)
|
|
59
60
|
child.stdin.end(options.input);
|
|
@@ -354,10 +355,10 @@ async function createUserCommit(checkoutPath, treeOid, parentOid, commitMessage)
|
|
|
354
355
|
}
|
|
355
356
|
async function prepareRollbackAssessment(tempRoot, sourceObjects, objectDirectory, localGitRoot, current, receipt) {
|
|
356
357
|
if (current.headRef !== receipt.localHeadRef) {
|
|
357
|
-
return { status: 'blocked', code: 'stale_local', message: '
|
|
358
|
+
return { status: 'blocked', code: 'stale_local', message: hostMessage('theLocalBranchHasChangedPreviewCannotBeRolled') };
|
|
358
359
|
}
|
|
359
360
|
if (current.headOid !== receipt.localHeadOid && !await isAncestor(localGitRoot, receipt.localHeadOid, current.headOid)) {
|
|
360
|
-
return { status: 'blocked', code: 'stale_local', message: '
|
|
361
|
+
return { status: 'blocked', code: 'stale_local', message: hostMessage('localHEADIsNotASafeFastForwardFrom') };
|
|
361
362
|
}
|
|
362
363
|
let rollbackBaselineTreeOid = receipt.localWorkingTreeOid;
|
|
363
364
|
let expectedPreviewTreeOid = receipt.previewWorkingTreeOid;
|
|
@@ -367,7 +368,7 @@ async function prepareRollbackAssessment(tempRoot, sourceObjects, objectDirector
|
|
|
367
368
|
if (advancedBaseline.status === 'conflict') {
|
|
368
369
|
return {
|
|
369
370
|
status: 'blocked', code: 'preview_modified',
|
|
370
|
-
message:
|
|
371
|
+
message: hostMessage('newLocalCommitsConflictWithPrePreviewLocalChanges', { p0: advancedBaseline.conflictingFiles.join('、') }),
|
|
371
372
|
conflictingFiles: [...advancedBaseline.conflictingFiles],
|
|
372
373
|
};
|
|
373
374
|
}
|
|
@@ -375,21 +376,21 @@ async function prepareRollbackAssessment(tempRoot, sourceObjects, objectDirector
|
|
|
375
376
|
if (previewAbsence.status === 'conflict') {
|
|
376
377
|
return {
|
|
377
378
|
status: 'blocked', code: 'preview_modified',
|
|
378
|
-
message:
|
|
379
|
+
message: hostMessage('newLocalCommitsConflictWithThePreviewTaskChanges', { p0: previewAbsence.conflictingFiles.join('、') }),
|
|
379
380
|
conflictingFiles: [...previewAbsence.conflictingFiles],
|
|
380
381
|
};
|
|
381
382
|
}
|
|
382
383
|
if (previewAbsence.treeOid !== advancedBaseline.treeOid) {
|
|
383
384
|
return {
|
|
384
385
|
status: 'blocked', code: 'preview_modified',
|
|
385
|
-
message: '
|
|
386
|
+
message: hostMessage('newLocalCommitsAlreadyContainSomeOrAllPreview'),
|
|
386
387
|
};
|
|
387
388
|
}
|
|
388
389
|
const advancedPreview = await computeTreeMerge(tempRoot, sourceObjects, objectDirectory, localGitRoot, 'preview-rollback-advanced-preview', receipt.localWorkingTreeOid, advancedBaseline.treeOid, receipt.previewWorkingTreeOid);
|
|
389
390
|
if (advancedPreview.status === 'conflict') {
|
|
390
391
|
return {
|
|
391
392
|
status: 'blocked', code: 'preview_modified',
|
|
392
|
-
message:
|
|
393
|
+
message: hostMessage('newLocalCommitsConflictWithThePreviewTaskChanges', { p0: advancedPreview.conflictingFiles.join('、') }),
|
|
393
394
|
conflictingFiles: [...advancedPreview.conflictingFiles],
|
|
394
395
|
};
|
|
395
396
|
}
|
|
@@ -400,7 +401,7 @@ async function prepareRollbackAssessment(tempRoot, sourceObjects, objectDirector
|
|
|
400
401
|
if (rollbackTree.status === 'conflict') {
|
|
401
402
|
return {
|
|
402
403
|
status: 'blocked', code: 'preview_modified',
|
|
403
|
-
message:
|
|
404
|
+
message: hostMessage('localHasAdditionalChangesInThePreviewAreaSafe', { p0: rollbackTree.conflictingFiles.join('、') }),
|
|
404
405
|
conflictingFiles: [...rollbackTree.conflictingFiles],
|
|
405
406
|
};
|
|
406
407
|
}
|
|
@@ -408,19 +409,19 @@ async function prepareRollbackAssessment(tempRoot, sourceObjects, objectDirector
|
|
|
408
409
|
}
|
|
409
410
|
async function prepareFinalizeAssessment(tempRoot, sourceObjects, objectDirectory, localGitRoot, current, receipt) {
|
|
410
411
|
if (!receipt.localHeadRef?.startsWith('refs/heads/')) {
|
|
411
|
-
return { status: 'blocked', code: 'operation_not_allowed', message: '
|
|
412
|
+
return { status: 'blocked', code: 'operation_not_allowed', message: hostMessage('localIsNotOnANormalBranchATask') };
|
|
412
413
|
}
|
|
413
414
|
if (current.headRef !== receipt.localHeadRef) {
|
|
414
|
-
return { status: 'blocked', code: 'stale_local', message: '
|
|
415
|
+
return { status: 'blocked', code: 'stale_local', message: hostMessage('theLocalBranchHasChangedThePreviewCommitCannot') };
|
|
415
416
|
}
|
|
416
417
|
if (current.headOid !== receipt.localHeadOid && !await isAncestor(localGitRoot, receipt.localHeadOid, current.headOid)) {
|
|
417
|
-
return { status: 'blocked', code: 'stale_local', message: '
|
|
418
|
+
return { status: 'blocked', code: 'stale_local', message: hostMessage('localHEADIsNotASafeFastForwardFrom281') };
|
|
418
419
|
}
|
|
419
420
|
const previewRemoval = await computeTreeMerge(tempRoot, sourceObjects, objectDirectory, localGitRoot, 'preview-finalize-separation', receipt.previewWorkingTreeOid, current.treeOid, receipt.localWorkingTreeOid);
|
|
420
421
|
if (previewRemoval.status === 'conflict') {
|
|
421
422
|
return {
|
|
422
423
|
status: 'blocked', code: 'preview_modified',
|
|
423
|
-
message:
|
|
424
|
+
message: hostMessage('localHasAdditionalChangesInThePreviewAreaA', { p0: previewRemoval.conflictingFiles.join('、') }),
|
|
424
425
|
conflictingFiles: [...previewRemoval.conflictingFiles],
|
|
425
426
|
};
|
|
426
427
|
}
|
|
@@ -428,21 +429,21 @@ async function prepareFinalizeAssessment(tempRoot, sourceObjects, objectDirector
|
|
|
428
429
|
if (taskTree.status === 'conflict') {
|
|
429
430
|
return {
|
|
430
431
|
status: 'blocked', code: 'commit_isolation_conflict',
|
|
431
|
-
message:
|
|
432
|
+
message: hostMessage('previewTaskChangesCannotBeReliablySeparatedFromThe', { p0: taskTree.conflictingFiles.join('、') }),
|
|
432
433
|
conflictingFiles: [...taskTree.conflictingFiles],
|
|
433
434
|
};
|
|
434
435
|
}
|
|
435
436
|
if (taskTree.treeOid === current.headTreeOid && receipt.changedFiles.length > 0) {
|
|
436
437
|
return {
|
|
437
438
|
status: 'blocked', code: 'preview_modified',
|
|
438
|
-
message: '
|
|
439
|
+
message: hostMessage('previewTaskChangesAreAlreadyInLocalHEADNo'),
|
|
439
440
|
};
|
|
440
441
|
}
|
|
441
442
|
const finalIndexTree = await computeTreeMerge(tempRoot, sourceObjects, objectDirectory, localGitRoot, 'preview-index-preservation', current.headTreeOid, taskTree.treeOid, current.indexTreeOid);
|
|
442
443
|
if (finalIndexTree.status === 'conflict') {
|
|
443
444
|
return {
|
|
444
445
|
status: 'blocked', code: 'commit_isolation_conflict',
|
|
445
|
-
message:
|
|
446
|
+
message: hostMessage('thePreviewCommitCannotBeReliablySeparatedFromLocal', { p0: finalIndexTree.conflictingFiles.join('、') }),
|
|
446
447
|
conflictingFiles: [...finalIndexTree.conflictingFiles],
|
|
447
448
|
};
|
|
448
449
|
}
|
|
@@ -529,7 +530,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
529
530
|
async checkpoint(input) {
|
|
530
531
|
const commitMessage = input.commitMessage.trim();
|
|
531
532
|
if (!commitMessage || commitMessage.length > 500 || !OID_PATTERN.test(input.expectedHeadOid) || !input.expectedFingerprint.trim()) {
|
|
532
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
533
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('invalidCheckpointInput') } };
|
|
533
534
|
}
|
|
534
535
|
let tempRoot = null;
|
|
535
536
|
let adjacentIndex = null;
|
|
@@ -540,22 +541,22 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
540
541
|
const isolatedGitRoot = await resolveGitRoot(input.isolatedPath);
|
|
541
542
|
const projectPathPrefix = projectPrefix(isolatedGitRoot, input.isolatedPath);
|
|
542
543
|
if (projectPathPrefix === null) {
|
|
543
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
544
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('theCheckpointProjectDirectoryDoesNotBelongToThe') } };
|
|
544
545
|
}
|
|
545
546
|
const snapshot = await captureSnapshot(isolatedGitRoot, join(tempRoot, 'checkpoint.index'), null, null);
|
|
546
547
|
if (snapshot.headRef !== null) {
|
|
547
|
-
return { status: 'error', error: { code: 'operation_not_allowed', message: '
|
|
548
|
+
return { status: 'error', error: { code: 'operation_not_allowed', message: hostMessage('checkpointCanOnlyWriteToADetachedManagedWorktree') } };
|
|
548
549
|
}
|
|
549
550
|
if (snapshot.headOid !== input.expectedHeadOid || snapshot.fingerprint !== input.expectedFingerprint) {
|
|
550
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
551
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('worktreeChangedAfterPreparingTheReviewTheCheckpointCannot') } };
|
|
551
552
|
}
|
|
552
553
|
const changedPaths = await changedTreePaths(isolatedGitRoot, snapshot.headOid, snapshot.treeOid, null, null);
|
|
553
554
|
if (changedPaths.some((path) => !isProjectPath(path, projectPathPrefix))) {
|
|
554
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
555
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('worktreeContainsChangesOutsideTheProjectRootTheCheckpoint') } };
|
|
555
556
|
}
|
|
556
557
|
const changedFiles = changedPaths.map((path) => toProjectPath(path, projectPathPrefix)).sort();
|
|
557
558
|
if (changedFiles.length === 0) {
|
|
558
|
-
return { status: 'error', error: { code: 'operation_not_allowed', message: '
|
|
559
|
+
return { status: 'error', error: { code: 'operation_not_allowed', message: hostMessage('theCurrentCheckpointHasNoNewChangesToSave') } };
|
|
559
560
|
}
|
|
560
561
|
const commitOid = await createUserCommit(isolatedGitRoot, snapshot.treeOid, snapshot.headOid, commitMessage);
|
|
561
562
|
await input.beforeCommit?.({ commitOid, parentOid: snapshot.headOid, indexTreeOid: snapshot.indexTreeOid, changedFiles });
|
|
@@ -572,7 +573,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
572
573
|
await this.options.beforeFinalIsolatedValidation?.();
|
|
573
574
|
const finalSnapshot = await captureSnapshot(isolatedGitRoot, join(tempRoot, 'final-checkpoint.index'), null, null);
|
|
574
575
|
if (finalSnapshot.headOid !== snapshot.headOid || finalSnapshot.fingerprint !== snapshot.fingerprint) {
|
|
575
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
576
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('worktreeChangedBeforeSavingTheCheckpointPrepareTheReview') } };
|
|
576
577
|
}
|
|
577
578
|
let headUpdated = false;
|
|
578
579
|
try {
|
|
@@ -594,7 +595,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
594
595
|
status: 'error',
|
|
595
596
|
error: {
|
|
596
597
|
code: 'git_error',
|
|
597
|
-
message:
|
|
598
|
+
message: hostMessage('checkpointIndexWriteFailedAndHEADCouldNotBe', { p0: this.errorMessage(error), p1: this.errorMessage(rollbackError) }),
|
|
598
599
|
recoveryState: 'uncertain',
|
|
599
600
|
},
|
|
600
601
|
};
|
|
@@ -604,7 +605,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
604
605
|
status: 'error',
|
|
605
606
|
error: {
|
|
606
607
|
code: 'git_error',
|
|
607
|
-
message:
|
|
608
|
+
message: hostMessage('checkpointWriteFailedAndWasRolledBack', { p0: this.errorMessage(error) }),
|
|
608
609
|
recoveryState: 'unchanged',
|
|
609
610
|
},
|
|
610
611
|
};
|
|
@@ -619,7 +620,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
619
620
|
|| completed.treeOid !== snapshot.treeOid) {
|
|
620
621
|
return {
|
|
621
622
|
status: 'error',
|
|
622
|
-
error: { code: 'git_error', message: '
|
|
623
|
+
error: { code: 'git_error', message: hostMessage('checkpointWasWrittenButWorktreeDidNotReachA'), recoveryState: 'uncertain' },
|
|
623
624
|
};
|
|
624
625
|
}
|
|
625
626
|
return {
|
|
@@ -635,7 +636,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
635
636
|
status: 'error',
|
|
636
637
|
error: {
|
|
637
638
|
code: 'git_error',
|
|
638
|
-
message:
|
|
639
|
+
message: hostMessage('cannotVerifyTheCheckpointAfterWritingTheEnvironmentMust', { p0: this.errorMessage(error) }),
|
|
639
640
|
recoveryState: 'uncertain',
|
|
640
641
|
},
|
|
641
642
|
};
|
|
@@ -646,7 +647,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
646
647
|
return {
|
|
647
648
|
status: 'error',
|
|
648
649
|
error: stale
|
|
649
|
-
? { code: 'stale_isolated', message: '
|
|
650
|
+
? { code: 'stale_isolated', message: hostMessage('anotherGitOperationIsUpdatingTheWorktreeIndexRetry') }
|
|
650
651
|
: { code: 'git_error', message: this.errorMessage(error) },
|
|
651
652
|
};
|
|
652
653
|
}
|
|
@@ -661,7 +662,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
661
662
|
}
|
|
662
663
|
async recoverCheckpoint(input) {
|
|
663
664
|
if (!OID_PATTERN.test(input.commitOid) || !OID_PATTERN.test(input.parentOid) || !OID_PATTERN.test(input.expectedIndexTreeOid)) {
|
|
664
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
665
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('invalidCheckpointRecoveryOID') } };
|
|
665
666
|
}
|
|
666
667
|
let tempRoot = null;
|
|
667
668
|
let ownedIndexLock = null;
|
|
@@ -677,12 +678,12 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
677
678
|
const markerOwned = await checkpointLockMarkerOwned(indexLockMarker, input.commitOid);
|
|
678
679
|
const snapshot = await captureSnapshot(isolatedGitRoot, join(tempRoot, 'current.index'), null, null);
|
|
679
680
|
if (snapshot.headRef !== null) {
|
|
680
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
681
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('worktreeNoLongerHasADetachedHEAD') } };
|
|
681
682
|
}
|
|
682
683
|
if (snapshot.headOid === input.parentOid) {
|
|
683
684
|
if (existingLock.exists) {
|
|
684
685
|
if (!markerOwned || (existingLock.treeOid !== null && existingLock.treeOid !== targetTreeOid)) {
|
|
685
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
686
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('cannotProveTheRemainingIndexLockBelongsToThe') } };
|
|
686
687
|
}
|
|
687
688
|
await unlink(indexLockPath);
|
|
688
689
|
}
|
|
@@ -691,19 +692,19 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
691
692
|
return { status: 'checkpoint_aborted', isolatedFingerprint: snapshot.fingerprint };
|
|
692
693
|
}
|
|
693
694
|
if (snapshot.headOid !== input.commitOid) {
|
|
694
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
695
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('worktreeHEADDoesNotMatchTheCheckpointBeingRecovered') } };
|
|
695
696
|
}
|
|
696
697
|
if (snapshot.treeOid !== snapshot.headTreeOid) {
|
|
697
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
698
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('worktreeHasNewChangesAfterCheckpointInterruptionTheIndex') } };
|
|
698
699
|
}
|
|
699
700
|
if (snapshot.indexTreeOid !== input.expectedIndexTreeOid && snapshot.indexTreeOid !== snapshot.headTreeOid) {
|
|
700
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
701
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('theIndexHasNewStagedChangesAfterCheckpointInterruption') } };
|
|
701
702
|
}
|
|
702
703
|
const cleanIndexPath = join(tempRoot, 'clean.index');
|
|
703
704
|
await prepareIndexFromPatch(isolatedGitRoot, cleanIndexPath, input.commitOid, Buffer.alloc(0));
|
|
704
705
|
if (existingLock.exists) {
|
|
705
706
|
if (!markerOwned || (existingLock.treeOid !== null && existingLock.treeOid !== targetTreeOid)) {
|
|
706
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
707
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('theRemainingIndexLockDoesNotMatchTheCheckpoint') } };
|
|
707
708
|
}
|
|
708
709
|
if (existingLock.treeOid === null)
|
|
709
710
|
await copyFile(cleanIndexPath, indexLockPath);
|
|
@@ -723,7 +724,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
723
724
|
if (lockedSnapshot.headOid !== snapshot.headOid
|
|
724
725
|
|| lockedSnapshot.fingerprint !== snapshot.fingerprint
|
|
725
726
|
|| (lockedSnapshot.indexTreeOid !== input.expectedIndexTreeOid && lockedSnapshot.indexTreeOid !== lockedSnapshot.headTreeOid)) {
|
|
726
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
727
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('worktreeOrItsIndexChangedBeforeAcquiringTheCheckpoint') } };
|
|
727
728
|
}
|
|
728
729
|
await rename(indexLockPath, realIndexPath);
|
|
729
730
|
ownedIndexLock = null;
|
|
@@ -734,7 +735,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
734
735
|
|| completed.headRef !== null
|
|
735
736
|
|| completed.indexTreeOid !== completed.headTreeOid
|
|
736
737
|
|| completed.treeOid !== completed.headTreeOid) {
|
|
737
|
-
return { status: 'error', error: { code: 'git_error', message: '
|
|
738
|
+
return { status: 'error', error: { code: 'git_error', message: hostMessage('worktreeIsStillNotCleanAfterCheckpointIndexRecovery'), recoveryState: 'uncertain' } };
|
|
738
739
|
}
|
|
739
740
|
return { status: 'checkpoint_recovered', isolatedFingerprint: completed.fingerprint };
|
|
740
741
|
}
|
|
@@ -750,7 +751,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
750
751
|
}
|
|
751
752
|
async inspectReview(input) {
|
|
752
753
|
if (!OID_PATTERN.test(input.baseOid)) {
|
|
753
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
754
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('invalidSessionBaseOIDFormat') } };
|
|
754
755
|
}
|
|
755
756
|
let tempRoot = null;
|
|
756
757
|
try {
|
|
@@ -762,18 +763,18 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
762
763
|
const localObjects = await sourceObjectDirectory(localGitRoot);
|
|
763
764
|
const isolatedObjects = await sourceObjectDirectory(isolatedGitRoot);
|
|
764
765
|
if (!pathsMatch(localObjects, isolatedObjects)) {
|
|
765
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
766
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('localAndIsolatedDoNotBelongToTheSame') } };
|
|
766
767
|
}
|
|
767
768
|
const localPrefix = projectPrefix(localGitRoot, input.localPath);
|
|
768
769
|
const isolatedPrefix = projectPrefix(isolatedGitRoot, input.isolatedPath);
|
|
769
770
|
if (localPrefix === null || isolatedPrefix === null || localPrefix !== isolatedPrefix) {
|
|
770
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
771
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('theLocalAndIsolatedProjectSubdirectoriesDoNotMatch') } };
|
|
771
772
|
}
|
|
772
773
|
await runGit(localGitRoot, ['cat-file', '-e', `${input.baseOid}^{commit}`]);
|
|
773
774
|
const isolated = await captureSnapshot(isolatedGitRoot, join(tempRoot, 'isolated.index'), objectDirectory, localObjects);
|
|
774
775
|
const changedFiles = await changedTreePaths(isolatedGitRoot, input.baseOid, isolated.treeOid, objectDirectory, localObjects);
|
|
775
776
|
if (changedFiles.some((path) => !isProjectPath(path, localPrefix))) {
|
|
776
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
777
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('isolatedContainsChangesOutsideTheProjectRootAReview') } };
|
|
777
778
|
}
|
|
778
779
|
return {
|
|
779
780
|
status: 'ready',
|
|
@@ -798,7 +799,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
798
799
|
}
|
|
799
800
|
async calculatePlan(input, persistPlan) {
|
|
800
801
|
if (!OID_PATTERN.test(input.baseOid)) {
|
|
801
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
802
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('invalidSessionBaseOIDFormat') } };
|
|
802
803
|
}
|
|
803
804
|
let tempRoot = null;
|
|
804
805
|
try {
|
|
@@ -810,12 +811,12 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
810
811
|
const localObjects = await sourceObjectDirectory(localGitRoot);
|
|
811
812
|
const isolatedObjects = await sourceObjectDirectory(isolatedGitRoot);
|
|
812
813
|
if (!pathsMatch(localObjects, isolatedObjects)) {
|
|
813
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
814
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('localAndIsolatedDoNotBelongToTheSame') } };
|
|
814
815
|
}
|
|
815
816
|
const localPrefix = projectPrefix(localGitRoot, input.localPath);
|
|
816
817
|
const isolatedPrefix = projectPrefix(isolatedGitRoot, input.isolatedPath);
|
|
817
818
|
if (localPrefix === null || isolatedPrefix === null || localPrefix !== isolatedPrefix) {
|
|
818
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
819
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('theLocalAndIsolatedProjectSubdirectoriesDoNotMatch') } };
|
|
819
820
|
}
|
|
820
821
|
await runGit(localGitRoot, ['cat-file', '-e', `${input.baseOid}^{commit}`]);
|
|
821
822
|
const local = await captureSnapshot(localGitRoot, join(tempRoot, 'local.index'), objectDirectory, localObjects);
|
|
@@ -825,7 +826,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
825
826
|
if (isolatedChangedPaths.some((path) => !isProjectPath(path, localPrefix))) {
|
|
826
827
|
return {
|
|
827
828
|
status: 'error',
|
|
828
|
-
error: { code: 'invalid_input', message: '
|
|
829
|
+
error: { code: 'invalid_input', message: hostMessage('isolatedContainsChangesOutsideTheProjectRootApplyWas') },
|
|
829
830
|
};
|
|
830
831
|
}
|
|
831
832
|
const merge = await computeMerge(tempRoot, localObjects, objectDirectory, effectiveBase.oid, local, isolated, localGitRoot, localPrefix);
|
|
@@ -880,7 +881,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
880
881
|
async apply(plan) {
|
|
881
882
|
const stored = this.plans.get(plan.revision);
|
|
882
883
|
if (!stored || !planMatches(stored.plan, plan)) {
|
|
883
|
-
return { status: 'error', error: { code: 'invalid_plan', message: '
|
|
884
|
+
return { status: 'error', error: { code: 'invalid_plan', message: hostMessage('theApplyPlanIsMissingAlreadyUsedOrModified') } };
|
|
884
885
|
}
|
|
885
886
|
let tempRoot = null;
|
|
886
887
|
try {
|
|
@@ -889,21 +890,21 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
889
890
|
await mkdir(objectDirectory, { recursive: true });
|
|
890
891
|
const sourceObjects = await sourceObjectDirectory(stored.scope.localGitRoot);
|
|
891
892
|
if (!pathsMatch(sourceObjects, stored.scope.sourceObjects)) {
|
|
892
|
-
return { status: 'error', error: { code: 'invalid_plan', message: '
|
|
893
|
+
return { status: 'error', error: { code: 'invalid_plan', message: hostMessage('theApplyPlanSGitRepositoryIdentityHasChanged') } };
|
|
893
894
|
}
|
|
894
895
|
const local = await captureSnapshot(stored.scope.localGitRoot, join(tempRoot, 'local.index'), objectDirectory, sourceObjects);
|
|
895
896
|
if (local.headOid !== stored.plan.localHeadOid || local.fingerprint !== stored.plan.localFingerprint) {
|
|
896
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
897
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedAfterPlanningRecalculateThePlan') } };
|
|
897
898
|
}
|
|
898
899
|
const isolated = await captureSnapshot(stored.scope.isolatedGitRoot, join(tempRoot, 'isolated.index'), objectDirectory, sourceObjects);
|
|
899
900
|
if (isolated.headOid !== stored.plan.isolatedHeadOid || isolated.fingerprint !== stored.plan.isolatedFingerprint) {
|
|
900
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
901
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('isolatedChangedAfterPlanningRecalculateThePlan') } };
|
|
901
902
|
}
|
|
902
903
|
// 将已审核的 Isolated 最终状态写成无 ref 的内部 commit,供同一 checkout 后续 Apply 去重。
|
|
903
904
|
// 使用独立 index,不改变 Isolated 的真实 staged/working tree。
|
|
904
905
|
const persistentIsolated = await captureSnapshot(stored.scope.isolatedGitRoot, join(tempRoot, 'persistent-isolated.index'), null, null);
|
|
905
906
|
if (persistentIsolated.fingerprint !== stored.plan.isolatedFingerprint) {
|
|
906
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
907
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('isolatedChangedAfterPlanningRecalculateThePlan') } };
|
|
907
908
|
}
|
|
908
909
|
const nextBaseOid = await createSnapshotCommit(stored.scope.isolatedGitRoot, persistentIsolated.treeOid, stored.plan.effectiveBaseOid, null, null, 'Applied Isolated snapshot');
|
|
909
910
|
await this.options.beforeFinalLocalValidation?.();
|
|
@@ -911,7 +912,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
911
912
|
if (finalLocal.headOid !== stored.plan.localHeadOid || finalLocal.fingerprint !== stored.plan.localFingerprint) {
|
|
912
913
|
return {
|
|
913
914
|
status: 'error',
|
|
914
|
-
error: { code: 'stale_local', message: '
|
|
915
|
+
error: { code: 'stale_local', message: hostMessage('localChangedBeforeApplyWroteItsChangesRecalculateThe') },
|
|
915
916
|
};
|
|
916
917
|
}
|
|
917
918
|
if (stored.patch.length > 0) {
|
|
@@ -931,10 +932,10 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
931
932
|
async preview(plan, options) {
|
|
932
933
|
const stored = this.plans.get(plan.revision);
|
|
933
934
|
if (!stored || !planMatches(stored.plan, plan)) {
|
|
934
|
-
return { status: 'error', error: { code: 'invalid_plan', message: '
|
|
935
|
+
return { status: 'error', error: { code: 'invalid_plan', message: hostMessage('thePreviewPlanIsMissingAlreadyUsedOrModified') } };
|
|
935
936
|
}
|
|
936
937
|
if (!options.previewId.trim() || !options.reviewId.trim() || !Number.isSafeInteger(options.iteration) || options.iteration < 1) {
|
|
937
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
938
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('invalidPreviewIdentity') } };
|
|
938
939
|
}
|
|
939
940
|
let tempRoot = null;
|
|
940
941
|
try {
|
|
@@ -943,23 +944,23 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
943
944
|
await mkdir(objectDirectory, { recursive: true });
|
|
944
945
|
const sourceObjects = await sourceObjectDirectory(stored.scope.localGitRoot);
|
|
945
946
|
if (!pathsMatch(sourceObjects, stored.scope.sourceObjects)) {
|
|
946
|
-
return { status: 'error', error: { code: 'invalid_plan', message: '
|
|
947
|
+
return { status: 'error', error: { code: 'invalid_plan', message: hostMessage('thePreviewPlanSGitRepositoryIdentityHasChanged') } };
|
|
947
948
|
}
|
|
948
949
|
const local = await captureSnapshot(stored.scope.localGitRoot, join(tempRoot, 'local.index'), objectDirectory, sourceObjects);
|
|
949
950
|
if (local.headOid !== stored.plan.localHeadOid
|
|
950
951
|
|| local.headRef !== stored.plan.localHeadRef
|
|
951
952
|
|| local.fingerprint !== stored.plan.localFingerprint) {
|
|
952
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
953
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedAfterPlanningRecalculateThePlan') } };
|
|
953
954
|
}
|
|
954
955
|
const isolated = await captureSnapshot(stored.scope.isolatedGitRoot, join(tempRoot, 'isolated.index'), objectDirectory, sourceObjects);
|
|
955
956
|
if (isolated.headOid !== stored.plan.isolatedHeadOid || isolated.fingerprint !== stored.plan.isolatedFingerprint) {
|
|
956
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
957
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('isolatedChangedAfterPlanningRecalculateThePlan') } };
|
|
957
958
|
}
|
|
958
959
|
const persistentLocal = await captureSnapshot(stored.scope.localGitRoot, join(tempRoot, 'persistent-local.index'), null, null);
|
|
959
960
|
const persistentIsolated = await captureSnapshot(stored.scope.isolatedGitRoot, join(tempRoot, 'persistent-isolated.index'), null, null);
|
|
960
961
|
if (persistentLocal.fingerprint !== stored.plan.localFingerprint
|
|
961
962
|
|| persistentIsolated.fingerprint !== stored.plan.isolatedFingerprint) {
|
|
962
|
-
return { status: 'error', error: { code: 'invalid_plan', message: '
|
|
963
|
+
return { status: 'error', error: { code: 'invalid_plan', message: hostMessage('thePersistentPreviewSnapshotDoesNotMatchTheReviewed') } };
|
|
963
964
|
}
|
|
964
965
|
const previewWorkingTreeOid = await prepareIndexFromPatch(stored.scope.localGitRoot, join(tempRoot, 'preview-working.index'), persistentLocal.treeOid, stored.patch);
|
|
965
966
|
const isolatedSnapshotOid = await createSnapshotCommit(stored.scope.isolatedGitRoot, persistentIsolated.treeOid, stored.plan.effectiveBaseOid, null, null, 'Previewed Isolated snapshot');
|
|
@@ -986,7 +987,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
986
987
|
await this.options.beforeFinalLocalValidation?.();
|
|
987
988
|
const finalLocal = await captureSnapshot(stored.scope.localGitRoot, join(tempRoot, 'final-local.index'), objectDirectory, sourceObjects);
|
|
988
989
|
if (finalLocal.headOid !== stored.plan.localHeadOid || finalLocal.fingerprint !== stored.plan.localFingerprint) {
|
|
989
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
990
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedBeforePreviewWroteItsChangesRecalculateThe') } };
|
|
990
991
|
}
|
|
991
992
|
await options.beforeWrite?.(preparedReceipt);
|
|
992
993
|
if (stored.patch.length > 0) {
|
|
@@ -996,7 +997,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
996
997
|
if (previewedLocal.treeOid !== previewWorkingTreeOid) {
|
|
997
998
|
return {
|
|
998
999
|
status: 'error',
|
|
999
|
-
error: { code: 'git_error', message: '
|
|
1000
|
+
error: { code: 'git_error', message: hostMessage('theLocalSnapshotAfterPreviewDoesNotMatchThe') },
|
|
1000
1001
|
};
|
|
1001
1002
|
}
|
|
1002
1003
|
this.plans.delete(plan.revision);
|
|
@@ -1066,7 +1067,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1066
1067
|
if (finalLocal.fingerprint !== current.fingerprint
|
|
1067
1068
|
|| finalLocal.headOid !== current.headOid
|
|
1068
1069
|
|| finalLocal.headRef !== current.headRef) {
|
|
1069
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
1070
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedBeforePreviewRollbackRetry') } };
|
|
1070
1071
|
}
|
|
1071
1072
|
if (rollbackPatch.length > 0) {
|
|
1072
1073
|
await runGit(localGitRoot, ['apply', '--binary', '--whitespace=nowarn'], { input: rollbackPatch });
|
|
@@ -1081,7 +1082,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1081
1082
|
status: 'error',
|
|
1082
1083
|
error: {
|
|
1083
1084
|
code: 'git_error',
|
|
1084
|
-
message:
|
|
1085
|
+
message: hostMessage('cannotVerifyAfterWritingPreviewRollbackTheEnvironmentMust', { p0: this.errorMessage(error) }),
|
|
1085
1086
|
recoveryState: 'uncertain',
|
|
1086
1087
|
},
|
|
1087
1088
|
};
|
|
@@ -1096,7 +1097,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1096
1097
|
status: 'error',
|
|
1097
1098
|
error: {
|
|
1098
1099
|
code: 'git_error',
|
|
1099
|
-
message: '
|
|
1100
|
+
message: hostMessage('theLocalSnapshotAfterPreviewRollbackDoesNotMatch'),
|
|
1100
1101
|
recoveryState: 'uncertain',
|
|
1101
1102
|
},
|
|
1102
1103
|
};
|
|
@@ -1114,7 +1115,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1114
1115
|
async finalize(input) {
|
|
1115
1116
|
const commitMessage = input.commitMessage.trim();
|
|
1116
1117
|
if (!commitMessage) {
|
|
1117
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
1118
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('theCommitMessageMustNotBeEmpty') } };
|
|
1118
1119
|
}
|
|
1119
1120
|
let tempRoot = null;
|
|
1120
1121
|
let ownedIndexLock = null;
|
|
@@ -1139,7 +1140,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1139
1140
|
if (finalLocal.headOid !== current.headOid
|
|
1140
1141
|
|| finalLocal.headRef !== current.headRef
|
|
1141
1142
|
|| finalLocal.fingerprint !== current.fingerprint) {
|
|
1142
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
1143
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedBeforeFinalizingPreviewRetry') } };
|
|
1143
1144
|
}
|
|
1144
1145
|
return {
|
|
1145
1146
|
status: 'finished',
|
|
@@ -1152,13 +1153,13 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1152
1153
|
const finalIndexPatch = await treePatch(localGitRoot, assessment.taskTreeOid, assessment.finalIndexTreeOid, objectDirectory, sourceObjects);
|
|
1153
1154
|
const actualTaskTreeOid = await prepareIndexFromPatch(localGitRoot, join(tempRoot, 'task.index'), current.headOid, taskPatch);
|
|
1154
1155
|
if (actualTaskTreeOid !== assessment.taskTreeOid) {
|
|
1155
|
-
return { status: 'error', error: { code: 'git_error', message: '
|
|
1156
|
+
return { status: 'error', error: { code: 'git_error', message: hostMessage('thePreviewTaskCommitTreeDoesNotMatchThe') } };
|
|
1156
1157
|
}
|
|
1157
1158
|
const commitOid = await createUserCommit(localGitRoot, actualTaskTreeOid, current.headOid, commitMessage);
|
|
1158
1159
|
const finalIndexPath = join(tempRoot, 'final.index');
|
|
1159
1160
|
const actualFinalIndexTreeOid = await prepareIndexFromPatch(localGitRoot, finalIndexPath, commitOid, finalIndexPatch);
|
|
1160
1161
|
if (actualFinalIndexTreeOid !== assessment.finalIndexTreeOid) {
|
|
1161
|
-
return { status: 'error', error: { code: 'git_error', message: '
|
|
1162
|
+
return { status: 'error', error: { code: 'git_error', message: hostMessage('thePreviewFinalIndexTreeDoesNotMatchThe') } };
|
|
1162
1163
|
}
|
|
1163
1164
|
const expectedIndexEntries = (await runGit(localGitRoot, ['ls-files', '--stage', '-z'], { env: { GIT_INDEX_FILE: finalIndexPath } })).stdout;
|
|
1164
1165
|
const intendedIndexBytes = await readFile(finalIndexPath);
|
|
@@ -1166,7 +1167,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1166
1167
|
await input.beforeCommit?.(commitOid);
|
|
1167
1168
|
const branchRef = current.headRef;
|
|
1168
1169
|
if (!branchRef?.startsWith('refs/heads/')) {
|
|
1169
|
-
return { status: 'error', error: { code: 'operation_not_allowed', message: '
|
|
1170
|
+
return { status: 'error', error: { code: 'operation_not_allowed', message: hostMessage('localIsNotOnANormalBranchATask') } };
|
|
1170
1171
|
}
|
|
1171
1172
|
backupIndex = `${realIndexPath}.domi-backup-${randomUUID()}`;
|
|
1172
1173
|
await copyFile(realIndexPath, backupIndex);
|
|
@@ -1178,7 +1179,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1178
1179
|
}
|
|
1179
1180
|
catch (error) {
|
|
1180
1181
|
if (isIndexLockContention(error)) {
|
|
1181
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
1182
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('anotherGitOperationIsUpdatingTheLocalIndexRetry') } };
|
|
1182
1183
|
}
|
|
1183
1184
|
throw error;
|
|
1184
1185
|
}
|
|
@@ -1188,7 +1189,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1188
1189
|
|| finalLocal.headRef !== current.headRef
|
|
1189
1190
|
|| finalLocal.fingerprint !== current.fingerprint
|
|
1190
1191
|
|| !finalIndexBytes.equals(originalIndexBytes)) {
|
|
1191
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
1192
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedBeforeThePreviewCommitWasWrittenRetry') } };
|
|
1192
1193
|
}
|
|
1193
1194
|
let refUpdated = false;
|
|
1194
1195
|
let indexReplaced = false;
|
|
@@ -1223,7 +1224,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1223
1224
|
nextBaseOid: input.receipt.isolatedSnapshotOid,
|
|
1224
1225
|
};
|
|
1225
1226
|
}
|
|
1226
|
-
writeError = new Error('
|
|
1227
|
+
writeError = new Error(hostMessage('previewCommitVerificationFailedAfterWriting'));
|
|
1227
1228
|
}
|
|
1228
1229
|
catch (error) {
|
|
1229
1230
|
writeError = error;
|
|
@@ -1251,7 +1252,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1251
1252
|
ownedIndexLock = await writeIndexLock(realIndexPath, backupBytes);
|
|
1252
1253
|
const currentIndexBytes = await readFile(realIndexPath);
|
|
1253
1254
|
if (!currentIndexBytes.equals(intendedIndexBytes)) {
|
|
1254
|
-
rollbackErrors.push('
|
|
1255
|
+
rollbackErrors.push(hostMessage('indexLocalIndexChangedAfterPreviewWroteItsChanges'));
|
|
1255
1256
|
}
|
|
1256
1257
|
else {
|
|
1257
1258
|
await runGit(localGitRoot, ['update-ref', branchRef, current.headOid, commitOid]);
|
|
@@ -1266,7 +1267,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1266
1267
|
}
|
|
1267
1268
|
}
|
|
1268
1269
|
else if (refUpdated) {
|
|
1269
|
-
rollbackErrors.push('
|
|
1270
|
+
rollbackErrors.push(hostMessage('refVerifiableIndexCompensationEvidenceIsMissingPartialRollback'));
|
|
1270
1271
|
}
|
|
1271
1272
|
let restored = false;
|
|
1272
1273
|
if (rollbackErrors.length === 0) {
|
|
@@ -1291,7 +1292,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1291
1292
|
status: 'error',
|
|
1292
1293
|
error: {
|
|
1293
1294
|
code: 'git_error',
|
|
1294
|
-
message:
|
|
1295
|
+
message: hostMessage('thePreviewCommitWriteCouldNotBeVerifiedAnd', { p0: this.errorMessage(writeError) }),
|
|
1295
1296
|
recoveryState: 'unchanged',
|
|
1296
1297
|
},
|
|
1297
1298
|
};
|
|
@@ -1302,7 +1303,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1302
1303
|
status: 'error',
|
|
1303
1304
|
error: {
|
|
1304
1305
|
code: 'git_error',
|
|
1305
|
-
message:
|
|
1306
|
+
message: hostMessage('cannotProveSuccessOrCompleteRollbackAfterWritingThe', { p0: this.errorMessage(writeError), p1: rollbackDetail }),
|
|
1306
1307
|
recoveryState: 'uncertain',
|
|
1307
1308
|
},
|
|
1308
1309
|
};
|
|
@@ -1321,16 +1322,16 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1321
1322
|
async finish(plan, options) {
|
|
1322
1323
|
const stored = this.plans.get(plan.revision);
|
|
1323
1324
|
if (!stored || !planMatches(stored.plan, plan)) {
|
|
1324
|
-
return { status: 'error', error: { code: 'invalid_plan', message: '
|
|
1325
|
+
return { status: 'error', error: { code: 'invalid_plan', message: hostMessage('theFinishPlanIsMissingAlreadyUsedOrModified') } };
|
|
1325
1326
|
}
|
|
1326
1327
|
const commitMessage = options.commitMessage.trim();
|
|
1327
1328
|
if (!commitMessage) {
|
|
1328
|
-
return { status: 'error', error: { code: 'invalid_input', message: '
|
|
1329
|
+
return { status: 'error', error: { code: 'invalid_input', message: hostMessage('theCommitMessageMustNotBeEmpty') } };
|
|
1329
1330
|
}
|
|
1330
1331
|
if (!stored.plan.localHeadRef?.startsWith('refs/heads/')) {
|
|
1331
1332
|
return {
|
|
1332
1333
|
status: 'error',
|
|
1333
|
-
error: { code: 'operation_not_allowed', message: '
|
|
1334
|
+
error: { code: 'operation_not_allowed', message: hostMessage('localIsNotOnANormalBranchATask') },
|
|
1334
1335
|
};
|
|
1335
1336
|
}
|
|
1336
1337
|
let tempRoot = null;
|
|
@@ -1341,39 +1342,39 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1341
1342
|
await mkdir(objectDirectory, { recursive: true });
|
|
1342
1343
|
const sourceObjects = await sourceObjectDirectory(stored.scope.localGitRoot);
|
|
1343
1344
|
if (!pathsMatch(sourceObjects, stored.scope.sourceObjects)) {
|
|
1344
|
-
return { status: 'error', error: { code: 'invalid_plan', message: '
|
|
1345
|
+
return { status: 'error', error: { code: 'invalid_plan', message: hostMessage('theFinishPlanSGitRepositoryIdentityHasChanged') } };
|
|
1345
1346
|
}
|
|
1346
1347
|
const local = await captureSnapshot(stored.scope.localGitRoot, join(tempRoot, 'local.index'), objectDirectory, sourceObjects);
|
|
1347
1348
|
if (local.headOid !== stored.plan.localHeadOid
|
|
1348
1349
|
|| local.headRef !== stored.plan.localHeadRef
|
|
1349
1350
|
|| local.fingerprint !== stored.plan.localFingerprint) {
|
|
1350
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
1351
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedAfterPlanningRecalculateThePlan') } };
|
|
1351
1352
|
}
|
|
1352
1353
|
const isolated = await captureSnapshot(stored.scope.isolatedGitRoot, join(tempRoot, 'isolated.index'), objectDirectory, sourceObjects);
|
|
1353
1354
|
if (isolated.headOid !== stored.plan.isolatedHeadOid || isolated.fingerprint !== stored.plan.isolatedFingerprint) {
|
|
1354
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
1355
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('isolatedChangedAfterPlanningRecalculateThePlan') } };
|
|
1355
1356
|
}
|
|
1356
1357
|
const merge = await computeMerge(tempRoot, sourceObjects, objectDirectory, stored.plan.effectiveBaseOid, local, isolated, stored.scope.localGitRoot, stored.scope.projectPrefix);
|
|
1357
1358
|
if (merge.status === 'conflict') {
|
|
1358
1359
|
return {
|
|
1359
1360
|
status: 'error',
|
|
1360
|
-
error: { code: 'invalid_plan', message: '
|
|
1361
|
+
error: { code: 'invalid_plan', message: hostMessage('finishRevalidationFoundConflictsInconsistentWithTheReviewedPlan') },
|
|
1361
1362
|
};
|
|
1362
1363
|
}
|
|
1363
1364
|
if (merge.changedFiles.length !== stored.plan.changedFiles.length
|
|
1364
1365
|
|| merge.changedFiles.some((path, index) => path !== stored.plan.changedFiles[index])) {
|
|
1365
|
-
return { status: 'error', error: { code: 'invalid_plan', message: '
|
|
1366
|
+
return { status: 'error', error: { code: 'invalid_plan', message: hostMessage('theFileSetChangedDuringFinishRevalidation') } };
|
|
1366
1367
|
}
|
|
1367
1368
|
const persistentIsolated = await captureSnapshot(stored.scope.isolatedGitRoot, join(tempRoot, 'persistent-isolated.index'), null, null);
|
|
1368
1369
|
if (persistentIsolated.fingerprint !== stored.plan.isolatedFingerprint) {
|
|
1369
|
-
return { status: 'error', error: { code: 'stale_isolated', message: '
|
|
1370
|
+
return { status: 'error', error: { code: 'stale_isolated', message: hostMessage('isolatedChangedAfterPlanningRecalculateThePlan') } };
|
|
1370
1371
|
}
|
|
1371
1372
|
const nextBaseOid = await createSnapshotCommit(stored.scope.isolatedGitRoot, persistentIsolated.treeOid, stored.plan.effectiveBaseOid, null, null, 'Finished Isolated snapshot');
|
|
1372
1373
|
if (merge.changedFiles.length === 0) {
|
|
1373
1374
|
await this.options.beforeFinalLocalValidation?.();
|
|
1374
1375
|
const finalLocal = await captureSnapshot(stored.scope.localGitRoot, join(tempRoot, 'final-local.index'), objectDirectory, sourceObjects);
|
|
1375
1376
|
if (finalLocal.fingerprint !== stored.plan.localFingerprint) {
|
|
1376
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
1377
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedBeforeFinishRecalculateThePlan') } };
|
|
1377
1378
|
}
|
|
1378
1379
|
this.plans.delete(plan.revision);
|
|
1379
1380
|
return { status: 'finished', changedFiles: [], commitOid: null, nextBaseOid };
|
|
@@ -1385,7 +1386,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1385
1386
|
status: 'error',
|
|
1386
1387
|
error: {
|
|
1387
1388
|
code: 'commit_isolation_conflict',
|
|
1388
|
-
message:
|
|
1389
|
+
message: hostMessage('taskChangesCannotBeReliablySeparatedFromExistingLocal', { p0: taskTree.conflictingFiles.join('、') }),
|
|
1389
1390
|
},
|
|
1390
1391
|
};
|
|
1391
1392
|
}
|
|
@@ -1396,7 +1397,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1396
1397
|
status: 'error',
|
|
1397
1398
|
error: {
|
|
1398
1399
|
code: 'commit_isolation_conflict',
|
|
1399
|
-
message:
|
|
1400
|
+
message: hostMessage('theTaskCommitCannotBeReliablySeparatedFromExisting', { p0: finalIndexTree.conflictingFiles.join('、') }),
|
|
1400
1401
|
},
|
|
1401
1402
|
};
|
|
1402
1403
|
}
|
|
@@ -1412,7 +1413,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1412
1413
|
if (finalLocal.headOid !== stored.plan.localHeadOid
|
|
1413
1414
|
|| finalLocal.headRef !== stored.plan.localHeadRef
|
|
1414
1415
|
|| finalLocal.fingerprint !== stored.plan.localFingerprint) {
|
|
1415
|
-
return { status: 'error', error: { code: 'stale_local', message: '
|
|
1416
|
+
return { status: 'error', error: { code: 'stale_local', message: hostMessage('localChangedBeforeFinishWroteItsChangesRecalculateThe') } };
|
|
1416
1417
|
}
|
|
1417
1418
|
const realIndexPath = await resolveIndexPath(stored.scope.localGitRoot);
|
|
1418
1419
|
adjacentIndex = `${realIndexPath}.domi-${randomUUID()}`;
|
|
@@ -1463,11 +1464,11 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1463
1464
|
status: 'error',
|
|
1464
1465
|
error: {
|
|
1465
1466
|
code: 'git_error',
|
|
1466
|
-
message:
|
|
1467
|
+
message: hostMessage('finishWriteFailedAndCompleteRollbackCouldNotBe', { p0: detail, p1: rollbackErrors.join(';') }),
|
|
1467
1468
|
},
|
|
1468
1469
|
};
|
|
1469
1470
|
}
|
|
1470
|
-
return { status: 'error', error: { code: 'git_error', message:
|
|
1471
|
+
return { status: 'error', error: { code: 'git_error', message: hostMessage('finishWriteFailedAndWasRolledBack', { p0: detail }) } };
|
|
1471
1472
|
}
|
|
1472
1473
|
this.plans.delete(plan.revision);
|
|
1473
1474
|
return {
|
|
@@ -1488,9 +1489,9 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1488
1489
|
}
|
|
1489
1490
|
errorMessage(error) {
|
|
1490
1491
|
if (error instanceof GitCommandFailure) {
|
|
1491
|
-
return
|
|
1492
|
+
return hostMessage('gitOperationFailed', { p0: error.args[0] ?? 'unknown', p1: error.message });
|
|
1492
1493
|
}
|
|
1493
|
-
return error instanceof Error ? error.message : '
|
|
1494
|
+
return error instanceof Error ? error.message : hostMessage('unknownGitError');
|
|
1494
1495
|
}
|
|
1495
1496
|
async cleanup(path) {
|
|
1496
1497
|
try {
|
|
@@ -1498,7 +1499,7 @@ class DefaultSessionCheckoutApplyEngine {
|
|
|
1498
1499
|
}
|
|
1499
1500
|
catch (error) {
|
|
1500
1501
|
// 清理失败不能掩盖 plan/apply 的主结果。
|
|
1501
|
-
console.warn('
|
|
1502
|
+
console.warn(hostMessage('tempDirectoryCleanupFailed'), error);
|
|
1502
1503
|
}
|
|
1503
1504
|
}
|
|
1504
1505
|
}
|