@straiffi/archon 1.8.5 → 1.8.6
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/dist/client/assets/{ActivityView-DGP4Juye.js → ActivityView-DDeEQgbm.js} +1 -1
- package/dist/client/assets/{GitHubActionsDialog-P_mpYAOO.js → GitHubActionsDialog-DH-B_LR3.js} +1 -1
- package/dist/client/assets/{VerificationDialog-C6FVSzcF.js → VerificationDialog-x43F7tbo.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BB0biSU2.js → architectureDiagram-3BPJPVTR-BH5LLK54.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-Bxm9GAiX.js → blockDiagram-GPEHLZMM-xOxShPwZ.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CC3YHLX3.js → c4Diagram-AAUBKEIU-BXMrXnKR.js} +1 -1
- package/dist/client/assets/channel-C0dk5Jiy.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-ComBHKwP.js → chunk-2J33WTMH-BdDEtNMz.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DSpFzCXF.js → chunk-3OPIFGDE-PtSkTtIU.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-pL2M3Ax3.js → chunk-5ZQYHXKU-D5PYP24m.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-BPi_lguN.js → chunk-727SXJPM-gDX0xZh1.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-Coc6laVv.js → chunk-AQP2D5EJ-Bat82lQQ.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-CpaX09-x.js → chunk-CSCIHK7Q-D3sau006.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-CLzz2KFY.js → chunk-KSCS5N6A-CqEcB7Dr.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DyyDuDZm.js → chunk-L5ZTLDWV-TRTuwXsF.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-Cm5npwZA.js → chunk-LZXEDZCA-DdpNmv37.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-CaopONUA.js → chunk-ND2GUHAM-BCUL9zWr.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-DIzt4PM_.js → chunk-NZK2D7GU-BAxuwE_2.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-C7Re_6FB.js → chunk-O5CBEL6O-Cttpc9yf.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-D5lmGfNl.js → chunk-WU5MYG2G-620NtSlY.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C5ptkk7I.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BFuXUVOx.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-BdFEW6fG.js → dagre-BM42HDAG-BTsZ2dQI.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-D2ZuWfhH.js → diagram-2AECGRRQ-afPu4kp4.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-Cs_AmUDw.js → diagram-5GNKFQAL-SixbJGPN.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DICE0UrL.js → diagram-KO2AKTUF-Dk1670ag.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-MwtTXZWl.js → diagram-LMA3HP47-BDNhjRaO.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-CxDJ75F5.js → diagram-OG6HWLK6-BLowQCLY.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-XgZtP-BK.js → erDiagram-TEJ5UH35-DAg9kUbm.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-B8uIExT5.js → flowDiagram-I6XJVG4X-BsaPmiIS.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-B6TrSB4S.js → ganttDiagram-6RSMTGT7-DfF1TT-s.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-qfMWSncV.js → gitGraphDiagram-PVQCEYII-CEvTiN6n.js} +1 -1
- package/dist/client/assets/index-B5TSPCJr.css +2 -0
- package/dist/client/assets/index-DV0vl670.js +274 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-CuOimNjJ.js → infoDiagram-5YYISTIA-CBjYYrzJ.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BWHbLru4.js → ishikawaDiagram-YF4QCWOH-BuCkp1uL.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-SAbFsYxI.js → journeyDiagram-JHISSGLW-DkUPCxT5.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-m4ZsWr_S.js → kanban-definition-UN3LZRKU-CXwNZ0Fa.js} +1 -1
- package/dist/client/assets/{line-C4dSESbS.js → line-CRTZgJXA.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-CstDlNL1.js → mermaid-parser.core-NTByLn30.js} +1 -1
- package/dist/client/assets/{mermaid.core-B-Yj5Lxm.js → mermaid.core-DszsB_Gl.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-Bzf9o0Md.js → mindmap-definition-RKZ34NQL-DCrqCcPX.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-C8c0F0hh.js → pieDiagram-4H26LBE5-CbHeTKto.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BewK5Lws.js → quadrantDiagram-W4KKPZXB-Ptpkh0Dc.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-C_gYL5ls.js → requirementDiagram-4Y6WPE33-73PM-Ae5.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Dt8OrhUl.js → sankeyDiagram-5OEKKPKP-B7RCxzNy.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Z578DkVy.js → sequenceDiagram-3UESZ5HK-DGXfHCo0.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CyQ5eQTE.js → stateDiagram-AJRCARHV-Cl0_SVL3.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CqzC1yAT.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-DMtbr-l6.js → timeline-definition-PNZ67QCA-Cufq-wYg.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BToePHO4.js → vennDiagram-CIIHVFJN-ROVZhhP-.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-FAzbTjvI.js → wardleyDiagram-YWT4CUSO-DlHRfLPH.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-BrlUCOgp.js → xychartDiagram-2RQKCTM6-DW-KpRbf.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +118 -10
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/automationResumeDecision.js +6 -0
- package/dist/server/lib/automationResumeDecision.js.map +1 -1
- package/dist/server/lib/bundleReviewOperations.js +81 -2
- package/dist/server/lib/bundleReviewOperations.js.map +1 -1
- package/dist/server/lib/figmaResources.js +15 -1
- package/dist/server/lib/figmaResources.js.map +1 -1
- package/dist/server/lib/git.js +312 -32
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/reviewFindings.js +31 -5
- package/dist/server/lib/reviewFindings.js.map +1 -1
- package/dist/server/lib/specArtifact.js +5 -1
- package/dist/server/lib/specArtifact.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +2 -2
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/followUp.js +1 -1
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/review.js +72 -6
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specSession.js +4 -1
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-CQOfjP3q.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DGUeerOX.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Cnum-Tdt.js +0 -1
- package/dist/client/assets/index-CzRhzdmk.js +0 -274
- package/dist/client/assets/index-DZqqZx53.css +0 -2
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C9e0OccX.js +0 -1
package/dist/server/lib/git.js
CHANGED
|
@@ -251,6 +251,15 @@ const resolveHeadCommit = (cwd) => {
|
|
|
251
251
|
const output = runGitText(['rev-parse', 'HEAD'], cwd).trim();
|
|
252
252
|
return output || null;
|
|
253
253
|
};
|
|
254
|
+
const resolveCommitSha = (revision, cwd) => {
|
|
255
|
+
try {
|
|
256
|
+
const output = runGitText(['rev-parse', '--verify', `${revision}^{commit}`], cwd).trim();
|
|
257
|
+
return output || null;
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
254
263
|
const countCommitRange = (range, cwd) => {
|
|
255
264
|
const output = runGitText(['rev-list', '--count', range], cwd).trim();
|
|
256
265
|
const count = Number.parseInt(output || '0', 10);
|
|
@@ -318,6 +327,35 @@ const resolveBranchUpstreamFetchSpec = (branch, cwd) => {
|
|
|
318
327
|
const upstreamRef = resolveBranchUpstreamRef(branch, cwd);
|
|
319
328
|
return parseRemoteBranchRef(upstreamRef);
|
|
320
329
|
};
|
|
330
|
+
const refreshSquashUpstream = async (branch, cwd) => {
|
|
331
|
+
const upstreamRef = resolveBranchUpstreamRef(branch, cwd);
|
|
332
|
+
if (!upstreamRef) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
const fetchSpec = parseRemoteBranchRef(upstreamRef);
|
|
336
|
+
if (!fetchSpec) {
|
|
337
|
+
const localUpstreamCommitSha = resolveCommitSha(upstreamRef, cwd);
|
|
338
|
+
if (!localUpstreamCommitSha) {
|
|
339
|
+
throw new Error('Unable to resolve the local upstream before squashing commits. Refresh and try again.');
|
|
340
|
+
}
|
|
341
|
+
return localUpstreamCommitSha;
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
await runGitTextAsync(['fetch', '--quiet', fetchSpec.remote, fetchSpec.branch], cwd);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
throw new Error('Unable to refresh the upstream branch before squashing commits. Pull or reconcile the branch and try again.');
|
|
348
|
+
}
|
|
349
|
+
const refreshedUpstreamRef = resolveBranchUpstreamRef(branch, cwd);
|
|
350
|
+
if (refreshedUpstreamRef !== upstreamRef) {
|
|
351
|
+
throw new Error('The upstream branch changed while preparing the squash. Refresh and try again.');
|
|
352
|
+
}
|
|
353
|
+
const upstreamCommitSha = resolveCommitSha(refreshedUpstreamRef, cwd);
|
|
354
|
+
if (!upstreamCommitSha) {
|
|
355
|
+
throw new Error('Unable to resolve the refreshed upstream commit before squashing commits. Refresh and try again.');
|
|
356
|
+
}
|
|
357
|
+
return upstreamCommitSha;
|
|
358
|
+
};
|
|
321
359
|
const resolveGitDir = (cwd) => {
|
|
322
360
|
const gitDir = runGitText(['rev-parse', '--git-dir'], cwd).trim();
|
|
323
361
|
if (!gitDir) {
|
|
@@ -343,6 +381,26 @@ const getGitOperationState = (cwd) => {
|
|
|
343
381
|
}
|
|
344
382
|
return 'idle';
|
|
345
383
|
};
|
|
384
|
+
const hasActiveGitOperation = (cwd) => {
|
|
385
|
+
if (getGitOperationState(cwd) !== 'idle') {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
const gitDir = resolveGitDir(cwd);
|
|
390
|
+
if (!gitDir) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
return [
|
|
394
|
+
'CHERRY_PICK_HEAD',
|
|
395
|
+
'REVERT_HEAD',
|
|
396
|
+
'BISECT_LOG',
|
|
397
|
+
'sequencer',
|
|
398
|
+
].some(marker => existsSync(join(gitDir, marker)));
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
346
404
|
const getBranchTrackingState = (branch, cwd) => {
|
|
347
405
|
if (!resolveBranchUpstreamRef(branch, cwd)) {
|
|
348
406
|
try {
|
|
@@ -500,6 +558,13 @@ const resolveCommitBaseFromBaseRef = (baseRef, cwd) => {
|
|
|
500
558
|
}
|
|
501
559
|
return null;
|
|
502
560
|
};
|
|
561
|
+
const resolveRecordedBaseCommit = (cwd, baseBranch = null, baseCommit = null) => {
|
|
562
|
+
const baseBranchCommit = resolveCommitBaseFromBaseRef(resolveNamedBranchRef(baseBranch, cwd), cwd);
|
|
563
|
+
if (baseBranchCommit) {
|
|
564
|
+
return baseBranchCommit;
|
|
565
|
+
}
|
|
566
|
+
return baseCommit?.trim() || null;
|
|
567
|
+
};
|
|
503
568
|
const resolveCommitRangeFromBaseRef = (baseRef, cwd) => {
|
|
504
569
|
const baseCommit = resolveCommitBaseFromBaseRef(baseRef, cwd);
|
|
505
570
|
return baseCommit ? `${baseCommit}..HEAD` : null;
|
|
@@ -508,25 +573,37 @@ const resolveBranchUniqueRange = (cwd) => {
|
|
|
508
573
|
return resolveCommitRangeFromBaseRef(resolveDefaultBranchRef(cwd), cwd);
|
|
509
574
|
};
|
|
510
575
|
const resolveRecordedBaseRange = (cwd, baseBranch = null, baseCommit = null) => {
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
return baseBranchRange;
|
|
514
|
-
}
|
|
515
|
-
if (baseCommit) {
|
|
516
|
-
return `${baseCommit}..HEAD`;
|
|
517
|
-
}
|
|
518
|
-
return null;
|
|
576
|
+
const resolvedBaseCommit = resolveRecordedBaseCommit(cwd, baseBranch, baseCommit);
|
|
577
|
+
return resolvedBaseCommit ? `${resolvedBaseCommit}..HEAD` : null;
|
|
519
578
|
};
|
|
520
579
|
const resolveUnpushedCommitRange = (cwd, branch, baseBranch = null, baseCommit = null) => {
|
|
521
580
|
const upstreamRef = resolveBranchUpstreamRef(branch, cwd);
|
|
522
581
|
if (upstreamRef) {
|
|
523
|
-
|
|
582
|
+
const upstreamCommit = resolveCommitSha(upstreamRef, cwd) ?? upstreamRef;
|
|
583
|
+
return {
|
|
584
|
+
range: `${upstreamCommit}..HEAD`,
|
|
585
|
+
baseCommit: upstreamCommit,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
const recordedBaseCommit = resolveRecordedBaseCommit(cwd, baseBranch, baseCommit);
|
|
589
|
+
if (recordedBaseCommit) {
|
|
590
|
+
return {
|
|
591
|
+
range: `${recordedBaseCommit}..HEAD`,
|
|
592
|
+
baseCommit: recordedBaseCommit,
|
|
593
|
+
};
|
|
524
594
|
}
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
595
|
+
const defaultBaseRef = resolveDefaultBranchRef(cwd);
|
|
596
|
+
const defaultBaseCommit = resolveCommitBaseFromBaseRef(defaultBaseRef, cwd);
|
|
597
|
+
if (defaultBaseCommit) {
|
|
598
|
+
return {
|
|
599
|
+
range: `${defaultBaseCommit}..HEAD`,
|
|
600
|
+
baseCommit: defaultBaseCommit,
|
|
601
|
+
};
|
|
528
602
|
}
|
|
529
|
-
return
|
|
603
|
+
return {
|
|
604
|
+
range: null,
|
|
605
|
+
baseCommit: null,
|
|
606
|
+
};
|
|
530
607
|
};
|
|
531
608
|
export const resolveBundleReviewBase = ({ cwd, branch, project, pullRequestBaseBranch = null, }) => {
|
|
532
609
|
const normalizedPullRequestBaseBranch = pullRequestBaseBranch?.trim() || null;
|
|
@@ -639,18 +716,31 @@ const getDiffDataForBaseRefAsync = async (cwd, baseRef) => {
|
|
|
639
716
|
};
|
|
640
717
|
}
|
|
641
718
|
};
|
|
719
|
+
const getUnpushedRangeArgs = (resolvedRange) => {
|
|
720
|
+
return resolvedRange.range ? [resolvedRange.range] : ['HEAD', '--not', '--remotes'];
|
|
721
|
+
};
|
|
722
|
+
const getUnpushedCommitSummary = (cwd, branch, baseBranch = null, baseCommit = null) => {
|
|
723
|
+
const resolvedRange = resolveUnpushedCommitRange(cwd, branch, baseBranch, baseCommit);
|
|
724
|
+
const rangeArgs = getUnpushedRangeArgs(resolvedRange);
|
|
725
|
+
const countOutput = runGitText(['rev-list', '--count', ...rangeArgs], cwd).trim();
|
|
726
|
+
const totalCount = Number.parseInt(countOutput || '0', 10);
|
|
727
|
+
const output = runGitText([
|
|
728
|
+
'log',
|
|
729
|
+
'--format=%H%x00%h%x00%s%x00%cI%x00',
|
|
730
|
+
'--no-show-signature',
|
|
731
|
+
'-n',
|
|
732
|
+
'20',
|
|
733
|
+
...rangeArgs,
|
|
734
|
+
], cwd);
|
|
735
|
+
return {
|
|
736
|
+
commits: parseUnpushedCommitsOutput(output),
|
|
737
|
+
totalCount: Number.isFinite(totalCount) ? totalCount : 0,
|
|
738
|
+
baseCommit: resolvedRange.baseCommit,
|
|
739
|
+
};
|
|
740
|
+
};
|
|
642
741
|
const listUnpushedCommitRecords = (cwd, branch, baseBranch = null, baseCommit = null, options = {}) => {
|
|
643
742
|
try {
|
|
644
|
-
|
|
645
|
-
const output = runGitText([
|
|
646
|
-
'log',
|
|
647
|
-
'--format=%H%x00%h%x00%s%x00%cI%x00',
|
|
648
|
-
'--no-show-signature',
|
|
649
|
-
'-n',
|
|
650
|
-
'20',
|
|
651
|
-
...(branchUniqueRange ? [branchUniqueRange] : ['HEAD', '--not', '--remotes']),
|
|
652
|
-
], cwd);
|
|
653
|
-
return parseUnpushedCommitsOutput(output);
|
|
743
|
+
return getUnpushedCommitSummary(cwd, branch, baseBranch, baseCommit).commits;
|
|
654
744
|
}
|
|
655
745
|
catch (error) {
|
|
656
746
|
if (options.throwOnError) {
|
|
@@ -1874,10 +1964,25 @@ export const pushWorktreeBranch = async (branch, project = null, options = {}) =
|
|
|
1874
1964
|
const cwd = resolveWorktreePath(branch, project);
|
|
1875
1965
|
await pushCurrentBranch(cwd, branch, options);
|
|
1876
1966
|
};
|
|
1967
|
+
export const getUnpushedCommitSummaryForCwd = (cwd, branch, project = null, options = {}) => {
|
|
1968
|
+
try {
|
|
1969
|
+
const recordedBase = project ? resolveRecordedBaseForCwd(cwd, branch, project) : null;
|
|
1970
|
+
const fallbackBase = recordedBase ?? (project ? resolveFallbackBase(branch, project) : null);
|
|
1971
|
+
return getUnpushedCommitSummary(cwd, branch, fallbackBase?.baseBranch ?? null, fallbackBase?.baseCommit ?? null);
|
|
1972
|
+
}
|
|
1973
|
+
catch (error) {
|
|
1974
|
+
if (options.throwOnError) {
|
|
1975
|
+
throw error;
|
|
1976
|
+
}
|
|
1977
|
+
return {
|
|
1978
|
+
commits: [],
|
|
1979
|
+
totalCount: 0,
|
|
1980
|
+
baseCommit: null,
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1877
1984
|
export const listUnpushedCommitsForCwd = (cwd, branch, project = null, options = {}) => {
|
|
1878
|
-
|
|
1879
|
-
const fallbackBase = recordedBase ?? (project ? resolveFallbackBase(branch, project) : null);
|
|
1880
|
-
return listUnpushedCommitRecords(cwd, branch, fallbackBase?.baseBranch ?? null, fallbackBase?.baseCommit ?? null, options);
|
|
1985
|
+
return getUnpushedCommitSummaryForCwd(cwd, branch, project, options).commits;
|
|
1881
1986
|
};
|
|
1882
1987
|
const resolveFirstParentOrEmptyTree = (cwd, commitHash) => {
|
|
1883
1988
|
const commitAndParents = runGitText(['rev-list', '--parents', '-n', '1', commitHash], cwd).trim().split(/\s+/);
|
|
@@ -1892,24 +1997,199 @@ export const getUnpushedCommitDiffForCwd = ({ cwd, branch, project = null, commi
|
|
|
1892
1997
|
if (commitHash !== null && !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(normalizedCommitHash ?? '')) {
|
|
1893
1998
|
throw new UnpushedCommitDiffSelectionError('commit must be a full 40- or 64-character Git commit hash.');
|
|
1894
1999
|
}
|
|
1895
|
-
const
|
|
1896
|
-
if (commits.length === 0) {
|
|
2000
|
+
const summary = getUnpushedCommitSummaryForCwd(cwd, branch, project, { throwOnError: true });
|
|
2001
|
+
if (summary.commits.length === 0) {
|
|
1897
2002
|
throw new UnpushedCommitDiffSelectionError('There are no local commits to compare.');
|
|
1898
2003
|
}
|
|
1899
2004
|
const selectedCommit = normalizedCommitHash
|
|
1900
|
-
? commits.find(commit => commit.hash === normalizedCommitHash)
|
|
1901
|
-
: commits[0];
|
|
2005
|
+
? summary.commits.find(commit => commit.hash === normalizedCommitHash)
|
|
2006
|
+
: summary.commits[0];
|
|
1902
2007
|
if (!selectedCommit) {
|
|
1903
2008
|
throw new UnpushedCommitDiffSelectionError('The selected commit is no longer in the local commits list.');
|
|
1904
2009
|
}
|
|
1905
|
-
const oldestCommit = normalizedCommitHash ? selectedCommit : commits[commits.length - 1];
|
|
1906
|
-
const baseCommit =
|
|
2010
|
+
const oldestCommit = normalizedCommitHash ? selectedCommit : summary.commits[summary.commits.length - 1];
|
|
2011
|
+
const baseCommit = normalizedCommitHash
|
|
2012
|
+
? resolveFirstParentOrEmptyTree(cwd, oldestCommit.hash)
|
|
2013
|
+
: (summary.baseCommit ?? resolveFirstParentOrEmptyTree(cwd, oldestCommit.hash));
|
|
1907
2014
|
return getCommittedDiffData(cwd, baseCommit, selectedCommit.hash);
|
|
1908
2015
|
};
|
|
1909
2016
|
export const listUnpushedCommits = (branch, project = null) => {
|
|
1910
2017
|
const cwd = resolveWorktreePath(branch, project);
|
|
1911
2018
|
return listUnpushedCommitsForCwd(cwd, branch, project);
|
|
1912
2019
|
};
|
|
2020
|
+
const assertSquashWorkspace = (cwd, branch, project) => {
|
|
2021
|
+
if (hasActiveGitOperation(cwd)) {
|
|
2022
|
+
throw new Error('Finish or abort the current Git operation before squashing commits.');
|
|
2023
|
+
}
|
|
2024
|
+
const status = getGitStatus(cwd, branch, project);
|
|
2025
|
+
if (!status.branch) {
|
|
2026
|
+
throw new Error('Unable to verify the current branch before squashing commits.');
|
|
2027
|
+
}
|
|
2028
|
+
if (status.branch !== branch) {
|
|
2029
|
+
throw new Error(`The workspace branch changed to "${status.branch}". Refresh and try again.`);
|
|
2030
|
+
}
|
|
2031
|
+
if (status.operation_state !== 'idle') {
|
|
2032
|
+
throw new Error('Finish or abort the current Git operation before squashing commits.');
|
|
2033
|
+
}
|
|
2034
|
+
if (status.files.some(file => file.conflicted)) {
|
|
2035
|
+
throw new Error('Resolve conflicted files before squashing commits.');
|
|
2036
|
+
}
|
|
2037
|
+
if (status.is_dirty || status.has_staged_changes || status.has_unstaged_changes) {
|
|
2038
|
+
throw new Error('Commit, stash, or discard workspace changes before squashing commits.');
|
|
2039
|
+
}
|
|
2040
|
+
return status;
|
|
2041
|
+
};
|
|
2042
|
+
const getSquashCommitSummary = (cwd, branch, project) => {
|
|
2043
|
+
try {
|
|
2044
|
+
return getUnpushedCommitSummaryForCwd(cwd, branch, project, { throwOnError: true });
|
|
2045
|
+
}
|
|
2046
|
+
catch {
|
|
2047
|
+
throw new Error('Unable to resolve the complete unpushed commit range. Reconcile the branch and refresh before squashing commits.');
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
const assertSquashStateUnchanged = async ({ cwd, branch, project, branchRef, headSha, baseCommitSha, totalCount, }) => {
|
|
2051
|
+
const currentBranchRef = runGitText(['symbolic-ref', '--quiet', 'HEAD'], cwd).trim();
|
|
2052
|
+
const currentHeadSha = resolveHeadCommit(cwd);
|
|
2053
|
+
if (currentBranchRef !== branchRef || currentHeadSha !== headSha) {
|
|
2054
|
+
throw new Error('The branch or HEAD changed while preparing the squash. Refresh and try again.');
|
|
2055
|
+
}
|
|
2056
|
+
assertSquashWorkspace(cwd, branch, project);
|
|
2057
|
+
const refreshedUpstreamCommitSha = await refreshSquashUpstream(branch, cwd);
|
|
2058
|
+
const currentSummary = getSquashCommitSummary(cwd, branch, project);
|
|
2059
|
+
const currentBaseCommitSha = currentSummary.baseCommit
|
|
2060
|
+
? resolveCommitSha(currentSummary.baseCommit, cwd)
|
|
2061
|
+
: null;
|
|
2062
|
+
if (currentSummary.totalCount !== totalCount
|
|
2063
|
+
|| currentBaseCommitSha !== baseCommitSha
|
|
2064
|
+
|| (refreshedUpstreamCommitSha !== null && refreshedUpstreamCommitSha !== baseCommitSha)) {
|
|
2065
|
+
throw new Error('The unpushed commit range changed while preparing the squash. Refresh and try again.');
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
export const squashUnpushedCommitsForCwd = async (cwd, branch, project, message) => {
|
|
2069
|
+
const normalizedMessage = message.trim();
|
|
2070
|
+
if (!normalizedMessage) {
|
|
2071
|
+
throw new Error('A commit message is required.');
|
|
2072
|
+
}
|
|
2073
|
+
const branchRef = runGitText(['symbolic-ref', '--quiet', 'HEAD'], cwd).trim();
|
|
2074
|
+
if (branchRef !== `refs/heads/${branch}`) {
|
|
2075
|
+
throw new Error(`The workspace is no longer on branch "${branch}". Refresh and try again.`);
|
|
2076
|
+
}
|
|
2077
|
+
const preActionHead = getGitHeadIdentity(cwd);
|
|
2078
|
+
assertSquashWorkspace(cwd, branch, project);
|
|
2079
|
+
const refreshedUpstreamCommitSha = await refreshSquashUpstream(branch, cwd);
|
|
2080
|
+
const summary = getSquashCommitSummary(cwd, branch, project);
|
|
2081
|
+
if (summary.totalCount < 2) {
|
|
2082
|
+
throw new Error('At least two unpushed commits are required to squash.');
|
|
2083
|
+
}
|
|
2084
|
+
const baseCommitSha = summary.baseCommit
|
|
2085
|
+
? resolveCommitSha(summary.baseCommit, cwd)
|
|
2086
|
+
: null;
|
|
2087
|
+
if (!baseCommitSha) {
|
|
2088
|
+
throw new Error('Unable to resolve the upstream or worktree base. Reconcile the branch before squashing commits.');
|
|
2089
|
+
}
|
|
2090
|
+
if (refreshedUpstreamCommitSha !== null && refreshedUpstreamCommitSha !== baseCommitSha) {
|
|
2091
|
+
throw new Error('The upstream branch changed while preparing the squash. Refresh and try again.');
|
|
2092
|
+
}
|
|
2093
|
+
try {
|
|
2094
|
+
runGitText(['merge-base', '--is-ancestor', baseCommitSha, preActionHead.headSha], cwd);
|
|
2095
|
+
}
|
|
2096
|
+
catch {
|
|
2097
|
+
throw new Error('The resolved base is not an ancestor of HEAD. Pull or rebase the branch before squashing commits.');
|
|
2098
|
+
}
|
|
2099
|
+
const preActionIndexTreeSha = getGitIndexTreeSha(cwd);
|
|
2100
|
+
if (preActionIndexTreeSha !== preActionHead.treeSha) {
|
|
2101
|
+
throw new Error('The index changed while preparing the squash. Refresh and try again.');
|
|
2102
|
+
}
|
|
2103
|
+
await assertSquashStateUnchanged({
|
|
2104
|
+
cwd,
|
|
2105
|
+
branch,
|
|
2106
|
+
project,
|
|
2107
|
+
branchRef,
|
|
2108
|
+
headSha: preActionHead.headSha,
|
|
2109
|
+
baseCommitSha,
|
|
2110
|
+
totalCount: summary.totalCount,
|
|
2111
|
+
});
|
|
2112
|
+
const commitOutput = await runGitTextAsync([
|
|
2113
|
+
'commit-tree',
|
|
2114
|
+
preActionHead.treeSha,
|
|
2115
|
+
'-p',
|
|
2116
|
+
baseCommitSha,
|
|
2117
|
+
'-m',
|
|
2118
|
+
normalizedMessage,
|
|
2119
|
+
], cwd);
|
|
2120
|
+
const commitSha = commitOutput.stdout.trim().split(/\s+/)[0] ?? '';
|
|
2121
|
+
if (!commitSha) {
|
|
2122
|
+
throw new Error('Git did not return the new squash commit.');
|
|
2123
|
+
}
|
|
2124
|
+
const commitMetadata = runGitText(['show', '-s', '--format=%H%n%P%n%T', commitSha], cwd).trim().split('\n');
|
|
2125
|
+
const observedCommitSha = commitMetadata[0]?.trim();
|
|
2126
|
+
const observedParents = commitMetadata[1]?.trim().split(/\s+/).filter(Boolean) ?? [];
|
|
2127
|
+
const observedTreeSha = commitMetadata[2]?.trim();
|
|
2128
|
+
if (observedCommitSha !== commitSha
|
|
2129
|
+
|| observedParents.length !== 1
|
|
2130
|
+
|| observedParents[0] !== baseCommitSha
|
|
2131
|
+
|| observedTreeSha !== preActionHead.treeSha) {
|
|
2132
|
+
throw new Error('The new squash commit did not match the expected tree and parent.');
|
|
2133
|
+
}
|
|
2134
|
+
try {
|
|
2135
|
+
await assertSquashStateUnchanged({
|
|
2136
|
+
cwd,
|
|
2137
|
+
branch,
|
|
2138
|
+
project,
|
|
2139
|
+
branchRef,
|
|
2140
|
+
headSha: preActionHead.headSha,
|
|
2141
|
+
baseCommitSha,
|
|
2142
|
+
totalCount: summary.totalCount,
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
catch {
|
|
2146
|
+
throw new Error('The branch, workspace, or upstream changed while squashing commits. Refresh and try again.');
|
|
2147
|
+
}
|
|
2148
|
+
try {
|
|
2149
|
+
await runGitTextAsync(['update-ref', branchRef, commitSha, preActionHead.headSha], cwd);
|
|
2150
|
+
}
|
|
2151
|
+
catch {
|
|
2152
|
+
throw new Error('The branch or HEAD changed while squashing commits. No branch ref was overwritten; refresh and try again.');
|
|
2153
|
+
}
|
|
2154
|
+
try {
|
|
2155
|
+
const finalHead = getGitHeadIdentity(cwd);
|
|
2156
|
+
const finalIndexTreeSha = getGitIndexTreeSha(cwd);
|
|
2157
|
+
const finalStatus = assertSquashWorkspace(cwd, branch, project);
|
|
2158
|
+
const finalUpstreamCommitSha = await refreshSquashUpstream(branch, cwd);
|
|
2159
|
+
const finalSummary = getSquashCommitSummary(cwd, branch, project);
|
|
2160
|
+
const finalBaseCommitSha = finalSummary.baseCommit
|
|
2161
|
+
? resolveCommitSha(finalSummary.baseCommit, cwd)
|
|
2162
|
+
: null;
|
|
2163
|
+
if (finalHead.headSha !== commitSha
|
|
2164
|
+
|| finalHead.treeSha !== preActionHead.treeSha
|
|
2165
|
+
|| finalIndexTreeSha !== preActionIndexTreeSha
|
|
2166
|
+
|| finalStatus.is_dirty
|
|
2167
|
+
|| finalSummary.totalCount !== 1
|
|
2168
|
+
|| finalBaseCommitSha !== baseCommitSha
|
|
2169
|
+
|| (finalUpstreamCommitSha !== null && finalUpstreamCommitSha !== baseCommitSha)) {
|
|
2170
|
+
throw new Error('The workspace or squash base changed while squashing commits.');
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
catch (error) {
|
|
2174
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2175
|
+
try {
|
|
2176
|
+
await runGitTextAsync(['update-ref', branchRef, preActionHead.headSha, commitSha], cwd);
|
|
2177
|
+
}
|
|
2178
|
+
catch {
|
|
2179
|
+
throw new Error(`${reason} The branch ref could not be rolled back safely; inspect the branch before retrying.`);
|
|
2180
|
+
}
|
|
2181
|
+
throw new Error(`${reason} The branch ref was rolled back safely; refresh the Changes view and try again.`);
|
|
2182
|
+
}
|
|
2183
|
+
return {
|
|
2184
|
+
commitSha,
|
|
2185
|
+
parentSha: baseCommitSha,
|
|
2186
|
+
treeSha: preActionHead.treeSha,
|
|
2187
|
+
};
|
|
2188
|
+
};
|
|
2189
|
+
export const squashUnpushedCommits = async (branch, project, message) => {
|
|
2190
|
+
const cwd = resolveWorktreePath(branch, project);
|
|
2191
|
+
return squashUnpushedCommitsForCwd(cwd, branch, project, message);
|
|
2192
|
+
};
|
|
1913
2193
|
export const undoLatestUnpushedCommitForCwd = async (cwd, branch, project = null) => {
|
|
1914
2194
|
const commits = listUnpushedCommitsForCwd(cwd, branch, project);
|
|
1915
2195
|
if (commits.length === 0) {
|