codeant-cli 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,774 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import { promisify } from 'util';
4
- import { exec } from 'child_process';
5
-
6
- const execAsync = promisify(exec);
7
-
8
- class GitDiffHelper {
9
- constructor(workspacePath) {
10
- this.workspacePath = workspacePath;
11
- this.gitRoot = null;
12
- this.currentBranch = null;
13
- this.defaultBranch = null;
14
- this.baseCommit = null;
15
- }
16
-
17
- /**
18
- * Find the git root directory by traversing up from the workspace path
19
- */
20
- async findGitRoot(directory) {
21
- try {
22
- // First check current directory
23
- const files = await fs.readdir(directory);
24
- if (files.includes('.git')) {
25
- return directory;
26
- }
27
-
28
- // Traverse up to parent directories
29
- let currentDir = directory;
30
- while (currentDir !== path.dirname(currentDir)) {
31
- currentDir = path.dirname(currentDir);
32
- try {
33
- const parentFiles = await fs.readdir(currentDir);
34
- if (parentFiles.includes('.git')) {
35
- return currentDir;
36
- }
37
- } catch (err) {
38
- break;
39
- }
40
- }
41
-
42
- return null;
43
- } catch (error) {
44
- console.error(`Error finding git root: ${error.message}`);
45
- return null;
46
- }
47
- }
48
-
49
- /**
50
- * Initialize the helper: finds git root, current branch, default branch, and base commit
51
- */
52
- async init() {
53
- // Find git root
54
- this.gitRoot = await this.findGitRoot(this.workspacePath);
55
- if (!this.gitRoot) {
56
- throw new Error('Could not find a .git directory.');
57
- }
58
-
59
- try {
60
- // Try to fetch origin (silently fails if no remote)
61
- await this.fetchOrigin();
62
- } catch (err) {
63
- // No remote, continue anyway
64
- }
65
-
66
- try {
67
- // Get current branch
68
- const { stdout: branchName } = await execAsync(
69
- 'git rev-parse --abbrev-ref HEAD',
70
- { cwd: this.gitRoot }
71
- );
72
- this.currentBranch = branchName.trim();
73
- } catch (err) {
74
- this.currentBranch = 'main';
75
- }
76
-
77
- // Determine default branch
78
- try {
79
- const { stdout: remoteBranch } = await execAsync(
80
- 'git rev-parse --abbrev-ref origin/HEAD',
81
- { cwd: this.gitRoot }
82
- );
83
- this.defaultBranch = remoteBranch.trim().replace('origin/', '');
84
- } catch (err) {
85
- this.defaultBranch = 'main';
86
- }
87
-
88
- // Find merge base commit or fallback to HEAD
89
- try {
90
- const { stdout: mergeBase } = await execAsync(
91
- `git merge-base ${this.currentBranch} origin/${this.defaultBranch}`,
92
- { cwd: this.gitRoot }
93
- );
94
- this.baseCommit = mergeBase.trim();
95
- } catch (err) {
96
- // Fallback to HEAD if no merge base found
97
- try {
98
- const { stdout: head } = await execAsync(
99
- 'git rev-parse HEAD',
100
- { cwd: this.gitRoot }
101
- );
102
- this.baseCommit = head.trim();
103
- } catch (headErr) {
104
- // No commits yet - set to empty tree
105
- this.baseCommit = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
106
- }
107
- }
108
- }
109
-
110
- async fetchOrigin() {
111
- try {
112
- await execAsync('git fetch origin', { cwd: this.gitRoot });
113
- } catch (err) {
114
- // Silently continue if fetch fails (e.g., offline)
115
- }
116
- }
117
-
118
- /**
119
- * Get all branches (local and remote)
120
- */
121
- async getAllBranches() {
122
- if (!this.gitRoot) {
123
- throw new Error('GitDiffHelper not initialized. Call init() first.');
124
- }
125
-
126
- const { stdout: branchesStr } = await execAsync(
127
- 'git branch -a --format="%(refname:short)"',
128
- { cwd: this.gitRoot }
129
- );
130
-
131
- const branches = branchesStr
132
- .split('\n')
133
- .map(b => b.trim())
134
- .filter(Boolean)
135
- .map(b => b.replace('origin/', ''))
136
- .filter((value, index, self) => self.indexOf(value) === index);
137
-
138
- return branches;
139
- }
140
-
141
- /**
142
- * Get recent commits with metadata
143
- */
144
- async getRecentCommits(limit = 10) {
145
- if (!this.gitRoot) {
146
- throw new Error('GitDiffHelper not initialized. Call init() first.');
147
- }
148
-
149
- const { stdout: commitsStr } = await execAsync(
150
- `git log --pretty=format:"%H|%s|%an|%ar" -n ${limit}`,
151
- { cwd: this.gitRoot }
152
- );
153
-
154
- const commits = commitsStr
155
- .split('\n')
156
- .filter(Boolean)
157
- .map(line => {
158
- const [hash, message, author, date] = line.split('|');
159
- return { hash, message, author, date };
160
- });
161
-
162
- return commits;
163
- }
164
-
165
- /**
166
- * Get files changed since merge base
167
- */
168
- async getChangedFiles() {
169
- if (!this.gitRoot || !this.baseCommit) {
170
- throw new Error('GitDiffHelper not initialized. Call init() first.');
171
- }
172
-
173
- const cmd = [
174
- `git diff --name-only --diff-filter=ACMRD ${this.baseCommit}`,
175
- `git diff --name-only --cached --diff-filter=ACMRD`,
176
- `git ls-files --others --exclude-standard`
177
- ].join(' && ');
178
-
179
- const { stdout: changedFiles } = await execAsync(cmd, { cwd: this.gitRoot });
180
-
181
- const uniqueFiles = Array.from(new Set(
182
- changedFiles
183
- .split('\n')
184
- .map(f => f.trim())
185
- .filter(Boolean)
186
- ));
187
-
188
- return uniqueFiles;
189
- }
190
-
191
- /**
192
- * Get staged files only
193
- */
194
- async getStagedFiles() {
195
- if (!this.gitRoot) {
196
- throw new Error('GitDiffHelper not initialized. Call init() first.');
197
- }
198
-
199
- const { stdout: stagedFiles } = await execAsync(
200
- 'git diff --name-only --cached',
201
- { cwd: this.gitRoot }
202
- );
203
-
204
- return stagedFiles
205
- .split('\n')
206
- .map(f => f.trim())
207
- .filter(Boolean);
208
- }
209
-
210
- /**
211
- * Get diff based on review configuration
212
- */
213
- async getDiffBasedOnReviewConfig(reviewConfig = null) {
214
- if (!this.gitRoot) {
215
- throw new Error('GitDiffHelper not initialized. Call init() first.');
216
- }
217
-
218
- const config = reviewConfig || {
219
- type: 'branch-diff',
220
- targetBranch: this.defaultBranch,
221
- commits: null
222
- };
223
-
224
- switch (config.type) {
225
- case 'branch-diff':
226
- return this.getAllDiffInfo();
227
-
228
- case 'last-commit':
229
- return this._getLastNCommitDiff(1);
230
-
231
- case 'last-n-commits':
232
- const n = config.commits || 1;
233
- return this._getLastNCommitDiff(n);
234
-
235
- case 'select-commits':
236
- if (!config.commits || config.commits.length === 0) {
237
- return [];
238
- }
239
- return this._getSpecificCommitsDiff(config.commits);
240
-
241
- case 'uncommitted':
242
- return this._getUncommittedChanges();
243
-
244
- case 'staged-only':
245
- return this._getStagedChanges();
246
-
247
- case 'unpushed':
248
- return this.getUnpushedChangesDiff();
249
-
250
- default:
251
- return this.getAllDiffInfo();
252
- }
253
- }
254
-
255
- async _getLastNCommitDiff(n) {
256
- if (!this.gitRoot) {
257
- throw new Error('GitDiffHelper not initialized. Call init() first.');
258
- }
259
-
260
- // Clamp n between 1 and 5
261
- n = Math.min(Math.max(1, n), 5);
262
-
263
- // First check if there are any commits
264
- try {
265
- await execAsync('git rev-parse HEAD', { cwd: this.gitRoot });
266
- } catch (err) {
267
- // No commits at all
268
- return [];
269
- }
270
-
271
- const originalBaseCommit = this.baseCommit;
272
-
273
- try {
274
- // Check if HEAD~n exists (more than n commits)
275
- await execAsync(`git rev-parse HEAD~${n}`, { cwd: this.gitRoot });
276
-
277
- const { stdout: changedFiles } = await execAsync(
278
- `git diff --name-only HEAD~${n} HEAD`,
279
- { cwd: this.gitRoot }
280
- );
281
-
282
- const files = changedFiles
283
- .split('\n')
284
- .map(f => f.trim())
285
- .filter(Boolean);
286
-
287
- if (files.length === 0) {
288
- return [];
289
- }
290
-
291
- this.baseCommit = `HEAD~${n}`;
292
-
293
- const diffs = await Promise.all(
294
- files.map(fp => this.getDiffInfoForFile(fp))
295
- );
296
-
297
- this.baseCommit = originalBaseCommit;
298
- return diffs.flat();
299
- } catch (err) {
300
- // HEAD~n doesn't exist - fewer than n commits, include all available
301
- try {
302
- const emptyTree = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
303
- const { stdout: changedFiles } = await execAsync(
304
- `git diff --name-only ${emptyTree} HEAD`,
305
- { cwd: this.gitRoot }
306
- );
307
-
308
- const files = changedFiles
309
- .split('\n')
310
- .map(f => f.trim())
311
- .filter(Boolean);
312
-
313
- if (files.length === 0) {
314
- return [];
315
- }
316
-
317
- // Compare against empty tree to capture all commits
318
- this.baseCommit = emptyTree;
319
-
320
- const diffs = await Promise.all(
321
- files.map(fp => this.getDiffInfoForFile(fp))
322
- );
323
-
324
- this.baseCommit = originalBaseCommit;
325
- return diffs.flat();
326
- } catch (innerErr) {
327
- console.error('Error getting last commit diff:', innerErr.message);
328
- this.baseCommit = originalBaseCommit;
329
- return [];
330
- }
331
- }
332
- }
333
-
334
- async _getSpecificCommitsDiff(commitHashes) {
335
- const allDiffs = [];
336
- const originalBaseCommit = this.baseCommit;
337
-
338
- for (const hash of commitHashes) {
339
- try {
340
- const { stdout: changedFiles } = await execAsync(
341
- `git diff --name-only ${hash}~1 ${hash}`,
342
- { cwd: this.gitRoot }
343
- );
344
-
345
- const files = changedFiles
346
- .split('\n')
347
- .map(f => f.trim())
348
- .filter(Boolean);
349
-
350
- this.baseCommit = `${hash}~1`;
351
- const commitDiffs = await Promise.all(
352
- files.map(fp => this.getDiffInfoForFile(fp))
353
- );
354
-
355
- allDiffs.push(...commitDiffs.flat());
356
- } catch (error) {
357
- console.error(`Error processing commit ${hash}:`, error.message);
358
- }
359
- }
360
-
361
- this.baseCommit = originalBaseCommit;
362
- return allDiffs;
363
- }
364
-
365
- async _getUncommittedChanges() {
366
- const { stdout: allModified } = await execAsync(
367
- 'git status --porcelain',
368
- { cwd: this.gitRoot }
369
- );
370
-
371
- const files = allModified
372
- .split('\n')
373
- .map(line => line.substring(3).trim())
374
- .filter(Boolean);
375
-
376
- const originalBaseCommit = this.baseCommit;
377
- this.baseCommit = 'HEAD';
378
-
379
- const diffs = [];
380
- for (const file of files) {
381
- try {
382
- const { stdout: patchStr } = await execAsync(
383
- `git diff HEAD -- "${file}"`,
384
- { cwd: this.gitRoot }
385
- );
386
-
387
- if (patchStr) {
388
- const diffInfo = await this.getDiffInfoForFile(file);
389
- diffs.push(...diffInfo);
390
- }
391
- } catch (error) {
392
- // Skip files that can't be diffed
393
- }
394
- }
395
-
396
- this.baseCommit = originalBaseCommit;
397
- return diffs;
398
- }
399
-
400
- async _getStagedChanges() {
401
- const { stdout: stagedFiles } = await execAsync(
402
- 'git diff --name-only --cached HEAD',
403
- { cwd: this.gitRoot }
404
- );
405
-
406
- const files = stagedFiles
407
- .split('\n')
408
- .map(f => f.trim())
409
- .filter(Boolean);
410
-
411
- const diffs = [];
412
- const originalBaseCommit = this.baseCommit;
413
-
414
- for (const file of files) {
415
- try {
416
- this.baseCommit = 'HEAD';
417
- const diffInfo = await this.getDiffInfoForFile(file);
418
- diffs.push(...diffInfo);
419
- } catch (error) {
420
- console.error(`Error processing staged file ${file}:`, error.message);
421
- }
422
- }
423
-
424
- this.baseCommit = originalBaseCommit;
425
- return diffs;
426
- }
427
-
428
- async getUnpushedChangesDiff() {
429
- if (!this.gitRoot) {
430
- throw new Error('GitDiffHelper not initialized. Call init() first.');
431
- }
432
-
433
- try {
434
- const { stdout: currentBranchRaw } = await execAsync(
435
- 'git rev-parse --abbrev-ref HEAD',
436
- { cwd: this.gitRoot }
437
- );
438
- const currentBranch = currentBranchRaw.trim();
439
-
440
- let upstream = null;
441
-
442
- try {
443
- const { stdout: upstreamRaw } = await execAsync(
444
- 'git rev-parse --abbrev-ref --symbolic-full-name @{u}',
445
- { cwd: this.gitRoot }
446
- );
447
- upstream = upstreamRaw.trim();
448
- } catch (upstreamError) {
449
- // Try to find remote branch
450
- try {
451
- const { stdout: remoteBranches } = await execAsync(
452
- 'git branch -r',
453
- { cwd: this.gitRoot }
454
- );
455
-
456
- if (remoteBranches.includes(`origin/${currentBranch}`)) {
457
- upstream = `origin/${currentBranch}`;
458
- }
459
- } catch (error) {
460
- // Ignore
461
- }
462
-
463
- if (!upstream) {
464
- const defaultBranches = ['origin/main', 'origin/master'];
465
- for (const branch of defaultBranches) {
466
- try {
467
- await execAsync(`git rev-parse ${branch}`, { cwd: this.gitRoot });
468
- upstream = branch;
469
- break;
470
- } catch (e) {
471
- // Try next
472
- }
473
- }
474
- }
475
- }
476
-
477
- if (!upstream) {
478
- return this._getUncommittedChanges();
479
- }
480
-
481
- // Get unpushed files
482
- const { stdout: unpushedFiles } = await execAsync(
483
- `git diff --name-only ${upstream}..HEAD`,
484
- { cwd: this.gitRoot }
485
- );
486
- const unpushedFilesList = unpushedFiles.split('\n').map(f => f.trim()).filter(Boolean);
487
-
488
- // Get staged files
489
- const { stdout: stagedFiles } = await execAsync(
490
- 'git diff --name-only --cached',
491
- { cwd: this.gitRoot }
492
- );
493
- const stagedFilesList = stagedFiles.split('\n').map(f => f.trim()).filter(Boolean);
494
-
495
- // Get unstaged files
496
- const { stdout: unstagedFiles } = await execAsync(
497
- 'git diff --name-only',
498
- { cwd: this.gitRoot }
499
- );
500
- const unstagedFilesList = unstagedFiles.split('\n').map(f => f.trim()).filter(Boolean);
501
-
502
- // Combine and deduplicate
503
- const uniqueFiles = Array.from(new Set([
504
- ...unpushedFilesList,
505
- ...stagedFilesList,
506
- ...unstagedFilesList
507
- ]));
508
-
509
- if (uniqueFiles.length === 0) {
510
- return [];
511
- }
512
-
513
- const originalBaseCommit = this.baseCommit;
514
- try {
515
- const { stdout: upstreamCommit } = await execAsync(
516
- `git rev-parse ${upstream}`,
517
- { cwd: this.gitRoot }
518
- );
519
- this.baseCommit = upstreamCommit.trim();
520
-
521
- const diffs = await Promise.all(
522
- uniqueFiles.map(fp => this.getDiffInfoForFile(fp))
523
- );
524
-
525
- return diffs.flat();
526
- } finally {
527
- this.baseCommit = originalBaseCommit;
528
- }
529
- } catch (error) {
530
- console.error('Error getting unpushed changes:', error.message);
531
- return this._getUncommittedChanges();
532
- }
533
- }
534
-
535
- async getUnpushedCommits() {
536
- if (!this.gitRoot) {
537
- throw new Error('GitDiffHelper not initialized. Call init() first.');
538
- }
539
-
540
- try {
541
- let upstream;
542
- try {
543
- const { stdout } = await execAsync(
544
- 'git rev-parse --abbrev-ref --symbolic-full-name @{u}',
545
- { cwd: this.gitRoot }
546
- );
547
- upstream = stdout.trim();
548
- } catch (error) {
549
- // Find alternative upstream
550
- const { stdout: currentBranch } = await execAsync(
551
- 'git rev-parse --abbrev-ref HEAD',
552
- { cwd: this.gitRoot }
553
- );
554
-
555
- const { stdout: remoteBranches } = await execAsync(
556
- `git ls-remote --heads origin ${currentBranch.trim()}`,
557
- { cwd: this.gitRoot }
558
- );
559
-
560
- if (remoteBranches.trim()) {
561
- upstream = `origin/${currentBranch.trim()}`;
562
- } else {
563
- upstream = 'origin/main';
564
- }
565
- }
566
-
567
- const { stdout: commitsStr } = await execAsync(
568
- `git log ${upstream}..HEAD --pretty=format:"%H|%s|%an|%ar"`,
569
- { cwd: this.gitRoot }
570
- );
571
-
572
- if (!commitsStr.trim()) {
573
- return [];
574
- }
575
-
576
- return commitsStr
577
- .split('\n')
578
- .filter(Boolean)
579
- .map(line => {
580
- const [hash, message, author, date] = line.split('|');
581
- return { hash, message, author, date };
582
- });
583
- } catch (error) {
584
- console.error('Error getting unpushed commits:', error.message);
585
- return [];
586
- }
587
- }
588
-
589
- _generateRandomHash() {
590
- return Math.random().toString(36).substring(2, 9) + Math.random().toString(36).substring(2, 9);
591
- }
592
-
593
- async getFileDiff(filePath) {
594
- if (!this.gitRoot || !this.baseCommit) {
595
- throw new Error('GitDiffHelper not initialized. Call init() first.');
596
- }
597
-
598
- let diff = '';
599
-
600
- try {
601
- await execAsync(`git ls-files --error-unmatch "${filePath}"`, {
602
- cwd: this.gitRoot,
603
- });
604
-
605
- const { stdout } = await execAsync(
606
- `git diff ${this.baseCommit} -- "${filePath}"`,
607
- { cwd: this.gitRoot }
608
- );
609
- diff = stdout;
610
- } catch (err) {
611
- // Handle untracked files
612
- try {
613
- const absoluteFilePath = path.resolve(this.gitRoot, filePath);
614
- const headFileStr = await fs.readFile(absoluteFilePath, 'utf8');
615
- const lines = headFileStr.split('\n');
616
-
617
- let patchStr = `diff --git a/${filePath} b/${filePath}\n`;
618
- patchStr += `new file mode 100644\n`;
619
- patchStr += `index 0000000..${this._generateRandomHash()}\n`;
620
- patchStr += `--- /dev/null\n`;
621
- patchStr += `+++ b/${filePath}\n`;
622
- patchStr += `@@ -0,0 +1,${lines.length} @@\n`;
623
-
624
- lines.forEach(line => {
625
- patchStr += `+${line}\n`;
626
- });
627
-
628
- diff = patchStr;
629
- } catch (manualDiffError) {
630
- diff = '';
631
- }
632
- }
633
-
634
- return diff;
635
- }
636
-
637
- async getAllDiffs() {
638
- if (!this.gitRoot || !this.baseCommit) {
639
- throw new Error('GitDiffHelper not initialized. Call init() first.');
640
- }
641
-
642
- const { stdout: fullDiff } = await execAsync(
643
- `git diff ${this.baseCommit}`,
644
- { cwd: this.gitRoot }
645
- );
646
-
647
- return fullDiff;
648
- }
649
-
650
- async getDiffInfoForFile(filePath) {
651
- if (!this.gitRoot || !this.baseCommit) {
652
- throw new Error('GitDiffHelper not initialized. Call init() first.');
653
- }
654
-
655
- // Get base and head file contents
656
- const baseFileStr = await execAsync(
657
- `git show ${this.baseCommit}:"${filePath}"`,
658
- { cwd: this.gitRoot }
659
- ).then(r => r.stdout).catch(() => '');
660
-
661
- const headFileStr = await fs.readFile(
662
- path.join(this.gitRoot, filePath), 'utf8'
663
- ).catch(() => '');
664
-
665
- // Get patch
666
- const patchStr = await this.getFileDiff(filePath);
667
-
668
- // Get file status
669
- const { stdout: nameStatus } = await execAsync(
670
- `git diff --name-status ${this.baseCommit} -- "${filePath}"`,
671
- { cwd: this.gitRoot }
672
- ).catch(() => ({ stdout: '' }));
673
-
674
- let editTypeStr = 'MODIFIED';
675
- let oldFilenameStr = null;
676
- let filenameStr = filePath;
677
-
678
- if (nameStatus.trim()) {
679
- const [statusCode, oldName, newName] = nameStatus.trim().split(/\t+/);
680
- if (statusCode.startsWith('A')) editTypeStr = 'ADDED';
681
- else if (statusCode.startsWith('D')) editTypeStr = 'DELETED';
682
- else if (statusCode.startsWith('R')) {
683
- editTypeStr = 'RENAMED';
684
- oldFilenameStr = oldName;
685
- filenameStr = newName;
686
- }
687
- }
688
-
689
- // Get line counts
690
- let numPlusLinesStr = '0', numMinusLinesStr = '0';
691
- try {
692
- const { stdout: ns } = await execAsync(
693
- `git diff --numstat ${this.baseCommit} -- "${filePath}"`,
694
- { cwd: this.gitRoot }
695
- );
696
- if (ns.trim()) [numPlusLinesStr, numMinusLinesStr] = ns.split('\t');
697
- } catch (_) { /* ignore */ }
698
-
699
- const tokensStr = headFileStr ? String(headFileStr.split(/\s+/).length) : '0';
700
-
701
- // Parse hunks
702
- const hunkHeader = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/gm;
703
- const results = [];
704
- let headerMatch;
705
-
706
- while ((headerMatch = hunkHeader.exec(patchStr)) !== null) {
707
- const newStart = Number(headerMatch[1]);
708
- const newCount = headerMatch[2] ? Number(headerMatch[2]) : 1;
709
- const hunkStartIdx = headerMatch.index;
710
-
711
- const nextHeaderIdx = patchStr.slice(hunkHeader.lastIndex).search(/^@@/m);
712
- const hunkEndIdx = nextHeaderIdx === -1
713
- ? patchStr.length
714
- : hunkHeader.lastIndex + nextHeaderIdx;
715
- const singleHunkPatch = patchStr.slice(hunkStartIdx, hunkEndIdx);
716
-
717
- results.push({
718
- base_file_str: baseFileStr,
719
- head_file_str: headFileStr,
720
- patch_str: singleHunkPatch,
721
- filename_str: filenameStr,
722
- edit_type_str: editTypeStr,
723
- old_filename_str: oldFilenameStr,
724
- num_plus_lines_str: numPlusLinesStr,
725
- num_minus_lines_str: numMinusLinesStr,
726
- tokens_str: tokensStr,
727
- start_line_str: String(newStart),
728
- end_line_str: String(newStart + newCount - 1),
729
- });
730
- }
731
-
732
- // If no hunks, return single item
733
- if (results.length === 0) {
734
- results.push({
735
- base_file_str: baseFileStr,
736
- head_file_str: headFileStr,
737
- patch_str: patchStr,
738
- filename_str: filenameStr,
739
- edit_type_str: editTypeStr,
740
- old_filename_str: oldFilenameStr,
741
- num_plus_lines_str: numPlusLinesStr,
742
- num_minus_lines_str: numMinusLinesStr,
743
- tokens_str: tokensStr,
744
- start_line_str: '',
745
- end_line_str: '',
746
- });
747
- }
748
-
749
- return results;
750
- }
751
-
752
- getLocalBranch() {
753
- if (!this.currentBranch) {
754
- throw new Error('GitDiffHelper not initialized. Call init() first.');
755
- }
756
- return this.currentBranch;
757
- }
758
-
759
- getGitRoot() {
760
- return this.gitRoot;
761
- }
762
-
763
- async getAllDiffInfo() {
764
- const changedFiles = await this.getChangedFiles();
765
-
766
- const perFileHunks = await Promise.all(
767
- changedFiles.map(fp => this.getDiffInfoForFile(fp))
768
- );
769
-
770
- return perFileHunks.flat();
771
- }
772
- }
773
-
774
- export default GitDiffHelper;