@straiffi/archon 1.8.7 → 1.8.8
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-D4Qh96zr.js → ActivityView--4X_m6mR.js} +1 -1
- package/dist/client/assets/{GitHubActionsDialog-Bg03OmVv.js → GitHubActionsDialog-DZ1lHM01.js} +1 -1
- package/dist/client/assets/{RepositoryGraphDialog-CS_hOqPw.js → RepositoryGraphDialog-B--YlQcV.js} +1 -1
- package/dist/client/assets/{VerificationDialog-BYH-Ek8n.js → VerificationDialog-BIWPtBg3.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-2lxabjSl.js → architectureDiagram-3BPJPVTR-CY5xTOZJ.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-C80HvJmb.js → blockDiagram-GPEHLZMM-DkMi7gMh.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BM3k_lKz.js → c4Diagram-AAUBKEIU-ZZxXBBU4.js} +1 -1
- package/dist/client/assets/channel-b2AN-U3x.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-BTk3lX07.js → chunk-2J33WTMH-C9PiKMs-.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DAxdBZ_4.js → chunk-3OPIFGDE-B81aUmvi.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-BYm9s62a.js → chunk-5ZQYHXKU-Bk2Owm90.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-iVUM8xuV.js → chunk-727SXJPM-Bx-79aJk.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DVM7riMJ.js → chunk-AQP2D5EJ-GQNXYOf0.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-_D1FzP9B.js → chunk-CSCIHK7Q-BV22j6vn.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-D3XouAHf.js → chunk-KSCS5N6A-C9LlcbWv.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-B7NYWgT4.js → chunk-L5ZTLDWV-D8PJqJnh.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-Dgkkbb6A.js → chunk-LZXEDZCA-DcbPPozR.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-CzO1oVCi.js → chunk-ND2GUHAM-NV-_2KqZ.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-re0VVZne.js → chunk-NZK2D7GU-CbqA26gb.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-BwsS59iy.js → chunk-O5CBEL6O-B0DMN44B.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-CLyh-xCA.js → chunk-WU5MYG2G-CYpcnwf9.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-C0exS5oM.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DkB9dQZ1.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-x71fYlqQ.js → dagre-BM42HDAG-B5HD9fyK.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-CMdwzuYj.js → diagram-2AECGRRQ-BCXvWFpM.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-B3l6I4qJ.js → diagram-5GNKFQAL-CSpWGfAO.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-BNhypLBW.js → diagram-KO2AKTUF-CFECGppT.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CmrhbiDt.js → diagram-LMA3HP47-Dpb99QbZ.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-CRobDMLl.js → diagram-OG6HWLK6-DJcTGnx0.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-CqO7K0TS.js → erDiagram-TEJ5UH35-jWArJxNU.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-CuLTJZU0.js → flowDiagram-I6XJVG4X-DDF3P1hY.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-Bm7v1eRG.js → ganttDiagram-6RSMTGT7-Corwr_18.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CNHZr-5i.js → gitGraphDiagram-PVQCEYII-WzlpszWn.js} +1 -1
- package/dist/client/assets/index-Dy-7h3lN.js +274 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-z27pUr2X.js → infoDiagram-5YYISTIA-C0VwTwNC.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CDocZ0jf.js → ishikawaDiagram-YF4QCWOH-m9l3k2qX.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-CgP31lsU.js → journeyDiagram-JHISSGLW-DMhT8HTW.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-DxTm1_fc.js → kanban-definition-UN3LZRKU-C2ZkcOi0.js} +1 -1
- package/dist/client/assets/{line-hrZ7m44_.js → line-gKkUdUWR.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-BThJAIK-.js → mermaid-parser.core-DdBfJWbP.js} +1 -1
- package/dist/client/assets/{mermaid.core-CfVyP_kK.js → mermaid.core-DXnhq-eu.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-CuISnLgW.js → mindmap-definition-RKZ34NQL-B0gX4aJy.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-DnWAdd44.js → pieDiagram-4H26LBE5-BHYdLmpx.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-Cb-RVl5A.js → quadrantDiagram-W4KKPZXB-C-H-yQwU.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-DrXg_gCS.js → requirementDiagram-4Y6WPE33-DY_pSCFy.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DzZjp_ZP.js → sankeyDiagram-5OEKKPKP-CC61HRGf.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-CQJ3PhQD.js → sequenceDiagram-3UESZ5HK-B8fXWNxw.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-B16j9pg1.js → stateDiagram-AJRCARHV-C_VYKsXW.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ctk1r914.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-bUG3mZQt.js → timeline-definition-PNZ67QCA-0fB4PIIX.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-DALCJcZD.js → vennDiagram-CIIHVFJN-CfpVF7Aa.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DPQd3l9A.js → wardleyDiagram-YWT4CUSO-CgdK4FcC.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-BIv1FqXG.js → xychartDiagram-2RQKCTM6-BEfatQYR.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/server/index.js +41 -36
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/git.js +302 -11
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/integrations/github.js +135 -65
- package/dist/server/lib/integrations/github.js.map +1 -1
- package/dist/server/lib/integrations/importedIssueImages.js +10 -15
- package/dist/server/lib/integrations/importedIssueImages.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/channel-HjENOCZ0.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DnhdaVrK.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C-BjeSGJ.js +0 -1
- package/dist/client/assets/index-LGNsdNYM.js +0 -274
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-vNiFfHwm.js +0 -1
package/dist/server/lib/git.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFile, execFileSync } from 'child_process';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
3
|
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, rmSync, statSync, symlinkSync } from 'fs';
|
|
4
|
-
import { cp as cpAsync, lstat as lstatAsync, mkdir as mkdirAsync, symlink as symlinkAsync } from 'fs/promises';
|
|
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';
|
|
7
7
|
import db from '../db.js';
|
|
@@ -11,6 +11,8 @@ export class UnpushedCommitDiffSelectionError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
const PROJECT_BRANCH_REMOTE_REFRESH_TTL_MS = 30_000;
|
|
13
13
|
const MAX_GIT_OUTPUT_BYTES = 512 * 1024 * 1024;
|
|
14
|
+
const GIT_READ_TIMEOUT_MS = 15_000;
|
|
15
|
+
const MAX_CONCURRENT_DIFF_PROCESSES = 8;
|
|
14
16
|
const lastProjectBranchRemoteRefreshAt = new Map();
|
|
15
17
|
const resolveCurrentBranchName = (cwd) => {
|
|
16
18
|
try {
|
|
@@ -85,12 +87,13 @@ const runGitDiffText = (args, cwd) => {
|
|
|
85
87
|
throw error;
|
|
86
88
|
}
|
|
87
89
|
};
|
|
88
|
-
const runGitTextAsync = (args, cwd) => new Promise((resolvePromise, reject) => {
|
|
90
|
+
const runGitTextAsync = (args, cwd, options = {}) => new Promise((resolvePromise, reject) => {
|
|
89
91
|
execFile('git', args, {
|
|
90
92
|
cwd,
|
|
91
93
|
encoding: 'utf8',
|
|
92
94
|
env: createWorkspaceProcessEnvironment(cwd),
|
|
93
95
|
maxBuffer: MAX_GIT_OUTPUT_BYTES,
|
|
96
|
+
timeout: options.timeoutMs,
|
|
94
97
|
}, (error, stdout, stderr) => {
|
|
95
98
|
if (error) {
|
|
96
99
|
const failed = error;
|
|
@@ -110,13 +113,26 @@ const runGitTextAsync = (args, cwd) => new Promise((resolvePromise, reject) => {
|
|
|
110
113
|
});
|
|
111
114
|
});
|
|
112
115
|
});
|
|
116
|
+
const mapWithConcurrency = async (inputs, concurrency, operation) => {
|
|
117
|
+
const results = new Array(inputs.length);
|
|
118
|
+
let nextIndex = 0;
|
|
119
|
+
const worker = async () => {
|
|
120
|
+
while (nextIndex < inputs.length) {
|
|
121
|
+
const index = nextIndex;
|
|
122
|
+
nextIndex += 1;
|
|
123
|
+
results[index] = await operation(inputs[index]);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, inputs.length) }, worker));
|
|
127
|
+
return results;
|
|
128
|
+
};
|
|
113
129
|
const isGitOutputLimitError = (error) => {
|
|
114
130
|
const failed = error;
|
|
115
131
|
return failed.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER' || failed.message.includes('maxBuffer');
|
|
116
132
|
};
|
|
117
|
-
const runGitDiffTextAsync = async (args, cwd) => {
|
|
133
|
+
const runGitDiffTextAsync = async (args, cwd, options = {}) => {
|
|
118
134
|
try {
|
|
119
|
-
return (await runGitTextAsync(args, cwd)).stdout;
|
|
135
|
+
return (await runGitTextAsync(args, cwd, options)).stdout;
|
|
120
136
|
}
|
|
121
137
|
catch (error) {
|
|
122
138
|
const failed = error;
|
|
@@ -869,6 +885,33 @@ const getWorkspaceSignature = (output, cwd, entries) => {
|
|
|
869
885
|
.join('\0');
|
|
870
886
|
return createTextSignature(`${output}\0${fileFingerprints}`);
|
|
871
887
|
};
|
|
888
|
+
const getWorkspaceSignatureAsync = async (output, cwd, entries) => {
|
|
889
|
+
const sortedEntries = [...entries]
|
|
890
|
+
.sort((left, right) => left.path.localeCompare(right.path) || (left.originalPath ?? '').localeCompare(right.originalPath ?? ''));
|
|
891
|
+
const fileFingerprints = (await Promise.all(sortedEntries.map(async (entry) => {
|
|
892
|
+
try {
|
|
893
|
+
const entryPath = resolve(cwd, entry.path);
|
|
894
|
+
const stat = await statAsync(entryPath);
|
|
895
|
+
const kind = stat.isDirectory() ? 'dir' : 'file';
|
|
896
|
+
return `${entry.path}:${kind}:${stat.size}:${Math.trunc(stat.mtimeMs)}`;
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
return `${entry.path}:missing`;
|
|
900
|
+
}
|
|
901
|
+
}))).map((fingerprint, index) => {
|
|
902
|
+
const entry = sortedEntries[index];
|
|
903
|
+
return [
|
|
904
|
+
entry.path,
|
|
905
|
+
entry.originalPath ?? '',
|
|
906
|
+
entry.status,
|
|
907
|
+
entry.staged ? '1' : '0',
|
|
908
|
+
entry.unstaged ? '1' : '0',
|
|
909
|
+
entry.conflicted ? '1' : '0',
|
|
910
|
+
fingerprint,
|
|
911
|
+
].join('\u0001');
|
|
912
|
+
}).join('\0');
|
|
913
|
+
return createTextSignature(`${output}\0${fileFingerprints}`);
|
|
914
|
+
};
|
|
872
915
|
const parseGitStatusOutput = (output) => {
|
|
873
916
|
const entries = output.split('\0').filter(Boolean);
|
|
874
917
|
const parsedEntries = [];
|
|
@@ -948,7 +991,7 @@ export const listGitSnapshotPaths = (cwd) => {
|
|
|
948
991
|
return [...new Set(paths.filter(Boolean))];
|
|
949
992
|
};
|
|
950
993
|
const listUntrackedFilesAsync = async (cwd) => {
|
|
951
|
-
return (await runGitTextAsync(['ls-files', '--others', '--exclude-standard'], cwd)).stdout
|
|
994
|
+
return (await runGitTextAsync(['ls-files', '--others', '--exclude-standard'], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS })).stdout
|
|
952
995
|
.split('\n')
|
|
953
996
|
.map(line => line.trim())
|
|
954
997
|
.filter(Boolean);
|
|
@@ -965,7 +1008,7 @@ const getUntrackedNumstatEntry = (path, cwd) => {
|
|
|
965
1008
|
};
|
|
966
1009
|
};
|
|
967
1010
|
const getUntrackedNumstatEntryAsync = async (path, cwd) => {
|
|
968
|
-
const output = await runGitDiffTextAsync(['diff', '--no-index', '--numstat', '--', '/dev/null', path], cwd);
|
|
1011
|
+
const output = await runGitDiffTextAsync(['diff', '--no-index', '--numstat', '--', '/dev/null', path], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
969
1012
|
const entry = parseNumstatOutput(output)[0];
|
|
970
1013
|
if (!entry) {
|
|
971
1014
|
return null;
|
|
@@ -979,7 +1022,7 @@ const getUntrackedUnifiedDiff = (path, cwd) => {
|
|
|
979
1022
|
return runGitDiffText(['diff', '--no-index', '-U3', '--', '/dev/null', path], cwd);
|
|
980
1023
|
};
|
|
981
1024
|
const getUntrackedUnifiedDiffAsync = (path, cwd) => {
|
|
982
|
-
return runGitDiffTextAsync(['diff', '--no-index', '-U3', '--', '/dev/null', path], cwd);
|
|
1025
|
+
return runGitDiffTextAsync(['diff', '--no-index', '-U3', '--', '/dev/null', path], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
983
1026
|
};
|
|
984
1027
|
const getSelectedDiffDataForCwd = (cwd, selectedPaths) => {
|
|
985
1028
|
const normalizedPaths = [...new Set(selectedPaths.map(path => path.trim()).filter(Boolean))];
|
|
@@ -1065,12 +1108,12 @@ const getDiffDataForCwd = (cwd) => {
|
|
|
1065
1108
|
};
|
|
1066
1109
|
const getDiffDataForCwdAsync = async (cwd) => {
|
|
1067
1110
|
try {
|
|
1068
|
-
const trackedNumstat = parseNumstatOutput((await runGitTextAsync(['diff', '--numstat', '--find-renames', '--no-ext-diff', 'HEAD'], cwd)).stdout);
|
|
1111
|
+
const trackedNumstat = parseNumstatOutput((await runGitTextAsync(['diff', '--numstat', '--find-renames', '--no-ext-diff', 'HEAD'], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS })).stdout);
|
|
1069
1112
|
const untrackedPaths = await listUntrackedFilesAsync(cwd);
|
|
1070
|
-
const untrackedNumstat = (await
|
|
1113
|
+
const untrackedNumstat = (await mapWithConcurrency(untrackedPaths, MAX_CONCURRENT_DIFF_PROCESSES, path => getUntrackedNumstatEntryAsync(path, cwd)))
|
|
1071
1114
|
.filter((entry) => entry !== null);
|
|
1072
|
-
const trackedDiff = (await runGitTextAsync(['diff', '-U3', '--find-renames', '--no-ext-diff', 'HEAD'], cwd)).stdout;
|
|
1073
|
-
const untrackedDiffs = await
|
|
1115
|
+
const trackedDiff = (await runGitTextAsync(['diff', '-U3', '--find-renames', '--no-ext-diff', 'HEAD'], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS })).stdout;
|
|
1116
|
+
const untrackedDiffs = await mapWithConcurrency(untrackedPaths, MAX_CONCURRENT_DIFF_PROCESSES, path => getUntrackedUnifiedDiffAsync(path, cwd));
|
|
1074
1117
|
return {
|
|
1075
1118
|
files: [...trackedNumstat, ...untrackedNumstat].map(entry => ({
|
|
1076
1119
|
path: entry.path,
|
|
@@ -1392,6 +1435,9 @@ export const getGitDiffFiles = (cwd) => {
|
|
|
1392
1435
|
export const getGitDiffData = (cwd) => {
|
|
1393
1436
|
return getDiffDataForCwd(cwd);
|
|
1394
1437
|
};
|
|
1438
|
+
export const getGitDiffDataAsync = (cwd) => {
|
|
1439
|
+
return getDiffDataForCwdAsync(cwd);
|
|
1440
|
+
};
|
|
1395
1441
|
export const getGitDiffFilesAsync = async (cwd) => {
|
|
1396
1442
|
return (await getDiffDataForCwdAsync(cwd)).files;
|
|
1397
1443
|
};
|
|
@@ -1580,6 +1626,209 @@ export const getGitStatus = (cwd, branch = null, project = null, options = {}) =
|
|
|
1580
1626
|
};
|
|
1581
1627
|
}
|
|
1582
1628
|
};
|
|
1629
|
+
const resolveBranchUpstreamRefAsync = async (branch, cwd) => {
|
|
1630
|
+
try {
|
|
1631
|
+
const output = await runGitTextAsync(['rev-parse', '--symbolic-full-name', `${branch}@{upstream}`], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
1632
|
+
return output.stdout.trim() || null;
|
|
1633
|
+
}
|
|
1634
|
+
catch {
|
|
1635
|
+
return null;
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
const getBranchSyncStateAsync = async (entries, cwd, recordedBase) => {
|
|
1639
|
+
const branchState = entries
|
|
1640
|
+
.filter(entry => entry.startsWith('# '))
|
|
1641
|
+
.reduce((current, entry) => parseBranchHeader(entry, current), {
|
|
1642
|
+
branch: null,
|
|
1643
|
+
ahead: 0,
|
|
1644
|
+
behind: 0,
|
|
1645
|
+
});
|
|
1646
|
+
if (!branchState.branch) {
|
|
1647
|
+
return { branch: null, ahead: 0, behind: 0, hasUpstream: false, canPush: false };
|
|
1648
|
+
}
|
|
1649
|
+
const upstreamRef = await resolveBranchUpstreamRefAsync(branchState.branch, cwd);
|
|
1650
|
+
let ahead = branchState.ahead;
|
|
1651
|
+
if (!upstreamRef && ahead === 0 && branchState.behind === 0) {
|
|
1652
|
+
const recordedBaseRef = recordedBase?.baseCommit ?? recordedBase?.baseBranch ?? null;
|
|
1653
|
+
const args = recordedBaseRef
|
|
1654
|
+
? ['rev-list', '--count', `${recordedBaseRef}..HEAD`]
|
|
1655
|
+
: ['rev-list', '--count', 'HEAD', '--not', '--remotes'];
|
|
1656
|
+
try {
|
|
1657
|
+
const output = await runGitTextAsync(args, cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
1658
|
+
const parsedAhead = Number.parseInt(output.stdout.trim() || '0', 10);
|
|
1659
|
+
ahead = Number.isFinite(parsedAhead) ? parsedAhead : 0;
|
|
1660
|
+
}
|
|
1661
|
+
catch {
|
|
1662
|
+
ahead = 0;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return {
|
|
1666
|
+
branch: branchState.branch,
|
|
1667
|
+
ahead,
|
|
1668
|
+
behind: branchState.behind,
|
|
1669
|
+
hasUpstream: upstreamRef !== null,
|
|
1670
|
+
canPush: upstreamRef !== null || ahead > 0,
|
|
1671
|
+
};
|
|
1672
|
+
};
|
|
1673
|
+
const getGitOperationStateAsync = async (cwd) => {
|
|
1674
|
+
try {
|
|
1675
|
+
const output = await runGitTextAsync(['rev-parse', '--git-dir'], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
1676
|
+
const gitDir = output.stdout.trim();
|
|
1677
|
+
if (!gitDir) {
|
|
1678
|
+
return 'idle';
|
|
1679
|
+
}
|
|
1680
|
+
const resolvedGitDir = resolve(cwd, gitDir);
|
|
1681
|
+
if (existsSync(join(resolvedGitDir, 'MERGE_HEAD'))) {
|
|
1682
|
+
return 'merge';
|
|
1683
|
+
}
|
|
1684
|
+
if (existsSync(join(resolvedGitDir, 'rebase-merge')) || existsSync(join(resolvedGitDir, 'rebase-apply'))) {
|
|
1685
|
+
return 'rebase';
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
catch {
|
|
1689
|
+
return 'idle';
|
|
1690
|
+
}
|
|
1691
|
+
return 'idle';
|
|
1692
|
+
};
|
|
1693
|
+
const resolveFallbackBaseAsync = async (branch, project) => {
|
|
1694
|
+
const repoPath = getRepoPath(project);
|
|
1695
|
+
try {
|
|
1696
|
+
const branchOutput = await runGitTextAsync(['branch', '--show-current'], repoPath, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
1697
|
+
const baseBranch = branchOutput.stdout.trim();
|
|
1698
|
+
if (!baseBranch || baseBranch === branch) {
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
const mergeBaseOutput = await runGitTextAsync(['merge-base', baseBranch, branch], repoPath, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
1702
|
+
return {
|
|
1703
|
+
baseBranch,
|
|
1704
|
+
baseCommit: mergeBaseOutput.stdout.trim() || null,
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
catch {
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
const refreshProjectBranchTrackingAsync = async (cwd, branch) => {
|
|
1712
|
+
try {
|
|
1713
|
+
const upstreamRef = await resolveBranchUpstreamRefAsync(branch, cwd);
|
|
1714
|
+
const fetchSpec = parseRemoteBranchRef(upstreamRef);
|
|
1715
|
+
const args = fetchSpec
|
|
1716
|
+
? ['fetch', '--quiet', fetchSpec.remote, fetchSpec.branch]
|
|
1717
|
+
: ['fetch', '--quiet', 'origin', branch];
|
|
1718
|
+
await runGitTextAsync(args, cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
1719
|
+
}
|
|
1720
|
+
catch {
|
|
1721
|
+
// Keep the last known tracking state when fetch is unavailable.
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
const getUnreadableGitStatus = () => ({
|
|
1725
|
+
branch: null,
|
|
1726
|
+
ahead: 0,
|
|
1727
|
+
behind: 0,
|
|
1728
|
+
has_upstream: false,
|
|
1729
|
+
operation_state: 'idle',
|
|
1730
|
+
can_abort: false,
|
|
1731
|
+
can_force_push_with_lease: false,
|
|
1732
|
+
base_branch: null,
|
|
1733
|
+
pull_disabled_reason: 'Unable to read git status for this worktree.',
|
|
1734
|
+
rebase_disabled_reason: 'Unable to read git status for this worktree.',
|
|
1735
|
+
workspace_signature: null,
|
|
1736
|
+
is_dirty: false,
|
|
1737
|
+
has_staged_changes: false,
|
|
1738
|
+
has_unstaged_changes: false,
|
|
1739
|
+
files: [],
|
|
1740
|
+
can_commit: false,
|
|
1741
|
+
can_push: false,
|
|
1742
|
+
blocked_reason: 'Unable to read git status for this worktree.',
|
|
1743
|
+
});
|
|
1744
|
+
export const getGitStatusAsync = async (cwd, branch = null, project = null, options = {}) => {
|
|
1745
|
+
const recordedBase = branch && project ? resolveRecordedBaseForCwd(cwd, branch, project) : null;
|
|
1746
|
+
try {
|
|
1747
|
+
if (branch && options.refreshRemote && shouldRefreshProjectBranchTracking(getProjectBranchRefreshCacheKey(cwd, branch), true)) {
|
|
1748
|
+
await refreshProjectBranchTrackingAsync(cwd, branch);
|
|
1749
|
+
}
|
|
1750
|
+
const output = (await runGitTextAsync(['status', '--porcelain=v2', '--branch', '--untracked-files=all', '-z'], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS })).stdout;
|
|
1751
|
+
const parsedOutput = parseGitStatusOutput(output);
|
|
1752
|
+
const branchState = await getBranchSyncStateAsync(parsedOutput.entries, cwd, recordedBase);
|
|
1753
|
+
const resolvedBranch = branchState.branch ?? branch;
|
|
1754
|
+
const fallbackBase = !recordedBase && resolvedBranch && project
|
|
1755
|
+
? await resolveFallbackBaseAsync(resolvedBranch, project)
|
|
1756
|
+
: null;
|
|
1757
|
+
const effectiveBase = recordedBase ?? fallbackBase;
|
|
1758
|
+
const includeFiles = options.includeFiles ?? true;
|
|
1759
|
+
const files = includeFiles
|
|
1760
|
+
? parsedOutput.parsedEntries.map(entry => ({
|
|
1761
|
+
path: entry.path,
|
|
1762
|
+
original_path: entry.originalPath,
|
|
1763
|
+
status: entry.status,
|
|
1764
|
+
staged: entry.staged,
|
|
1765
|
+
unstaged: entry.unstaged,
|
|
1766
|
+
conflicted: entry.conflicted,
|
|
1767
|
+
}))
|
|
1768
|
+
: [];
|
|
1769
|
+
const blockedReason = includeFiles ? getCommitBlockedReason(files) : null;
|
|
1770
|
+
const operationState = await getGitOperationStateAsync(cwd);
|
|
1771
|
+
const baseBranch = effectiveBase?.baseBranch && effectiveBase.baseBranch !== resolvedBranch
|
|
1772
|
+
? effectiveBase.baseBranch
|
|
1773
|
+
: null;
|
|
1774
|
+
const workspaceSignature = options.includeWorkspaceSignature === false
|
|
1775
|
+
? null
|
|
1776
|
+
: await getWorkspaceSignatureAsync(output, cwd, parsedOutput.parsedEntries);
|
|
1777
|
+
const status = {
|
|
1778
|
+
branch: resolvedBranch,
|
|
1779
|
+
ahead: branchState.ahead,
|
|
1780
|
+
behind: branchState.behind,
|
|
1781
|
+
has_upstream: branchState.hasUpstream,
|
|
1782
|
+
operation_state: operationState,
|
|
1783
|
+
can_abort: operationState !== 'idle',
|
|
1784
|
+
can_force_push_with_lease: branchState.hasUpstream && branchState.ahead > 0 && branchState.behind > 0 && operationState === 'idle',
|
|
1785
|
+
base_branch: baseBranch,
|
|
1786
|
+
pull_disabled_reason: null,
|
|
1787
|
+
rebase_disabled_reason: null,
|
|
1788
|
+
workspace_signature: workspaceSignature,
|
|
1789
|
+
is_dirty: parsedOutput.parsedEntries.length > 0,
|
|
1790
|
+
has_staged_changes: parsedOutput.parsedEntries.some(entry => entry.staged),
|
|
1791
|
+
has_unstaged_changes: parsedOutput.parsedEntries.some(entry => entry.unstaged || entry.status === 'untracked'),
|
|
1792
|
+
files,
|
|
1793
|
+
can_commit: includeFiles && files.length > 0 && blockedReason === null,
|
|
1794
|
+
can_push: branchState.canPush,
|
|
1795
|
+
blocked_reason: blockedReason,
|
|
1796
|
+
};
|
|
1797
|
+
return {
|
|
1798
|
+
...status,
|
|
1799
|
+
pull_disabled_reason: !status.branch
|
|
1800
|
+
? 'Select a named branch before pulling.'
|
|
1801
|
+
: operationState !== 'idle' || status.is_dirty || !status.has_upstream
|
|
1802
|
+
? getPullDisabledReasonWithoutGit(status)
|
|
1803
|
+
: null,
|
|
1804
|
+
rebase_disabled_reason: getRebaseDisabledReason(status),
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
catch {
|
|
1808
|
+
return getUnreadableGitStatus();
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1811
|
+
const getPullDisabledReasonWithoutGit = (status) => {
|
|
1812
|
+
if (!status.branch) {
|
|
1813
|
+
return 'Select a named branch before pulling.';
|
|
1814
|
+
}
|
|
1815
|
+
if (status.operation_state === 'merge') {
|
|
1816
|
+
return 'Abort the current merge before pulling again.';
|
|
1817
|
+
}
|
|
1818
|
+
if (status.operation_state === 'rebase') {
|
|
1819
|
+
return 'Abort the current rebase before pulling again.';
|
|
1820
|
+
}
|
|
1821
|
+
if (status.files.some(file => file.conflicted)) {
|
|
1822
|
+
return 'Resolve conflicted files before pulling from the UI.';
|
|
1823
|
+
}
|
|
1824
|
+
if (status.is_dirty) {
|
|
1825
|
+
return 'Commit or stash changes before pulling from the UI.';
|
|
1826
|
+
}
|
|
1827
|
+
if (!status.has_upstream) {
|
|
1828
|
+
return `Branch "${status.branch}" has no configured upstream.`;
|
|
1829
|
+
}
|
|
1830
|
+
return null;
|
|
1831
|
+
};
|
|
1583
1832
|
export const getWorktreeGitStatus = (branch, project = null) => {
|
|
1584
1833
|
const cwd = resolveWorktreePath(branch, project);
|
|
1585
1834
|
return getGitStatus(cwd, branch, project);
|
|
@@ -1981,6 +2230,48 @@ export const getUnpushedCommitSummaryForCwd = (cwd, branch, project = null, opti
|
|
|
1981
2230
|
};
|
|
1982
2231
|
}
|
|
1983
2232
|
};
|
|
2233
|
+
export const getUnpushedCommitSummaryForCwdAsync = async (cwd, branch, project = null) => {
|
|
2234
|
+
const recordedBase = project ? resolveRecordedBaseForCwd(cwd, branch, project) : null;
|
|
2235
|
+
const upstreamRef = await resolveBranchUpstreamRefAsync(branch, cwd);
|
|
2236
|
+
const baseRef = upstreamRef ?? recordedBase?.baseBranch ?? recordedBase?.baseCommit ?? null;
|
|
2237
|
+
let baseCommit = null;
|
|
2238
|
+
if (baseRef) {
|
|
2239
|
+
try {
|
|
2240
|
+
const output = await runGitTextAsync(['rev-parse', '--verify', `${baseRef}^{commit}`], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
2241
|
+
baseCommit = output.stdout.trim() || null;
|
|
2242
|
+
}
|
|
2243
|
+
catch {
|
|
2244
|
+
baseCommit = recordedBase?.baseCommit?.trim() || null;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
if (!baseCommit && project && resolve(cwd) === getRepoPath(project)) {
|
|
2248
|
+
try {
|
|
2249
|
+
const output = await runGitTextAsync(['rev-parse', 'HEAD'], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
|
|
2250
|
+
baseCommit = output.stdout.trim() || null;
|
|
2251
|
+
}
|
|
2252
|
+
catch {
|
|
2253
|
+
baseCommit = null;
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
const rangeArgs = baseCommit ? [`${baseCommit}..HEAD`] : ['HEAD', '--not', '--remotes'];
|
|
2257
|
+
const [countOutput, logOutput] = await Promise.all([
|
|
2258
|
+
runGitTextAsync(['rev-list', '--count', ...rangeArgs], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS }),
|
|
2259
|
+
runGitTextAsync([
|
|
2260
|
+
'log',
|
|
2261
|
+
'--format=%H%x00%h%x00%s%x00%cI%x00',
|
|
2262
|
+
'--no-show-signature',
|
|
2263
|
+
'-n',
|
|
2264
|
+
'20',
|
|
2265
|
+
...rangeArgs,
|
|
2266
|
+
], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS }),
|
|
2267
|
+
]);
|
|
2268
|
+
const totalCount = Number.parseInt(countOutput.stdout.trim() || '0', 10);
|
|
2269
|
+
return {
|
|
2270
|
+
commits: parseUnpushedCommitsOutput(logOutput.stdout),
|
|
2271
|
+
totalCount: Number.isFinite(totalCount) ? totalCount : 0,
|
|
2272
|
+
baseCommit,
|
|
2273
|
+
};
|
|
2274
|
+
};
|
|
1984
2275
|
export const listUnpushedCommitsForCwd = (cwd, branch, project = null, options = {}) => {
|
|
1985
2276
|
return getUnpushedCommitSummaryForCwd(cwd, branch, project, options).commits;
|
|
1986
2277
|
};
|