dsh-gitnexus-plus 0.0.0-stage → 0.1.1

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.
Files changed (78) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +483 -2
  3. package/cordis.patch.yml +18 -0
  4. package/lib/bin/gitnexus-coverage.d.ts +14 -0
  5. package/lib/bin/gitnexus-coverage.d.ts.map +1 -0
  6. package/lib/bin/gitnexus-coverage.js +171 -0
  7. package/lib/bin/gitnexus-coverage.js.map +1 -0
  8. package/lib/client.js +1642 -0
  9. package/lib/gitnexus/analyze.d.ts +148 -0
  10. package/lib/gitnexus/analyze.d.ts.map +1 -0
  11. package/lib/gitnexus/analyze.js +283 -0
  12. package/lib/gitnexus/analyze.js.map +1 -0
  13. package/lib/gitnexus/commands.d.ts +96 -0
  14. package/lib/gitnexus/commands.d.ts.map +1 -0
  15. package/lib/gitnexus/commands.js +132 -0
  16. package/lib/gitnexus/commands.js.map +1 -0
  17. package/lib/gitnexus/coverage.d.ts +340 -0
  18. package/lib/gitnexus/coverage.d.ts.map +1 -0
  19. package/lib/gitnexus/coverage.js +552 -0
  20. package/lib/gitnexus/coverage.js.map +1 -0
  21. package/lib/gitnexus/format.d.ts +16 -0
  22. package/lib/gitnexus/format.d.ts.map +1 -0
  23. package/lib/gitnexus/format.js +178 -0
  24. package/lib/gitnexus/format.js.map +1 -0
  25. package/lib/gitnexus/git-activity.d.ts +136 -0
  26. package/lib/gitnexus/git-activity.d.ts.map +1 -0
  27. package/lib/gitnexus/git-activity.js +345 -0
  28. package/lib/gitnexus/git-activity.js.map +1 -0
  29. package/lib/gitnexus/git.d.ts +70 -0
  30. package/lib/gitnexus/git.d.ts.map +1 -0
  31. package/lib/gitnexus/git.js +121 -0
  32. package/lib/gitnexus/git.js.map +1 -0
  33. package/lib/gitnexus/index-meta.d.ts +150 -0
  34. package/lib/gitnexus/index-meta.d.ts.map +1 -0
  35. package/lib/gitnexus/index-meta.js +156 -0
  36. package/lib/gitnexus/index-meta.js.map +1 -0
  37. package/lib/gitnexus/install.d.ts +97 -0
  38. package/lib/gitnexus/install.d.ts.map +1 -0
  39. package/lib/gitnexus/install.js +228 -0
  40. package/lib/gitnexus/install.js.map +1 -0
  41. package/lib/gitnexus/languages.d.ts +73 -0
  42. package/lib/gitnexus/languages.d.ts.map +1 -0
  43. package/lib/gitnexus/languages.js +217 -0
  44. package/lib/gitnexus/languages.js.map +1 -0
  45. package/lib/gitnexus/root.d.ts +68 -0
  46. package/lib/gitnexus/root.d.ts.map +1 -0
  47. package/lib/gitnexus/root.js +162 -0
  48. package/lib/gitnexus/root.js.map +1 -0
  49. package/lib/gitnexus/scan.d.ts +67 -0
  50. package/lib/gitnexus/scan.d.ts.map +1 -0
  51. package/lib/gitnexus/scan.js +247 -0
  52. package/lib/gitnexus/scan.js.map +1 -0
  53. package/lib/gitnexus/toolchain.d.ts +136 -0
  54. package/lib/gitnexus/toolchain.d.ts.map +1 -0
  55. package/lib/gitnexus/toolchain.js +251 -0
  56. package/lib/gitnexus/toolchain.js.map +1 -0
  57. package/lib/index.d.ts +79 -0
  58. package/lib/index.d.ts.map +1 -0
  59. package/lib/index.js +211 -0
  60. package/lib/index.js.map +1 -0
  61. package/lib/routes/analyze.d.ts +35 -0
  62. package/lib/routes/analyze.d.ts.map +1 -0
  63. package/lib/routes/analyze.js +71 -0
  64. package/lib/routes/analyze.js.map +1 -0
  65. package/lib/routes/coverage.d.ts +44 -0
  66. package/lib/routes/coverage.d.ts.map +1 -0
  67. package/lib/routes/coverage.js +68 -0
  68. package/lib/routes/coverage.js.map +1 -0
  69. package/lib/routes/guard.d.ts +92 -0
  70. package/lib/routes/guard.d.ts.map +1 -0
  71. package/lib/routes/guard.js +187 -0
  72. package/lib/routes/guard.js.map +1 -0
  73. package/lib/routes/install.d.ts +27 -0
  74. package/lib/routes/install.d.ts.map +1 -0
  75. package/lib/routes/install.js +46 -0
  76. package/lib/routes/install.js.map +1 -0
  77. package/package.json +87 -4
  78. package/scripts/link-runtime.mjs +146 -0
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Git activity, correlated with the index.
3
+ *
4
+ * WHAT THIS ADDS THAT PLAIN `git log` DOES NOT
5
+ * -------------------------------------------
6
+ * GitNexus is built on git, and its manifest records a content hash for every file
7
+ * it indexed. Crossing recent git activity with that set answers a question neither
8
+ * tool answers alone:
9
+ *
10
+ * of the files people are actively changing right now,
11
+ * how many does the knowledge graph even see?
12
+ *
13
+ * A file that is edited weekly but absent from the index contributes nothing to
14
+ * `impact`, `context` or `query` — and nothing in GitNexus's own UI says so. That
15
+ * is the `indexed` flag on every row here.
16
+ *
17
+ * BOUNDS ARE PART OF THE CONTRACT
18
+ * -------------------------------
19
+ * `git log --numstat` on a large repository is unbounded work, so every dimension
20
+ * is capped and the result says when a cap was hit (`window.truncated`). A silently
21
+ * truncated activity list reads as "this is all the activity there was", which is
22
+ * the one thing it must not mean.
23
+ */
24
+ import { execFile } from 'node:child_process';
25
+ import { promisify } from 'node:util';
26
+ import { normalizePathKey } from './index-meta.js';
27
+ const run = promisify(execFile);
28
+ /** Generous but finite: the log is informational, and it must not outlast the scan. */
29
+ const TIMEOUT_MS = 20_000;
30
+ /** Record separator (RS) opens each commit; unit separator (US) separates its fields. */
31
+ const RS = '\u001e';
32
+ const US = '\u001f';
33
+ const DEFAULTS = Object.freeze({
34
+ sinceDays: 90,
35
+ maxCommits: 300,
36
+ maxFiles: 200,
37
+ maxAuthors: 20,
38
+ maxBranches: 20,
39
+ });
40
+ async function git(repoPath, args, maxBuffer = 32 * 1024 * 1024) {
41
+ try {
42
+ const { stdout } = await run('git', args, {
43
+ cwd: repoPath,
44
+ timeout: TIMEOUT_MS,
45
+ windowsHide: true,
46
+ maxBuffer,
47
+ });
48
+ return stdout;
49
+ }
50
+ catch (error) {
51
+ // `maxBuffer` overflow still leaves usable prefix output on some platforms, but
52
+ // treating it as failure is honest: a partial parse would understate activity.
53
+ return null;
54
+ }
55
+ }
56
+ /**
57
+ * Whether an author is a machine account.
58
+ *
59
+ * Worth separating because a bot's commit count and its file count disagree in a
60
+ * way a human's never does — measured on a real repository, `dependabot[bot]` was
61
+ * the second-busiest author with 80 commits against just 12 files. Left in the same
62
+ * list, it makes "who is active here" unreadable.
63
+ */
64
+ function looksLikeBot(name, email) {
65
+ return (/\[bot\]$/i.test(name) ||
66
+ /(^|[-_.])bot@/i.test(email) ||
67
+ /^(dependabot|renovate|github-actions|greenkeeper)/i.test(name));
68
+ }
69
+ /**
70
+ * Bucket commit stamps into a time histogram.
71
+ *
72
+ * Daily buckets while the window is short enough to read, weekly once it is not:
73
+ * 90 daily columns in a sidebar is a smear, not a chart. The bucket width is
74
+ * returned so the axis can be labelled with the real unit instead of implying one.
75
+ *
76
+ * Empty buckets are materialised rather than skipped — a gap in a bar chart must
77
+ * read as "no commits that day", and inferring that from irregular spacing is
78
+ * exactly the misreading the chart exists to prevent.
79
+ */
80
+ export function buildHistogram(stamps, sinceDays) {
81
+ if (stamps.length === 0)
82
+ return { bucketDays: 1, buckets: [] };
83
+ const bucketDays = sinceDays > 60 ? 7 : 1;
84
+ const sorted = [...stamps].sort((a, b) => (a.date < b.date ? -1 : 1));
85
+ const first = Date.parse(sorted[0].date);
86
+ const last = Date.parse(sorted[sorted.length - 1].date);
87
+ if (!Number.isFinite(first) || !Number.isFinite(last))
88
+ return { bucketDays, buckets: [] };
89
+ const dayMs = 86_400_000;
90
+ const byIndex = new Map();
91
+ for (const stamp of sorted) {
92
+ const at = Date.parse(stamp.date);
93
+ if (!Number.isFinite(at))
94
+ continue;
95
+ const index = Math.floor((at - first) / dayMs / bucketDays);
96
+ let bucket = byIndex.get(index);
97
+ if (bucket === undefined) {
98
+ const startMs = first + index * bucketDays * dayMs;
99
+ bucket = {
100
+ label: new Date(startMs).toISOString().slice(0, 10),
101
+ commits: 0,
102
+ insertions: 0,
103
+ deletions: 0,
104
+ };
105
+ byIndex.set(index, bucket);
106
+ }
107
+ bucket.commits += 1;
108
+ bucket.insertions += stamp.insertions;
109
+ bucket.deletions += stamp.deletions;
110
+ }
111
+ const count = Math.max(...byIndex.keys()) + 1;
112
+ const buckets = [];
113
+ for (let i = 0; i < count; i += 1) {
114
+ const existing = byIndex.get(i);
115
+ if (existing !== undefined) {
116
+ buckets.push(existing);
117
+ continue;
118
+ }
119
+ const startMs = first + i * bucketDays * dayMs;
120
+ buckets.push({
121
+ label: new Date(startMs).toISOString().slice(0, 10),
122
+ commits: 0,
123
+ insertions: 0,
124
+ deletions: 0,
125
+ });
126
+ }
127
+ return { bucketDays, buckets };
128
+ }
129
+ /**
130
+ * Read recent commits, authors, changed files and branches.
131
+ *
132
+ * Returns `null` when git cannot answer at all.
133
+ */
134
+ export async function readGitActivity(repoPath, options = {}) {
135
+ // The window is expressed in days rather than as a git date expression so the
136
+ // result can be *labelled* without parsing one back out. A first revision passed
137
+ // the raw spec straight into the UI, which rendered "近 90 days ago 内".
138
+ const sinceDays = options.sinceDays ?? DEFAULTS.sinceDays;
139
+ const since = `${sinceDays} days ago`;
140
+ const maxCommits = options.maxCommits ?? DEFAULTS.maxCommits;
141
+ const maxFiles = options.maxFiles ?? DEFAULTS.maxFiles;
142
+ const maxAuthors = options.maxAuthors ?? DEFAULTS.maxAuthors;
143
+ const maxBranches = options.maxBranches ?? DEFAULTS.maxBranches;
144
+ const indexed = new Set([...(options.indexedPaths ?? [])].map((p) => normalizePathKey(p)));
145
+ const scannable = new Set([...(options.scannablePaths ?? [])].map((p) => normalizePathKey(p)));
146
+ const classified = indexed.size > 0 || scannable.size > 0;
147
+ /**
148
+ * Three-way, because "not in the index" is two different facts. A `.gitignore`
149
+ * or a `package-lock.json` is absent by design; a source file a fresh scan would
150
+ * pick up is absent because the index is behind. Reporting them as one number
151
+ * would make every repository look badly stale.
152
+ */
153
+ const classify = (filePath) => {
154
+ const key = normalizePathKey(filePath);
155
+ if (indexed.has(key))
156
+ return 'indexed';
157
+ if (scannable.size > 0 && scannable.has(key))
158
+ return 'missing';
159
+ return 'excluded';
160
+ };
161
+ const log = await git(repoPath, [
162
+ 'log',
163
+ `--since=${since}`,
164
+ `--max-count=${maxCommits}`,
165
+ '--numstat',
166
+ '--no-renames',
167
+ `--format=${RS}%H${US}%an${US}%ae${US}%aI${US}%s`,
168
+ ]);
169
+ if (log === null)
170
+ return null;
171
+ const authors = [];
172
+ const files = new Map();
173
+ const authorMap = new Map();
174
+ let commitCount = 0;
175
+ let totalInsertions = 0;
176
+ let totalDeletions = 0;
177
+ let newestCommit = '';
178
+ let oldestCommit = '';
179
+ /** Per-commit stamps, for the time histogram. Bounded by `maxCommits`. */
180
+ const stamps = [];
181
+ for (const chunk of log.split(RS)) {
182
+ if (chunk.trim() === '')
183
+ continue;
184
+ const newline = chunk.indexOf('\n');
185
+ const header = newline === -1 ? chunk : chunk.slice(0, newline);
186
+ const body = newline === -1 ? '' : chunk.slice(newline + 1);
187
+ // `split` yields a plain array, so a short header really can leave these
188
+ // undefined even though the declared element type says otherwise.
189
+ const [sha, author, email, date, _subject] = header.split(US);
190
+ if (sha === undefined || date === undefined)
191
+ continue;
192
+ commitCount += 1;
193
+ if (newestCommit === '' || date > newestCommit)
194
+ newestCommit = date;
195
+ if (oldestCommit === '' || date < oldestCommit)
196
+ oldestCommit = date;
197
+ const key = `${author ?? '?'} <${email ?? '?'}>`;
198
+ let entry = authorMap.get(key);
199
+ if (entry === undefined) {
200
+ entry = {
201
+ name: author ?? '?',
202
+ email: email ?? '?',
203
+ commits: 0,
204
+ insertions: 0,
205
+ deletions: 0,
206
+ files: 0,
207
+ lastCommit: date,
208
+ fileSet: new Set(),
209
+ };
210
+ authorMap.set(key, entry);
211
+ }
212
+ entry.commits += 1;
213
+ if (date > entry.lastCommit)
214
+ entry.lastCommit = date;
215
+ let commitInsertions = 0;
216
+ let commitDeletions = 0;
217
+ for (const line of body.split('\n')) {
218
+ if (line.trim() === '')
219
+ continue;
220
+ const parts = line.split('\t');
221
+ if (parts.length < 3)
222
+ continue;
223
+ const [addedRaw, deletedRaw, ...pathParts] = parts;
224
+ const filePath = pathParts.join('\t');
225
+ if (filePath === '')
226
+ continue;
227
+ // `-` marks a binary file in --numstat; it counts as a change with no
228
+ // line delta, which is the truthful reading.
229
+ const added = Number.parseInt(addedRaw, 10);
230
+ const deleted = Number.parseInt(deletedRaw, 10);
231
+ const ins = Number.isFinite(added) ? added : 0;
232
+ const del = Number.isFinite(deleted) ? deleted : 0;
233
+ totalInsertions += ins;
234
+ totalDeletions += del;
235
+ commitInsertions += ins;
236
+ commitDeletions += del;
237
+ entry.insertions += ins;
238
+ entry.deletions += del;
239
+ entry.fileSet.add(filePath);
240
+ let file = files.get(filePath);
241
+ if (file === undefined) {
242
+ file = { path: filePath, changes: 0, insertions: 0, deletions: 0, lastChange: date };
243
+ files.set(filePath, file);
244
+ }
245
+ file.changes += 1;
246
+ file.insertions += ins;
247
+ file.deletions += del;
248
+ if (date > file.lastChange)
249
+ file.lastChange = date;
250
+ }
251
+ stamps.push({ date, insertions: commitInsertions, deletions: commitDeletions });
252
+ }
253
+ const toActivityFile = (file) => {
254
+ const verdict = classified ? classify(file.path) : 'excluded';
255
+ return { ...file, indexed: verdict === 'indexed', verdict };
256
+ };
257
+ const byChanges = [...files.values()]
258
+ .sort((a, b) => b.changes - a.changes || (a.path < b.path ? -1 : 1))
259
+ .slice(0, maxFiles)
260
+ .map(toActivityFile);
261
+ const byRecency = [...files.values()]
262
+ .sort((a, b) => (a.lastChange < b.lastChange ? 1 : a.lastChange > b.lastChange ? -1 : 0))
263
+ .slice(0, maxFiles)
264
+ .map(toActivityFile);
265
+ authors.push(...[...authorMap.values()]
266
+ .sort((a, b) => b.commits - a.commits)
267
+ .slice(0, maxAuthors)
268
+ .map(({ fileSet, ...rest }) => ({
269
+ ...rest,
270
+ files: fileSet.size,
271
+ bot: looksLikeBot(rest.name, rest.email),
272
+ })));
273
+ const branchRaw = await git(repoPath, [
274
+ 'for-each-ref',
275
+ `--count=${maxBranches}`,
276
+ '--sort=-committerdate',
277
+ `--format=%(refname:short)${US}%(committerdate:iso-strict)${US}%(authorname)${US}%(HEAD)`,
278
+ 'refs/heads',
279
+ ]);
280
+ const branches = [];
281
+ if (branchRaw !== null) {
282
+ for (const line of branchRaw.split('\n')) {
283
+ if (line.trim() === '')
284
+ continue;
285
+ const [name, date, author, head] = line.split(US);
286
+ if (name === undefined)
287
+ continue;
288
+ branches.push({
289
+ name,
290
+ lastCommitDate: date ?? '',
291
+ lastAuthor: author ?? '',
292
+ current: head === '*',
293
+ });
294
+ }
295
+ }
296
+ const verdictCounts = { indexed: 0, excluded: 0, missing: 0 };
297
+ for (const path of files.keys()) {
298
+ verdictCounts[classified ? classify(path) : 'excluded'] += 1;
299
+ }
300
+ // Kept out of the `byChanges`/`byRecency` display caps on purpose. Those caps
301
+ // exist to bound a list a reader browses; "missing from the index" is the
302
+ // actionable subset, and a first revision lost it there — the summary reported
303
+ // "1 MISSING" and then listed nothing, because the file was not among the 200
304
+ // most-changed. A count with no rows is worse than no count.
305
+ const missingFiles = [...files.values()]
306
+ .filter((file) => classified && classify(file.path) === 'missing')
307
+ .sort((a, b) => b.changes - a.changes || (a.path < b.path ? -1 : 1))
308
+ .slice(0, 50)
309
+ .map(toActivityFile);
310
+ return {
311
+ window: {
312
+ sinceDays,
313
+ since,
314
+ maxCommits,
315
+ maxFiles,
316
+ // A capped log that does not say so reads as "this is all the activity there
317
+ // was" — the one conclusion the caller must not draw.
318
+ truncated: commitCount >= maxCommits,
319
+ oldestCommit: oldestCommit === '' ? null : oldestCommit,
320
+ newestCommit: newestCommit === '' ? null : newestCommit,
321
+ },
322
+ totals: {
323
+ commits: commitCount,
324
+ authors: authorMap.size,
325
+ bots: authors.filter((a) => a.bot).length,
326
+ files: files.size,
327
+ insertions: totalInsertions,
328
+ deletions: totalDeletions,
329
+ verdicts: verdictCounts,
330
+ indexKnown: classified,
331
+ // Kept under their original names so a caller written against the first
332
+ // revision keeps working; `verdicts` is the richer view.
333
+ filesTouchedThatAreIndexed: verdictCounts.indexed,
334
+ filesTouchedThatAreNotIndexed: verdictCounts.excluded + verdictCounts.missing,
335
+ },
336
+ authors,
337
+ byChanges,
338
+ byRecency,
339
+ missingFiles,
340
+ branches,
341
+ histogram: buildHistogram(stamps, sinceDays),
342
+ currentBranch: branches.find((b) => b.current)?.name ?? null,
343
+ };
344
+ }
345
+ //# sourceMappingURL=git-activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-activity.js","sourceRoot":"","sources":["../../src/gitnexus/git-activity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The two git facts the panel needs, read without a shell.
3
+ *
4
+ * Deliberately not `exec`: a repository path is caller-supplied, and the only way
5
+ * to keep it from becoming an argument is to pass it as `cwd` and never as part of
6
+ * a command string. No shell is spawned, so nothing in the path can be
7
+ * interpreted.
8
+ *
9
+ * Both calls are bounded and both fail soft. A repository with no commits, a
10
+ * detached worktree, or no git at all is a normal state for an index that still
11
+ * has a valid manifest — the panel should say "unknown", not refuse to render.
12
+ */
13
+ /**
14
+ * How far each side has moved from the other, as `rev-list --left-right --count`
15
+ * reports it: commits the indexed commit has that HEAD does not, and the reverse.
16
+ */
17
+ export interface AheadBehind {
18
+ /** commits the index side has that HEAD does not */
19
+ behind: number;
20
+ /** commits HEAD has that the index side does not */
21
+ ahead: number;
22
+ }
23
+ export interface GitState {
24
+ /** git answered at all */
25
+ available: boolean;
26
+ /** full sha of HEAD */
27
+ headCommit: string | null;
28
+ /** current branch, or null when detached */
29
+ branch: string | null;
30
+ /** tracked modifications plus untracked files */
31
+ dirtyCount: number | null;
32
+ /** commits ahead/behind the indexed commit */
33
+ aheadBehind: AheadBehind | null;
34
+ }
35
+ /**
36
+ * Read HEAD, branch and working-tree dirtiness.
37
+ *
38
+ * @returns the state, with every unanswerable field left `null`
39
+ */
40
+ export declare function readGitState(repoPath: string, indexedCommit?: string | null): Promise<GitState>;
41
+ /** The states {@link classifyStaleness} can report. */
42
+ export type StalenessState = 'current' | 'behind' | 'diverged' | 'unknown';
43
+ /** What {@link classifyStaleness} judges: the indexed commit and the live git state. */
44
+ export interface StalenessInput {
45
+ lastCommit: string | null;
46
+ git: GitState;
47
+ }
48
+ /**
49
+ * Classify staleness the way `gitnexus status` does, so the panel and the CLI
50
+ * cannot disagree.
51
+ *
52
+ * READING `rev-list --left-right --count <indexed>...<HEAD>`
53
+ * ---------------------------------------------------------
54
+ * The output is `<left> <right>`: commits reachable from the first ref but not the
55
+ * second, then the reverse. With the indexed commit first:
56
+ *
57
+ * left = commits the INDEX has that HEAD does not → histories have split
58
+ * right = commits HEAD has that the index does not → how far the index lags
59
+ *
60
+ * So a linear fast-forward is `left = 0, right = N`: the index is simply N commits
61
+ * behind, and calling that "diverged" is wrong. Divergence requires `left > 0` —
62
+ * the indexed commit carries work that HEAD no longer reaches, which is when the
63
+ * distance between them stops measuring how stale the index is.
64
+ *
65
+ * A first revision had this exactly backwards, and the panel told an operator their
66
+ * index had diverged when it was one ordinary commit behind. The condition is
67
+ * asserted in both directions by `test/run.mjs` for that reason.
68
+ */
69
+ export declare function classifyStaleness({ lastCommit, git }: StalenessInput): StalenessState;
70
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/gitnexus/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA6CH;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,uBAAuB;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4CAA4C;IAC5C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iDAAiD;IACjD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,8CAA8C;IAC9C,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,aAAa,GAAE,MAAM,GAAG,IAAW,GAClC,OAAO,CAAC,QAAQ,CAAC,CA0CnB;AAED,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE3E,wFAAwF;AACxF,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,QAAQ,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,cAAc,GAAG,cAAc,CAOrF"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * The two git facts the panel needs, read without a shell.
3
+ *
4
+ * Deliberately not `exec`: a repository path is caller-supplied, and the only way
5
+ * to keep it from becoming an argument is to pass it as `cwd` and never as part of
6
+ * a command string. No shell is spawned, so nothing in the path can be
7
+ * interpreted.
8
+ *
9
+ * Both calls are bounded and both fail soft. A repository with no commits, a
10
+ * detached worktree, or no git at all is a normal state for an index that still
11
+ * has a valid manifest — the panel should say "unknown", not refuse to render.
12
+ */
13
+ import { execFile } from 'node:child_process';
14
+ import { promisify } from 'node:util';
15
+ /**
16
+ * `execFile`, promisified.
17
+ *
18
+ * The assertion picks out the text-encoding result: no `encoding` is passed, so
19
+ * Node decodes stdout as UTF-8, and the `Buffer` arm the generic overload admits
20
+ * is one this call never takes.
21
+ */
22
+ const run = promisify(execFile);
23
+ /** Short cap: `rev-parse` and `status` on a large repo are fast, and a hang here
24
+ * would block the whole panel for a value that is merely informative. */
25
+ const TIMEOUT_MS = 8000;
26
+ /**
27
+ * @returns trimmed stdout, or null on any failure
28
+ */
29
+ async function git(repoPath, args) {
30
+ try {
31
+ const { stdout } = await run('git', args, {
32
+ cwd: repoPath,
33
+ timeout: TIMEOUT_MS,
34
+ windowsHide: true,
35
+ maxBuffer: 8 * 1024 * 1024,
36
+ });
37
+ return stdout;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * Read HEAD, branch and working-tree dirtiness.
45
+ *
46
+ * @returns the state, with every unanswerable field left `null`
47
+ */
48
+ export async function readGitState(repoPath, indexedCommit = null) {
49
+ const head = await git(repoPath, ['rev-parse', 'HEAD']);
50
+ if (head === null) {
51
+ return {
52
+ available: false,
53
+ headCommit: null,
54
+ branch: null,
55
+ dirtyCount: null,
56
+ aheadBehind: null,
57
+ };
58
+ }
59
+ const [branchRaw, statusRaw] = await Promise.all([
60
+ git(repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']),
61
+ git(repoPath, ['status', '--porcelain']),
62
+ ]);
63
+ let dirtyCount = null;
64
+ if (statusRaw !== null) {
65
+ dirtyCount = statusRaw.split('\n').filter((line) => line.trim() !== '').length;
66
+ }
67
+ // How far HEAD has moved from the indexed commit, when both are known. Counted
68
+ // in one direction only: what matters for staleness is whether the index is
69
+ // behind, and a diverged history is reported as such rather than counted.
70
+ let aheadBehind = null;
71
+ const indexed = typeof indexedCommit === 'string' && indexedCommit !== '' ? indexedCommit : null;
72
+ if (indexed !== null) {
73
+ const counts = await git(repoPath, ['rev-list', '--left-right', '--count', `${indexed}...HEAD`]);
74
+ if (counts !== null) {
75
+ const [behind, ahead] = counts.trim().split(/\s+/).map((n) => Number.parseInt(n, 10));
76
+ if (Number.isFinite(behind) && Number.isFinite(ahead))
77
+ aheadBehind = { behind, ahead };
78
+ }
79
+ }
80
+ return {
81
+ available: true,
82
+ headCommit: head.trim(),
83
+ branch: branchRaw === null ? null : branchRaw.trim() === 'HEAD' ? null : branchRaw.trim(),
84
+ dirtyCount,
85
+ aheadBehind,
86
+ };
87
+ }
88
+ /**
89
+ * Classify staleness the way `gitnexus status` does, so the panel and the CLI
90
+ * cannot disagree.
91
+ *
92
+ * READING `rev-list --left-right --count <indexed>...<HEAD>`
93
+ * ---------------------------------------------------------
94
+ * The output is `<left> <right>`: commits reachable from the first ref but not the
95
+ * second, then the reverse. With the indexed commit first:
96
+ *
97
+ * left = commits the INDEX has that HEAD does not → histories have split
98
+ * right = commits HEAD has that the index does not → how far the index lags
99
+ *
100
+ * So a linear fast-forward is `left = 0, right = N`: the index is simply N commits
101
+ * behind, and calling that "diverged" is wrong. Divergence requires `left > 0` —
102
+ * the indexed commit carries work that HEAD no longer reaches, which is when the
103
+ * distance between them stops measuring how stale the index is.
104
+ *
105
+ * A first revision had this exactly backwards, and the panel told an operator their
106
+ * index had diverged when it was one ordinary commit behind. The condition is
107
+ * asserted in both directions by `test/run.mjs` for that reason.
108
+ */
109
+ export function classifyStaleness({ lastCommit, git }) {
110
+ if (!git.available || git.headCommit === null)
111
+ return 'unknown';
112
+ if (typeof lastCommit !== 'string' || lastCommit === '')
113
+ return 'unknown';
114
+ if (git.headCommit === lastCommit)
115
+ return 'current';
116
+ if (git.aheadBehind === null)
117
+ return 'unknown';
118
+ // `left > 0` means the indexed commit holds commits HEAD does not reach.
119
+ return git.aheadBehind.behind > 0 ? 'diverged' : 'behind';
120
+ }
121
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/gitnexus/git.ts"],"names":[],"mappings":"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"}