@straiffi/archon 1.11.2 → 1.11.3
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-DJq1H7eJ.js → ActivityView-BjvhZ4Bh.js} +1 -1
- package/dist/client/assets/{GitHubActionsDialog-uhkY79FP.js → GitHubActionsDialog-jhteSKM0.js} +1 -1
- package/dist/client/assets/{VerificationDialog-B1fn5Xb_.js → VerificationDialog-DXtxeTKq.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BIJiODuH.js → architectureDiagram-3BPJPVTR-DJecrWnU.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-zF74yReS.js → blockDiagram-GPEHLZMM-LK02iFZS.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-Bhedpri8.js → c4Diagram-AAUBKEIU-m96725AW.js} +1 -1
- package/dist/client/assets/channel-LI2rscLR.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-Cii8qtH1.js → chunk-2J33WTMH-CwmuCvx8.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-Hsh5MsIU.js → chunk-3OPIFGDE-SW4m-rIq.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-B2H33geG.js → chunk-5ZQYHXKU-DFfSwh_S.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-o3GIhJ7i.js → chunk-727SXJPM-D2f6Vwlb.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DZGEJOYd.js → chunk-AQP2D5EJ-_jtM3Tdt.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-D7-p3_zX.js → chunk-CSCIHK7Q-DZ1s84Fd.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-Dg344Qr2.js → chunk-KSCS5N6A-D448HkSV.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DTLPM19a.js → chunk-L5ZTLDWV-Ba5YxsnW.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-13gIpIRU.js → chunk-LZXEDZCA-B0hk_4PK.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-Bhl3l4MJ.js → chunk-ND2GUHAM-BpZJJZiB.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-BDZ0gHw4.js → chunk-NZK2D7GU-LuaOcOtL.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-C1xjgaqI.js → chunk-O5CBEL6O-Bzlew6XY.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-DXrekGgA.js → chunk-WU5MYG2G-BYEmBNm1.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BYYHQoLT.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BNvuOkCY.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-ymk8yb4x.js → dagre-BM42HDAG-3iYykaMs.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BFKit5Xx.js → diagram-2AECGRRQ-6H-oqgXo.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-BLuEM5D6.js → diagram-5GNKFQAL-DeGtRWDY.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-_wS_dDU4.js → diagram-KO2AKTUF-nSErzJJr.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DXIrjyQ-.js → diagram-LMA3HP47-D5RxIE7c.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-DquYa7lV.js → diagram-OG6HWLK6-uAqkbcsO.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-MKI9p3C-.js → erDiagram-TEJ5UH35-04qHL_j_.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-DMZ4-2rY.js → flowDiagram-I6XJVG4X-CBDUXM_F.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-GgzwvVRo.js → ganttDiagram-6RSMTGT7-BL0PIBG6.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Br_tAiVM.js → gitGraphDiagram-PVQCEYII-Ddmxyu19.js} +1 -1
- package/dist/client/assets/index-Bidb-jfj.js +287 -0
- package/dist/client/assets/index-BrYBOiOf.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-DbbDZQMC.js → infoDiagram-5YYISTIA-C3eZ7CLn.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CqZ2rJxu.js → ishikawaDiagram-YF4QCWOH-CbNplKIS.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-B29_JOsJ.js → journeyDiagram-JHISSGLW-DsYeBcDj.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-CESA-TYi.js → kanban-definition-UN3LZRKU-D7hvpkBq.js} +1 -1
- package/dist/client/assets/{line-DdPRuRKH.js → line-TFhte-nm.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-vFFzvyX4.js → mermaid-parser.core-C-gXz2-T.js} +1 -1
- package/dist/client/assets/{mermaid.core-DkyZ6FR3.js → mermaid.core-C-rJqYoR.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-ChH-rvOD.js → mindmap-definition-RKZ34NQL-BSM0bQDy.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-BjrFBvU1.js → pieDiagram-4H26LBE5-B6uILvFr.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-2Si58cQa.js → quadrantDiagram-W4KKPZXB-DqXc5qbT.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-C7VJ8QDP.js → requirementDiagram-4Y6WPE33-Ci9VQX_M.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Boka1d1I.js → sankeyDiagram-5OEKKPKP-L2MZIY9w.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-A2g_mJL3.js → sequenceDiagram-3UESZ5HK-UvFhbq-E.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-DZN-P8xp.js → stateDiagram-AJRCARHV-DqosHMjy.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-D1r9uj3H.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-C_IatSHm.js → timeline-definition-PNZ67QCA-T_HpTr2n.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CICUDBE3.js → vennDiagram-CIIHVFJN-CSfFKA-z.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BStcFc77.js → wardleyDiagram-YWT4CUSO-BxCQBsV1.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-Cby4OnB9.js → xychartDiagram-2RQKCTM6-Dx2zLNeh.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/index.js +164 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +76 -16
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentPreview.js +41 -0
- package/dist/server/lib/agentPreview.js.map +1 -0
- package/dist/server/lib/agentRuns.js +15 -0
- package/dist/server/lib/agentRuns.js.map +1 -1
- package/dist/server/lib/buildSequences.js +31 -0
- package/dist/server/lib/buildSequences.js.map +1 -1
- package/dist/server/lib/bundleBuildRecovery.js +110 -0
- package/dist/server/lib/bundleBuildRecovery.js.map +1 -0
- package/dist/server/lib/codexAgentProvider.js +44 -3
- package/dist/server/lib/codexAgentProvider.js.map +1 -1
- package/dist/server/lib/codexAppServer.js +143 -6
- package/dist/server/lib/codexAppServer.js.map +1 -1
- package/dist/server/lib/git.js +479 -2
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/preview.js +37 -24
- package/dist/server/lib/preview.js.map +1 -1
- package/dist/server/lib/promptSkills.js +6 -4
- package/dist/server/lib/promptSkills.js.map +1 -1
- package/dist/server/lib/run.js +40 -5
- package/dist/server/lib/run.js.map +1 -1
- package/dist/server/lib/runPreviewCandidate.js +6 -0
- package/dist/server/lib/runPreviewCandidate.js.map +1 -0
- package/dist/server/lib/tickets.js +4 -1
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/workers/build.js +3 -3
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +15 -6
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +5 -1
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +40 -6
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/review.js +2 -2
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specSession.js +2 -2
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-DZI461ra.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DWu6cWxw.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CAI_KWMC.js +0 -1
- package/dist/client/assets/index-4RQBXPuO.css +0 -2
- package/dist/client/assets/index-X5cdbVG5.js +0 -279
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Cqrxk_FO.js +0 -1
package/dist/server/lib/git.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile, execFileSync } from 'child_process';
|
|
2
2
|
import { createHash, randomUUID } from 'crypto';
|
|
3
|
-
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, rmSync, statSync, symlinkSync } from 'fs';
|
|
3
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, readdirSync, rmSync, statSync, symlinkSync } from 'fs';
|
|
4
4
|
import { cp as cpAsync, lstat as lstatAsync, mkdir as mkdirAsync, stat as statAsync, symlink as symlinkAsync } from 'fs/promises';
|
|
5
5
|
import { tmpdir } from 'os';
|
|
6
6
|
import { dirname, isAbsolute, join, relative, resolve } from 'path';
|
|
@@ -15,6 +15,8 @@ const MAX_GIT_OUTPUT_BYTES = 512 * 1024 * 1024;
|
|
|
15
15
|
const GIT_READ_TIMEOUT_MS = 15_000;
|
|
16
16
|
const GIT_REMOTE_KILL_GRACE_MS = 500;
|
|
17
17
|
const MAX_CONCURRENT_DIFF_PROCESSES = 8;
|
|
18
|
+
const MAX_REBASE_MARKER_SCAN_BYTES = 1024 * 1024;
|
|
19
|
+
const MAX_REBASE_FINGERPRINT_BYTES = 1024 * 1024;
|
|
18
20
|
const lastProjectBranchRemoteRefreshAt = new Map();
|
|
19
21
|
const projectBranchRemoteRefreshes = new Map();
|
|
20
22
|
const resolveCurrentBranchName = (cwd) => {
|
|
@@ -33,6 +35,10 @@ const toText = (value) => {
|
|
|
33
35
|
}
|
|
34
36
|
return value ? value.toString('utf8') : '';
|
|
35
37
|
};
|
|
38
|
+
const getGitErrorMessage = (error) => {
|
|
39
|
+
const failed = error;
|
|
40
|
+
return toText(failed.stderr).trim() || toText(failed.stdout).trim() || (error instanceof Error ? error.message : String(error));
|
|
41
|
+
};
|
|
36
42
|
const createTextSignature = (value) => {
|
|
37
43
|
let hash = 5381;
|
|
38
44
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -416,6 +422,272 @@ const getGitOperationState = (cwd) => {
|
|
|
416
422
|
}
|
|
417
423
|
return 'idle';
|
|
418
424
|
};
|
|
425
|
+
const readGitPath = (cwd, name) => {
|
|
426
|
+
const path = runGitText(['rev-parse', '--git-path', name], cwd).trim();
|
|
427
|
+
return path ? resolve(cwd, path) : null;
|
|
428
|
+
};
|
|
429
|
+
const readOptionalFile = (path) => {
|
|
430
|
+
if (!path || !existsSync(path)) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
return readFileSync(path, 'utf8').trim() || null;
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const parsePositiveInteger = (value) => {
|
|
441
|
+
const parsed = Number.parseInt(value ?? '', 10);
|
|
442
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
443
|
+
};
|
|
444
|
+
const normalizeRebaseBaseBranch = (value) => {
|
|
445
|
+
if (!value) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
return value
|
|
449
|
+
.replace(/^refs\/remotes\/origin\//, '')
|
|
450
|
+
.replace(/^refs\/heads\//, '')
|
|
451
|
+
.replace(/^origin\//, '');
|
|
452
|
+
};
|
|
453
|
+
const getActiveRebaseDirectory = (cwd) => {
|
|
454
|
+
const mergeDir = readGitPath(cwd, 'rebase-merge');
|
|
455
|
+
if (mergeDir && existsSync(mergeDir)) {
|
|
456
|
+
return mergeDir;
|
|
457
|
+
}
|
|
458
|
+
const applyDir = readGitPath(cwd, 'rebase-apply');
|
|
459
|
+
return applyDir && existsSync(applyDir) ? applyDir : null;
|
|
460
|
+
};
|
|
461
|
+
export const getActiveRebaseBranch = (cwd) => {
|
|
462
|
+
if (getGitOperationState(cwd) !== 'rebase') {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
const rebaseDir = getActiveRebaseDirectory(cwd);
|
|
466
|
+
return readOptionalFile(rebaseDir ? join(rebaseDir, 'head-name') : null)?.replace(/^refs\/heads\//, '') ?? null;
|
|
467
|
+
};
|
|
468
|
+
const getRemainingRebaseOwnedPaths = (cwd) => {
|
|
469
|
+
const rebaseDir = getActiveRebaseDirectory(cwd);
|
|
470
|
+
const todo = readOptionalFile(rebaseDir ? join(rebaseDir, 'git-rebase-todo') : null);
|
|
471
|
+
const paths = new Set();
|
|
472
|
+
if (!todo) {
|
|
473
|
+
return paths;
|
|
474
|
+
}
|
|
475
|
+
const commitCommands = new Set(['pick', 'p', 'reword', 'r', 'edit', 'e', 'squash', 's', 'fixup', 'f']);
|
|
476
|
+
for (const line of todo.split('\n')) {
|
|
477
|
+
const [command, commit] = line.trim().split(/\s+/, 3);
|
|
478
|
+
if (!command || !commit || !commitCommands.has(command)) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const output = runGitText(['diff-tree', '--root', '--no-commit-id', '--name-only', '-r', '-z', commit], cwd);
|
|
483
|
+
for (const path of output.split('\0').filter(Boolean)) {
|
|
484
|
+
paths.add(path);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// Git will remain authoritative and reject a conflicting later application.
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return paths;
|
|
492
|
+
};
|
|
493
|
+
const parseRebaseConflictStages = (output) => {
|
|
494
|
+
const stagesByPath = new Map();
|
|
495
|
+
for (const entry of output.split('\0').filter(Boolean)) {
|
|
496
|
+
const separatorIndex = entry.indexOf('\t');
|
|
497
|
+
if (separatorIndex === -1) {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const path = entry.slice(separatorIndex + 1);
|
|
501
|
+
const stage = Number(entry.slice(0, separatorIndex).trim().split(/\s+/).at(-1));
|
|
502
|
+
if (!Number.isInteger(stage)) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const stages = stagesByPath.get(path) ?? new Set();
|
|
506
|
+
stages.add(stage);
|
|
507
|
+
stagesByPath.set(path, stages);
|
|
508
|
+
}
|
|
509
|
+
return stagesByPath;
|
|
510
|
+
};
|
|
511
|
+
const hasCustomConflictMarkerSize = (cwd, path) => {
|
|
512
|
+
try {
|
|
513
|
+
const value = runGitText(['check-attr', '-z', 'conflict-marker-size', '--', path], cwd).split('\0')[2];
|
|
514
|
+
return Boolean(value && value !== 'unspecified' && value !== 'unset' && value !== '7');
|
|
515
|
+
}
|
|
516
|
+
catch {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
const getRebaseResolution = (cwd, path, conflicted, conflictStages) => {
|
|
521
|
+
if (!conflicted) {
|
|
522
|
+
return { resolution: 'ready', resolution_reason: null };
|
|
523
|
+
}
|
|
524
|
+
if (conflictStages && conflictStages.has(2) !== conflictStages.has(3)) {
|
|
525
|
+
return { resolution: 'acknowledgement_required', resolution_reason: 'delete_modify' };
|
|
526
|
+
}
|
|
527
|
+
if (hasCustomConflictMarkerSize(cwd, path)) {
|
|
528
|
+
return { resolution: 'acknowledgement_required', resolution_reason: 'marker_override' };
|
|
529
|
+
}
|
|
530
|
+
const absolutePath = resolve(cwd, path);
|
|
531
|
+
if (!existsSync(absolutePath)) {
|
|
532
|
+
return { resolution: 'acknowledgement_required', resolution_reason: 'deleted' };
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
const fileStatus = lstatSync(absolutePath);
|
|
536
|
+
if (fileStatus.isSymbolicLink()) {
|
|
537
|
+
return { resolution: 'acknowledgement_required', resolution_reason: 'symlink' };
|
|
538
|
+
}
|
|
539
|
+
if (!fileStatus.isFile()) {
|
|
540
|
+
return { resolution: 'acknowledgement_required', resolution_reason: 'unreadable' };
|
|
541
|
+
}
|
|
542
|
+
if (fileStatus.size > MAX_REBASE_MARKER_SCAN_BYTES) {
|
|
543
|
+
return { resolution: 'acknowledgement_required', resolution_reason: 'oversized' };
|
|
544
|
+
}
|
|
545
|
+
const content = readFileSync(absolutePath);
|
|
546
|
+
if (content.includes(0)) {
|
|
547
|
+
return { resolution: 'acknowledgement_required', resolution_reason: 'binary' };
|
|
548
|
+
}
|
|
549
|
+
const text = content.toString('utf8');
|
|
550
|
+
const hasConflictMarkers = /^(<<<<<<<(?: |$)|=======$|>>>>>>>[ \t])/m.test(text);
|
|
551
|
+
return hasConflictMarkers
|
|
552
|
+
? { resolution: 'acknowledgement_required', resolution_reason: 'conflict_markers' }
|
|
553
|
+
: { resolution: 'ready', resolution_reason: null };
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return { resolution: 'acknowledgement_required', resolution_reason: 'unreadable' };
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
const getWorkingTreeFingerprint = (cwd, path) => {
|
|
560
|
+
const absolutePath = resolve(cwd, path);
|
|
561
|
+
try {
|
|
562
|
+
const status = lstatSync(absolutePath);
|
|
563
|
+
if (status.isSymbolicLink()) {
|
|
564
|
+
return `symlink:${createHash('sha256').update(readlinkSync(absolutePath)).digest('hex')}`;
|
|
565
|
+
}
|
|
566
|
+
if (!status.isFile()) {
|
|
567
|
+
return `other:${status.mode}`;
|
|
568
|
+
}
|
|
569
|
+
if (status.size > MAX_REBASE_FINGERPRINT_BYTES) {
|
|
570
|
+
return `oversized:${status.mode}:${status.size}:${status.mtimeMs}:${status.ctimeMs}`;
|
|
571
|
+
}
|
|
572
|
+
return `file:${status.mode}:${status.size}:${createHash('sha256').update(readFileSync(absolutePath)).digest('hex')}`;
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
576
|
+
return 'missing';
|
|
577
|
+
}
|
|
578
|
+
return 'unreadable';
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
const getWorkingTreeFingerprints = (cwd, paths) => paths
|
|
582
|
+
.map(path => [path, getWorkingTreeFingerprint(cwd, path)]);
|
|
583
|
+
const getRebaseStepCoordinates = (cwd) => {
|
|
584
|
+
const rebaseDir = getActiveRebaseDirectory(cwd);
|
|
585
|
+
return {
|
|
586
|
+
stoppedCommitSha: (() => {
|
|
587
|
+
try {
|
|
588
|
+
return runGitText(['rev-parse', '--verify', 'REBASE_HEAD^{commit}'], cwd).trim();
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
})(),
|
|
594
|
+
currentStep: parsePositiveInteger(readOptionalFile(rebaseDir ? join(rebaseDir, 'msgnum') : null))
|
|
595
|
+
?? parsePositiveInteger(readOptionalFile(rebaseDir ? join(rebaseDir, 'next') : null)),
|
|
596
|
+
totalSteps: parsePositiveInteger(readOptionalFile(rebaseDir ? join(rebaseDir, 'end') : null))
|
|
597
|
+
?? parsePositiveInteger(readOptionalFile(rebaseDir ? join(rebaseDir, 'last') : null)),
|
|
598
|
+
};
|
|
599
|
+
};
|
|
600
|
+
const getRebaseContinuationFingerprint = (cwd, status, selectedPaths) => {
|
|
601
|
+
if (status.operation_state !== 'rebase' || !status.rebase_step) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
const selectedPathStates = [...selectedPaths].sort().map(path => {
|
|
605
|
+
const pathStatus = status.rebase_step?.paths.find(candidate => candidate.path === path);
|
|
606
|
+
return pathStatus
|
|
607
|
+
? [path, pathStatus.index_state, pathStatus.resolution, pathStatus.resolution_reason]
|
|
608
|
+
: [path, 'missing'];
|
|
609
|
+
});
|
|
610
|
+
return createHash('sha256').update(JSON.stringify({
|
|
611
|
+
...getRebaseStepCoordinates(cwd),
|
|
612
|
+
stagedIndexEntries: runGitText(['diff', '--cached', '--raw', '--abbrev=40', '-z'], cwd),
|
|
613
|
+
unmergedEntries: runGitText(['ls-files', '--unmerged', '-z'], cwd),
|
|
614
|
+
resolveUndoEntries: runGitText(['ls-files', '--resolve-undo', '-z'], cwd),
|
|
615
|
+
selectedPathStates,
|
|
616
|
+
})).digest('hex');
|
|
617
|
+
};
|
|
618
|
+
const getRebaseStepStatus = (cwd, fallbackBaseBranch) => {
|
|
619
|
+
if (getGitOperationState(cwd) !== 'rebase') {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
const rebaseDir = getActiveRebaseDirectory(cwd);
|
|
623
|
+
const stoppedCommitSha = (() => {
|
|
624
|
+
try {
|
|
625
|
+
return runGitText(['rev-parse', '--verify', 'REBASE_HEAD^{commit}'], cwd).trim() || null;
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
})();
|
|
631
|
+
const stoppedCommitSubject = stoppedCommitSha
|
|
632
|
+
? runGitText(['show', '-s', '--format=%s', stoppedCommitSha], cwd).trim() || null
|
|
633
|
+
: null;
|
|
634
|
+
const ownedPaths = new Set(stoppedCommitSha
|
|
635
|
+
? runGitText(['diff-tree', '--root', '--no-commit-id', '--name-only', '-r', '-z', stoppedCommitSha], cwd).split('\0').filter(Boolean)
|
|
636
|
+
: []);
|
|
637
|
+
const stagedPaths = new Set(runGitText(['diff', '--cached', '--name-only', '-z'], cwd).split('\0').filter(Boolean));
|
|
638
|
+
const unmergedOutput = runGitText(['ls-files', '--unmerged', '-z'], cwd);
|
|
639
|
+
const unmergedStagesByPath = parseRebaseConflictStages(unmergedOutput);
|
|
640
|
+
const unmergedPaths = new Set(unmergedStagesByPath.keys());
|
|
641
|
+
const resolveUndoOutput = runGitText(['ls-files', '--resolve-undo', '-z'], cwd);
|
|
642
|
+
const resolveUndoStagesByPath = parseRebaseConflictStages(resolveUndoOutput);
|
|
643
|
+
const formerlyConflictedPaths = new Set(resolveUndoStagesByPath.keys());
|
|
644
|
+
const workspacePaths = parseGitStatusOutput(runGitText(['status', '--porcelain=v2', '--untracked-files=all', '-z'], cwd)).parsedEntries.map(entry => entry.path);
|
|
645
|
+
const allPaths = [...new Set([...stagedPaths, ...unmergedPaths, ...formerlyConflictedPaths, ...workspacePaths])].sort();
|
|
646
|
+
const paths = allPaths.map(path => {
|
|
647
|
+
const conflicted = unmergedPaths.has(path);
|
|
648
|
+
const staged = stagedPaths.has(path);
|
|
649
|
+
const rebaseOwned = ownedPaths.has(path);
|
|
650
|
+
return {
|
|
651
|
+
path,
|
|
652
|
+
rebase_owned: rebaseOwned,
|
|
653
|
+
index_state: conflicted ? 'unmerged' : staged ? 'staged' : 'unstaged',
|
|
654
|
+
selection_required: conflicted || formerlyConflictedPaths.has(path) || (rebaseOwned && staged),
|
|
655
|
+
...getRebaseResolution(cwd, path, conflicted || formerlyConflictedPaths.has(path), unmergedStagesByPath.get(path) ?? resolveUndoStagesByPath.get(path)),
|
|
656
|
+
};
|
|
657
|
+
});
|
|
658
|
+
const currentStep = parsePositiveInteger(readOptionalFile(rebaseDir ? join(rebaseDir, 'msgnum') : null))
|
|
659
|
+
?? parsePositiveInteger(readOptionalFile(rebaseDir ? join(rebaseDir, 'next') : null));
|
|
660
|
+
const totalSteps = parsePositiveInteger(readOptionalFile(rebaseDir ? join(rebaseDir, 'end') : null))
|
|
661
|
+
?? parsePositiveInteger(readOptionalFile(rebaseDir ? join(rebaseDir, 'last') : null));
|
|
662
|
+
const baseBranch = normalizeRebaseBaseBranch(readOptionalFile(rebaseDir ? join(rebaseDir, 'onto_name') : null)) ?? fallbackBaseBranch;
|
|
663
|
+
const stagedIndexEntries = runGitText(['diff', '--cached', '--raw', '-z'], cwd);
|
|
664
|
+
const headSha = runGitText(['rev-parse', '--verify', 'HEAD^{commit}'], cwd).trim();
|
|
665
|
+
const workingTreeFingerprints = getWorkingTreeFingerprints(cwd, allPaths);
|
|
666
|
+
const signature = createHash('sha256')
|
|
667
|
+
.update(JSON.stringify({ headSha, stoppedCommitSha, currentStep, totalSteps, unmergedOutput, resolveUndoOutput, stagedIndexEntries, workingTreeFingerprints }))
|
|
668
|
+
.digest('hex');
|
|
669
|
+
return {
|
|
670
|
+
step_signature: signature,
|
|
671
|
+
current_step: currentStep,
|
|
672
|
+
total_steps: totalSteps,
|
|
673
|
+
stopped_commit_sha: stoppedCommitSha,
|
|
674
|
+
stopped_commit_subject: stoppedCommitSubject,
|
|
675
|
+
base_branch: baseBranch,
|
|
676
|
+
paths,
|
|
677
|
+
};
|
|
678
|
+
};
|
|
679
|
+
const MAX_REBASE_STAGED_DIFF_BYTES = 512 * 1024;
|
|
680
|
+
export const getRebaseStagedDiff = (cwd) => {
|
|
681
|
+
const diff = runGitText(['diff', '--cached', '--no-ext-diff', '--no-textconv'], cwd);
|
|
682
|
+
const bytes = Buffer.byteLength(diff);
|
|
683
|
+
if (bytes <= MAX_REBASE_STAGED_DIFF_BYTES) {
|
|
684
|
+
return { diff, truncated: false };
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
diff: Buffer.from(diff).subarray(0, MAX_REBASE_STAGED_DIFF_BYTES).toString('utf8'),
|
|
688
|
+
truncated: true,
|
|
689
|
+
};
|
|
690
|
+
};
|
|
419
691
|
const hasActiveGitOperation = (cwd) => {
|
|
420
692
|
if (getGitOperationState(cwd) !== 'idle') {
|
|
421
693
|
return true;
|
|
@@ -1495,6 +1767,206 @@ export const rebaseGitBranchOntoBase = async (cwd, branch = null, project = null
|
|
|
1495
1767
|
});
|
|
1496
1768
|
}
|
|
1497
1769
|
};
|
|
1770
|
+
const finalizeRebaseWorktreeBase = (cwd, branch, project, baseBranch) => {
|
|
1771
|
+
if (!project || resolve(cwd) === getRepoPath(project) || !baseBranch) {
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
const nextBaseCommit = resolveCommitBaseFromBaseRef(resolveNamedBranchRef(baseBranch, cwd), cwd);
|
|
1775
|
+
saveWorktreeBase(branch, project, { baseBranch, baseCommit: nextBaseCommit });
|
|
1776
|
+
};
|
|
1777
|
+
const getRebaseActionStatus = (cwd, branch, project) => getGitStatus(cwd, branch, project);
|
|
1778
|
+
const rejectRebaseAction = (outcome, error, status) => ({ outcome, error, status });
|
|
1779
|
+
const getLiteralPathspec = (path) => `:(literal)${path}`;
|
|
1780
|
+
export const continueGitRebase = async (cwd, targetBranch, project, options) => {
|
|
1781
|
+
const status = getRebaseActionStatus(cwd, targetBranch ?? options.branch, project);
|
|
1782
|
+
if (status.operation_state !== 'rebase' || !status.rebase_step) {
|
|
1783
|
+
return rejectRebaseAction('stale_step', 'There is no active rebase step. Refresh and try again.', status);
|
|
1784
|
+
}
|
|
1785
|
+
if (!targetBranch || !options.branch || options.branch !== targetBranch) {
|
|
1786
|
+
return rejectRebaseAction('stale_step', 'The active target branch changed. Refresh and try again.', status);
|
|
1787
|
+
}
|
|
1788
|
+
if (options.expectedStepSignature !== status.rebase_step.step_signature) {
|
|
1789
|
+
return rejectRebaseAction('stale_step', 'The rebase step or index changed. Review the refreshed status and try again.', status);
|
|
1790
|
+
}
|
|
1791
|
+
const pathsByName = new Map(status.rebase_step.paths.map(path => [path.path, path]));
|
|
1792
|
+
const selectedPaths = [...new Set(options.selectedPaths)];
|
|
1793
|
+
const acknowledgedPaths = new Set(options.acknowledgedPaths);
|
|
1794
|
+
const unknownPath = selectedPaths.find(path => !pathsByName.has(path));
|
|
1795
|
+
if (unknownPath) {
|
|
1796
|
+
return rejectRebaseAction('rejected', `Path "${unknownPath}" is not part of the current rebase status.`, status);
|
|
1797
|
+
}
|
|
1798
|
+
const unknownAcknowledgement = [...acknowledgedPaths].find(path => !pathsByName.has(path));
|
|
1799
|
+
if (unknownAcknowledgement) {
|
|
1800
|
+
return rejectRebaseAction('rejected', `Acknowledged path "${unknownAcknowledgement}" is not part of the current rebase status.`, status);
|
|
1801
|
+
}
|
|
1802
|
+
const missingMandatoryPath = status.rebase_step.paths.find(path => path.selection_required && !selectedPaths.includes(path.path));
|
|
1803
|
+
if (missingMandatoryPath) {
|
|
1804
|
+
return rejectRebaseAction('rejected', `Path "${missingMandatoryPath.path}" is required for this rebase step.`, status);
|
|
1805
|
+
}
|
|
1806
|
+
const missingAcknowledgement = status.rebase_step.paths.find(path => (selectedPaths.includes(path.path)
|
|
1807
|
+
&& path.resolution === 'acknowledgement_required'
|
|
1808
|
+
&& !acknowledgedPaths.has(path.path)));
|
|
1809
|
+
if (missingAcknowledgement) {
|
|
1810
|
+
return rejectRebaseAction('rejected', `Acknowledge the resolution state for "${missingAcknowledgement.path}" before continuing.`, status);
|
|
1811
|
+
}
|
|
1812
|
+
const expectedSelectedFingerprints = new Map(getWorkingTreeFingerprints(cwd, selectedPaths));
|
|
1813
|
+
const excludedOptionalPaths = status.rebase_step.paths
|
|
1814
|
+
.filter(path => !path.selection_required && !selectedPaths.includes(path.path))
|
|
1815
|
+
.map(path => path.path);
|
|
1816
|
+
const excludedStagedPaths = status.rebase_step.paths
|
|
1817
|
+
.filter(path => excludedOptionalPaths.includes(path.path) && path.index_state === 'staged')
|
|
1818
|
+
.map(path => path.path);
|
|
1819
|
+
try {
|
|
1820
|
+
for (const path of excludedStagedPaths) {
|
|
1821
|
+
await runGitTextAsync(['restore', '--staged', '--', getLiteralPathspec(path)], cwd);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
catch (error) {
|
|
1825
|
+
const failedPath = excludedStagedPaths.find(path => {
|
|
1826
|
+
try {
|
|
1827
|
+
return runGitText(['diff', '--cached', '--name-only', '-z', '--', getLiteralPathspec(path)], cwd).includes(path);
|
|
1828
|
+
}
|
|
1829
|
+
catch {
|
|
1830
|
+
return true;
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
return rejectRebaseAction('rejected', failedPath
|
|
1834
|
+
? `Unable to safely remove "${failedPath}" from the index while preserving its working-tree content.`
|
|
1835
|
+
: getGitErrorMessage(error), getRebaseActionStatus(cwd, targetBranch, project));
|
|
1836
|
+
}
|
|
1837
|
+
const changedSelectedPath = getWorkingTreeFingerprints(cwd, selectedPaths)
|
|
1838
|
+
.find(([path, fingerprint]) => expectedSelectedFingerprints.get(path) !== fingerprint)?.[0];
|
|
1839
|
+
if (changedSelectedPath) {
|
|
1840
|
+
return rejectRebaseAction('stale_step', `Selected path "${changedSelectedPath}" changed before it could be staged. Review the refreshed status and try again.`, getRebaseActionStatus(cwd, targetBranch, project));
|
|
1841
|
+
}
|
|
1842
|
+
const pathsToStage = status.rebase_step.paths
|
|
1843
|
+
.filter(path => selectedPaths.includes(path.path) && path.index_state !== 'staged')
|
|
1844
|
+
.map(path => getLiteralPathspec(path.path));
|
|
1845
|
+
if (pathsToStage.length > 0) {
|
|
1846
|
+
try {
|
|
1847
|
+
await runGitTextAsync(['add', '--', ...pathsToStage], cwd);
|
|
1848
|
+
}
|
|
1849
|
+
catch (error) {
|
|
1850
|
+
return rejectRebaseAction('rejected', getGitErrorMessage(error) || 'Unable to stage the selected rebase paths.', getRebaseActionStatus(cwd, targetBranch, project));
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
const refreshed = getRebaseActionStatus(cwd, targetBranch, project);
|
|
1854
|
+
if (refreshed.operation_state !== 'rebase' || !refreshed.rebase_step) {
|
|
1855
|
+
return rejectRebaseAction('stale_step', 'The rebase ended while its index was being prepared. Refresh and try again.', refreshed);
|
|
1856
|
+
}
|
|
1857
|
+
const refreshedStagedPaths = new Set(refreshed.rebase_step.paths.filter(path => path.index_state === 'staged').map(path => path.path));
|
|
1858
|
+
const refreshedFormerlyConflictedPaths = new Set(runGitText(['ls-files', '--resolve-undo', '-z'], cwd)
|
|
1859
|
+
.split('\0')
|
|
1860
|
+
.filter(Boolean)
|
|
1861
|
+
.map(entry => entry.slice(entry.indexOf('\t') + 1)));
|
|
1862
|
+
const stillStagedExcludedPath = excludedOptionalPaths.find(path => refreshedStagedPaths.has(path));
|
|
1863
|
+
if (stillStagedExcludedPath) {
|
|
1864
|
+
return rejectRebaseAction('rejected', `Excluded path "${stillStagedExcludedPath}" is still staged; the rebase was not continued.`, refreshed);
|
|
1865
|
+
}
|
|
1866
|
+
const expectedStagedPaths = new Set(selectedPaths.filter(path => refreshedStagedPaths.has(path)));
|
|
1867
|
+
const unexpectedStagedPath = [...refreshedStagedPaths].find(path => !expectedStagedPaths.has(path));
|
|
1868
|
+
const missingSelectedPath = selectedPaths.find(path => (!refreshedStagedPaths.has(path) && !refreshedFormerlyConflictedPaths.has(path)));
|
|
1869
|
+
if (unexpectedStagedPath || missingSelectedPath) {
|
|
1870
|
+
const changedPath = unexpectedStagedPath ?? missingSelectedPath;
|
|
1871
|
+
return rejectRebaseAction('stale_step', `The staged path set changed at "${changedPath}". Review the refreshed status and try again.`, refreshed);
|
|
1872
|
+
}
|
|
1873
|
+
if (refreshedStagedPaths.size === 0) {
|
|
1874
|
+
return { outcome: 'empty_step', status: refreshed };
|
|
1875
|
+
}
|
|
1876
|
+
const expectedContinuationFingerprint = getRebaseContinuationFingerprint(cwd, refreshed, selectedPaths);
|
|
1877
|
+
if (!expectedContinuationFingerprint) {
|
|
1878
|
+
return rejectRebaseAction('stale_step', 'The rebase ended while its index was being verified. Refresh and try again.', refreshed);
|
|
1879
|
+
}
|
|
1880
|
+
const baseBranch = status.rebase_step.base_branch;
|
|
1881
|
+
const trackedExcludedPaths = excludedOptionalPaths.filter(path => {
|
|
1882
|
+
try {
|
|
1883
|
+
return runGitText(['ls-files', '--error-unmatch', '--', getLiteralPathspec(path)], cwd).trim() !== '';
|
|
1884
|
+
}
|
|
1885
|
+
catch {
|
|
1886
|
+
return false;
|
|
1887
|
+
}
|
|
1888
|
+
});
|
|
1889
|
+
const remainingRebaseOwnedPaths = getRemainingRebaseOwnedPaths(cwd);
|
|
1890
|
+
const futureOwnedExcludedPath = excludedOptionalPaths.find(path => remainingRebaseOwnedPaths.has(path));
|
|
1891
|
+
if (futureOwnedExcludedPath) {
|
|
1892
|
+
return rejectRebaseAction('rejected', `Excluded working-tree changes in "${futureOwnedExcludedPath}" overlap a later rebase step. Handle that path before continuing.`, refreshed);
|
|
1893
|
+
}
|
|
1894
|
+
let continueError = null;
|
|
1895
|
+
try {
|
|
1896
|
+
if (trackedExcludedPaths.length > 0) {
|
|
1897
|
+
await runGitTextAsync(['update-index', '--assume-unchanged', '--', ...trackedExcludedPaths], cwd);
|
|
1898
|
+
}
|
|
1899
|
+
const finalStatus = getRebaseActionStatus(cwd, targetBranch, project);
|
|
1900
|
+
const finalStagedPaths = new Set(finalStatus.rebase_step?.paths
|
|
1901
|
+
.filter(path => path.index_state === 'staged')
|
|
1902
|
+
.map(path => path.path) ?? []);
|
|
1903
|
+
const finalUnexpectedPath = [...finalStagedPaths].find(path => !expectedStagedPaths.has(path))
|
|
1904
|
+
?? [...expectedStagedPaths].find(path => !finalStagedPaths.has(path));
|
|
1905
|
+
const finalContinuationFingerprint = getRebaseContinuationFingerprint(cwd, finalStatus, selectedPaths);
|
|
1906
|
+
const continuationChanged = finalContinuationFingerprint !== expectedContinuationFingerprint;
|
|
1907
|
+
if (finalStatus.operation_state !== 'rebase' || !finalStatus.rebase_step || finalUnexpectedPath || continuationChanged) {
|
|
1908
|
+
return rejectRebaseAction('stale_step', finalUnexpectedPath
|
|
1909
|
+
? `The staged path set changed at "${finalUnexpectedPath}" immediately before continue. Review the refreshed status and try again.`
|
|
1910
|
+
: continuationChanged
|
|
1911
|
+
? 'The staged content, resolution state, or rebase step changed immediately before continue. Review the refreshed status and try again.'
|
|
1912
|
+
: 'The rebase ended while its staged paths were being verified. Refresh and try again.', finalStatus);
|
|
1913
|
+
}
|
|
1914
|
+
await runGitTextAsync(['rebase', '--continue'], cwd, {
|
|
1915
|
+
env: { GIT_EDITOR: 'true', GIT_SEQUENCE_EDITOR: 'true' },
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
catch (error) {
|
|
1919
|
+
continueError = error;
|
|
1920
|
+
}
|
|
1921
|
+
finally {
|
|
1922
|
+
if (trackedExcludedPaths.length > 0) {
|
|
1923
|
+
try {
|
|
1924
|
+
await runGitTextAsync(['update-index', '--no-assume-unchanged', '--', ...trackedExcludedPaths], cwd);
|
|
1925
|
+
}
|
|
1926
|
+
catch {
|
|
1927
|
+
// Status below remains authoritative if Git removed a path while advancing the rebase.
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
const nextStatus = getRebaseActionStatus(cwd, targetBranch, project);
|
|
1932
|
+
if (continueError) {
|
|
1933
|
+
if (nextStatus.operation_state === 'rebase' && nextStatus.rebase_step?.step_signature !== refreshed.rebase_step.step_signature) {
|
|
1934
|
+
return { outcome: 'paused', status: nextStatus };
|
|
1935
|
+
}
|
|
1936
|
+
return rejectRebaseAction('rejected', getGitErrorMessage(continueError) || 'Git could not continue the current rebase step.', nextStatus);
|
|
1937
|
+
}
|
|
1938
|
+
if (nextStatus.operation_state === 'rebase') {
|
|
1939
|
+
return { outcome: 'paused', status: nextStatus };
|
|
1940
|
+
}
|
|
1941
|
+
finalizeRebaseWorktreeBase(cwd, targetBranch, project, baseBranch);
|
|
1942
|
+
return { outcome: 'completed', status: getRebaseActionStatus(cwd, targetBranch, project) };
|
|
1943
|
+
};
|
|
1944
|
+
export const skipGitRebaseStep = async (cwd, targetBranch, project, expectedStepSignature, branch) => {
|
|
1945
|
+
const status = getRebaseActionStatus(cwd, targetBranch ?? branch, project);
|
|
1946
|
+
if (status.operation_state !== 'rebase' || !status.rebase_step || !targetBranch || branch !== targetBranch) {
|
|
1947
|
+
return rejectRebaseAction('stale_step', 'The active rebase target changed. Refresh and try again.', status);
|
|
1948
|
+
}
|
|
1949
|
+
if (expectedStepSignature !== status.rebase_step.step_signature) {
|
|
1950
|
+
return rejectRebaseAction('stale_step', 'The rebase step or index changed. Review the refreshed status and try again.', status);
|
|
1951
|
+
}
|
|
1952
|
+
const baseBranch = status.rebase_step.base_branch;
|
|
1953
|
+
try {
|
|
1954
|
+
await runGitTextAsync(['rebase', '--skip'], cwd, { env: { GIT_EDITOR: 'true', GIT_SEQUENCE_EDITOR: 'true' } });
|
|
1955
|
+
}
|
|
1956
|
+
catch (error) {
|
|
1957
|
+
const nextStatus = getRebaseActionStatus(cwd, targetBranch, project);
|
|
1958
|
+
if (nextStatus.operation_state === 'rebase' && nextStatus.rebase_step?.step_signature !== status.rebase_step.step_signature) {
|
|
1959
|
+
return { outcome: 'paused', status: nextStatus };
|
|
1960
|
+
}
|
|
1961
|
+
return rejectRebaseAction('rejected', getGitErrorMessage(error) || 'Git could not skip the current rebase step.', nextStatus);
|
|
1962
|
+
}
|
|
1963
|
+
const nextStatus = getRebaseActionStatus(cwd, targetBranch, project);
|
|
1964
|
+
if (nextStatus.operation_state === 'rebase') {
|
|
1965
|
+
return { outcome: 'paused', status: nextStatus };
|
|
1966
|
+
}
|
|
1967
|
+
finalizeRebaseWorktreeBase(cwd, targetBranch, project, baseBranch);
|
|
1968
|
+
return { outcome: 'completed', status: getRebaseActionStatus(cwd, targetBranch, project) };
|
|
1969
|
+
};
|
|
1498
1970
|
export const abortGitOperation = async (cwd) => {
|
|
1499
1971
|
const operationState = getGitOperationState(cwd);
|
|
1500
1972
|
if (operationState === 'merge') {
|
|
@@ -1646,14 +2118,17 @@ const getRebaseDisabledReason = (status, baseBranch = status.base_branch) => {
|
|
|
1646
2118
|
const applyGitActionState = (status, options) => {
|
|
1647
2119
|
const operationState = getGitOperationState(options.cwd);
|
|
1648
2120
|
const baseBranch = resolveTargetBaseBranch(options.branch, options.project, options.recordedBase ?? null);
|
|
2121
|
+
const rebaseStep = operationState === 'rebase' ? getRebaseStepStatus(options.cwd, baseBranch) : null;
|
|
1649
2122
|
const nextStatus = {
|
|
1650
2123
|
...status,
|
|
2124
|
+
branch: status.branch ?? options.branch,
|
|
1651
2125
|
operation_state: operationState,
|
|
1652
2126
|
can_abort: operationState !== 'idle',
|
|
1653
2127
|
can_force_push_with_lease: status.has_upstream && status.ahead > 0 && status.behind > 0 && operationState === 'idle',
|
|
1654
2128
|
base_branch: baseBranch,
|
|
1655
2129
|
pull_disabled_reason: null,
|
|
1656
2130
|
rebase_disabled_reason: null,
|
|
2131
|
+
...(rebaseStep ? { rebase_step: rebaseStep, base_branch: rebaseStep.base_branch } : {}),
|
|
1657
2132
|
};
|
|
1658
2133
|
return {
|
|
1659
2134
|
...nextStatus,
|
|
@@ -1868,6 +2343,7 @@ export const getGitStatusAsync = async (cwd, branch = null, project = null, opti
|
|
|
1868
2343
|
const baseBranch = effectiveBase?.baseBranch && effectiveBase.baseBranch !== resolvedBranch
|
|
1869
2344
|
? effectiveBase.baseBranch
|
|
1870
2345
|
: null;
|
|
2346
|
+
const rebaseStep = operationState === 'rebase' ? getRebaseStepStatus(cwd, baseBranch) : null;
|
|
1871
2347
|
const workspaceSignature = options.includeWorkspaceSignature === false
|
|
1872
2348
|
? null
|
|
1873
2349
|
: await getWorkspaceSignatureAsync(output, cwd, parsedOutput.parsedEntries);
|
|
@@ -1879,9 +2355,10 @@ export const getGitStatusAsync = async (cwd, branch = null, project = null, opti
|
|
|
1879
2355
|
operation_state: operationState,
|
|
1880
2356
|
can_abort: operationState !== 'idle',
|
|
1881
2357
|
can_force_push_with_lease: branchState.hasUpstream && branchState.ahead > 0 && branchState.behind > 0 && operationState === 'idle',
|
|
1882
|
-
base_branch: baseBranch,
|
|
2358
|
+
base_branch: rebaseStep?.base_branch ?? baseBranch,
|
|
1883
2359
|
pull_disabled_reason: null,
|
|
1884
2360
|
rebase_disabled_reason: null,
|
|
2361
|
+
...(rebaseStep ? { rebase_step: rebaseStep } : {}),
|
|
1885
2362
|
workspace_signature: workspaceSignature,
|
|
1886
2363
|
is_dirty: parsedOutput.parsedEntries.length > 0,
|
|
1887
2364
|
has_staged_changes: parsedOutput.parsedEntries.some(entry => entry.staged),
|