phantomx-tool-client 1.0.7 → 1.0.9

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.
@@ -40,6 +40,8 @@ exports.getRepoList = getRepoList;
40
40
  exports.getRepoBranch = getRepoBranch;
41
41
  const Logger_1 = require("./Services/Logger");
42
42
  const child_process_1 = require("child_process");
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
43
45
  // ---------------------------------------------------------------------------
44
46
  // Local bash executor — drop-in replacement for ssh.executeCommand()
45
47
  // Returns { success, output, error, code } matching the SSH client response.
@@ -127,6 +129,34 @@ function getFileStats(fileStats) {
127
129
  }
128
130
  return { insertions: 0, deletions: 0 };
129
131
  }
132
+ // Helper: recursively find all git repositories under a root path (max depth 10)
133
+ async function findGitRepositories(rootPath) {
134
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
135
+ const pathModule = await Promise.resolve().then(() => __importStar(require('path')));
136
+ const repositories = [];
137
+ async function searchDirectory(currentPath, depth = 0) {
138
+ if (depth > 10)
139
+ return;
140
+ try {
141
+ const items = await fs.promises.readdir(currentPath, { withFileTypes: true });
142
+ const gitPath = pathModule.join(currentPath, '.git');
143
+ if (fs.existsSync(gitPath)) {
144
+ repositories.push(currentPath);
145
+ return; // don't recurse inside a git repo looking for nested repos
146
+ }
147
+ for (const item of items) {
148
+ if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') {
149
+ await searchDirectory(pathModule.join(currentPath, item.name), depth + 1);
150
+ }
151
+ }
152
+ }
153
+ catch {
154
+ // skip directories we can't read
155
+ }
156
+ }
157
+ await searchDirectory(rootPath);
158
+ return repositories;
159
+ }
130
160
  async function getRepositoryChangesMetadataOnly(repoPath) {
131
161
  const pathModule = await Promise.resolve().then(() => __importStar(require('path')));
132
162
  const fs = await Promise.resolve().then(() => __importStar(require('fs')));
@@ -285,6 +315,246 @@ async function getSingleFileDiff(repoPath, filePath, status) {
285
315
  }
286
316
  }
287
317
  class GithubOperationsService {
318
+ constructor(localWorkspacePath) {
319
+ this.localWorkspacePath = localWorkspacePath;
320
+ }
321
+ resolveWorkspacePath(targetPath) {
322
+ const workspace = path.resolve(this.localWorkspacePath || tool_server_1.folderPath);
323
+ const resolved = targetPath
324
+ ? path.resolve(path.isAbsolute(targetPath) ? targetPath : path.join(workspace, targetPath))
325
+ : workspace;
326
+ const relative = path.relative(workspace, resolved);
327
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
328
+ throw new Error(`Path is outside the workspace: ${targetPath}`);
329
+ }
330
+ return resolved;
331
+ }
332
+ resolveLocalRepository(repoPathOrName) {
333
+ const candidate = path.isAbsolute(repoPathOrName)
334
+ ? repoPathOrName
335
+ : path.join(this.localWorkspacePath || tool_server_1.folderPath, repoPathOrName);
336
+ const repoPath = this.resolveWorkspacePath(candidate);
337
+ if (!fs.existsSync(repoPath)) {
338
+ throw new Error(`Repository path does not exist: ${repoPath}`);
339
+ }
340
+ if (!fs.existsSync(path.join(repoPath, '.git'))) {
341
+ throw new Error(`Not a git repository: ${repoPath}`);
342
+ }
343
+ return repoPath;
344
+ }
345
+ async findLocalGitRepositories(rootPath) {
346
+ const repositories = [];
347
+ const search = async (currentPath, depth) => {
348
+ if (depth > 10)
349
+ return;
350
+ if (fs.existsSync(path.join(currentPath, '.git'))) {
351
+ repositories.push(currentPath);
352
+ return;
353
+ }
354
+ let entries;
355
+ try {
356
+ entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
357
+ }
358
+ catch {
359
+ logger.warn(`Cannot read directory: ${currentPath}`);
360
+ return;
361
+ }
362
+ for (const entry of entries) {
363
+ if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules')
364
+ continue;
365
+ await search(path.join(currentPath, entry.name), depth + 1);
366
+ }
367
+ };
368
+ await search(rootPath, 0);
369
+ return repositories;
370
+ }
371
+ getDiffStats(fileStats) {
372
+ return {
373
+ insertions: Number(fileStats === null || fileStats === void 0 ? void 0 : fileStats.insertions) || 0,
374
+ deletions: Number(fileStats === null || fileStats === void 0 ? void 0 : fileStats.deletions) || 0
375
+ };
376
+ }
377
+ async getLocalRepositoryChanges(repoPath) {
378
+ const git = (0, simple_git_1.simpleGit)(repoPath);
379
+ const status = await git.status();
380
+ const stagedSummary = await git.diffSummary(['--cached']);
381
+ const unstagedSummary = await git.diffSummary();
382
+ const changes = [];
383
+ const processed = new Set();
384
+ const addChange = (file, statusCode, insertions, deletions) => {
385
+ changes.push({ file, status: statusCode, insertions, deletions, changes: insertions + deletions });
386
+ processed.add(file);
387
+ };
388
+ for (const file of status.modified) {
389
+ const isStaged = status.staged.includes(file);
390
+ const unstaged = this.getDiffStats(unstagedSummary.files.find(item => item.file === file));
391
+ const staged = isStaged
392
+ ? this.getDiffStats(stagedSummary.files.find(item => item.file === file))
393
+ : { insertions: 0, deletions: 0 };
394
+ addChange(file, isStaged ? 'MM' : 'M', unstaged.insertions + staged.insertions, unstaged.deletions + staged.deletions);
395
+ }
396
+ for (const file of status.staged) {
397
+ if (processed.has(file) || status.deleted.includes(file))
398
+ continue;
399
+ const stats = this.getDiffStats(stagedSummary.files.find(item => item.file === file));
400
+ addChange(file, 'A', stats.insertions, stats.deletions);
401
+ }
402
+ for (const file of status.not_added) {
403
+ if (processed.has(file))
404
+ continue;
405
+ try {
406
+ const stat = await fs.promises.stat(path.join(repoPath, file));
407
+ addChange(file, '?', Math.max(1, Math.ceil(stat.size / 50)), 0);
408
+ }
409
+ catch {
410
+ addChange(file, '?', 1, 0);
411
+ }
412
+ }
413
+ for (const file of status.created || []) {
414
+ if (processed.has(file))
415
+ continue;
416
+ try {
417
+ const stat = await fs.promises.stat(path.join(repoPath, file));
418
+ addChange(file, '?', Math.max(1, Math.ceil(stat.size / 50)), 0);
419
+ }
420
+ catch {
421
+ addChange(file, '?', 1, 0);
422
+ }
423
+ }
424
+ for (const file of status.deleted) {
425
+ if (processed.has(file))
426
+ continue;
427
+ const summary = status.staged.includes(file) ? stagedSummary : unstagedSummary;
428
+ const stats = this.getDiffStats(summary.files.find(item => item.file === file));
429
+ addChange(file, 'D', 0, stats.deletions || 1);
430
+ }
431
+ for (const rename of status.renamed) {
432
+ if (processed.has(rename.to))
433
+ continue;
434
+ const staged = this.getDiffStats(stagedSummary.files.find(item => item.file === rename.to));
435
+ const unstaged = this.getDiffStats(unstagedSummary.files.find(item => item.file === rename.to));
436
+ addChange(rename.to, 'R', staged.insertions + unstaged.insertions, staged.deletions + unstaged.deletions);
437
+ }
438
+ const totalInsertions = changes.reduce((total, change) => total + change.insertions, 0);
439
+ const totalDeletions = changes.reduce((total, change) => total + change.deletions, 0);
440
+ return {
441
+ repoName: path.basename(repoPath),
442
+ repoPath,
443
+ changes,
444
+ summary: {
445
+ totalFiles: changes.length,
446
+ totalInsertions,
447
+ totalDeletions,
448
+ totalChanges: totalInsertions + totalDeletions
449
+ }
450
+ };
451
+ }
452
+ async getGitChanges(requestedPath) {
453
+ try {
454
+ const rootPath = this.resolveWorkspacePath(requestedPath);
455
+ if (!fs.existsSync(rootPath)) {
456
+ return { success: false, error: `Path does not exist: ${rootPath}` };
457
+ }
458
+ const repoPaths = await this.findLocalGitRepositories(rootPath);
459
+ if (repoPaths.length === 0) {
460
+ return { success: false, error: `No git repositories found in: ${rootPath}` };
461
+ }
462
+ const repositories = [];
463
+ for (const repoPath of repoPaths) {
464
+ try {
465
+ repositories.push(await this.getLocalRepositoryChanges(repoPath));
466
+ }
467
+ catch (error) {
468
+ logger.error(`Failed to inspect repository ${repoPath}`, error);
469
+ }
470
+ }
471
+ const totalFiles = repositories.reduce((total, repo) => total + repo.summary.totalFiles, 0);
472
+ const totalInsertions = repositories.reduce((total, repo) => total + repo.summary.totalInsertions, 0);
473
+ const totalDeletions = repositories.reduce((total, repo) => total + repo.summary.totalDeletions, 0);
474
+ return {
475
+ success: true,
476
+ repositories,
477
+ overallSummary: {
478
+ totalRepositories: repositories.length,
479
+ totalFiles,
480
+ totalInsertions,
481
+ totalDeletions,
482
+ totalChanges: totalInsertions + totalDeletions
483
+ }
484
+ };
485
+ }
486
+ catch (error) {
487
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
488
+ }
489
+ }
490
+ async getFileDiff(repoPathOrName, filePath, statusCode) {
491
+ const repoPath = this.resolveLocalRepository(repoPathOrName);
492
+ const fullFilePath = this.resolveWorkspacePath(path.join(repoPath, filePath));
493
+ const relativeFilePath = path.relative(repoPath, fullFilePath);
494
+ if (relativeFilePath.startsWith('..') || path.isAbsolute(relativeFilePath)) {
495
+ throw new Error(`File is outside the repository: ${filePath}`);
496
+ }
497
+ const git = (0, simple_git_1.simpleGit)(repoPath);
498
+ let diff = '';
499
+ if (statusCode === 'MM') {
500
+ const staged = await git.diff(['--cached', relativeFilePath]);
501
+ const unstaged = await git.diff([relativeFilePath]);
502
+ diff = `--- STAGED CHANGES ---\n${staged}\n\n--- UNSTAGED CHANGES ---\n${unstaged}`;
503
+ }
504
+ else if (statusCode === 'A') {
505
+ diff = await git.diff(['--cached', relativeFilePath]);
506
+ }
507
+ else if (statusCode === '?') {
508
+ if (fs.existsSync(fullFilePath)) {
509
+ const lines = (await fs.promises.readFile(fullFilePath, 'utf8')).split('\n');
510
+ diff = `+++ b/${relativeFilePath}\n@@ -0,0 +1,${lines.length} @@\n${lines.map(line => `+${line}`).join('\n')}\n`;
511
+ }
512
+ }
513
+ else if (statusCode === 'D') {
514
+ const currentStatus = await git.status();
515
+ const args = currentStatus.staged.includes(relativeFilePath)
516
+ ? ['--cached', 'HEAD', relativeFilePath]
517
+ : ['HEAD', relativeFilePath];
518
+ diff = await git.diff(args).catch(() => '');
519
+ }
520
+ else {
521
+ diff = await git.diff([relativeFilePath]);
522
+ }
523
+ return { success: true, diff, filePath: relativeFilePath, repoPath };
524
+ }
525
+ async discardFileChanges(repoPathOrName, filePath) {
526
+ const repoPath = this.resolveLocalRepository(repoPathOrName);
527
+ const fullFilePath = this.resolveWorkspacePath(path.join(repoPath, filePath));
528
+ const relativeFilePath = path.relative(repoPath, fullFilePath);
529
+ if (relativeFilePath.startsWith('..') || path.isAbsolute(relativeFilePath)) {
530
+ throw new Error(`File is outside the repository: ${filePath}`);
531
+ }
532
+ const git = (0, simple_git_1.simpleGit)(repoPath);
533
+ const status = await git.status();
534
+ const isUntracked = status.not_added.includes(relativeFilePath) || (status.created || []).includes(relativeFilePath);
535
+ if (isUntracked) {
536
+ if (!fs.existsSync(fullFilePath)) {
537
+ return { success: false, error: `File '${relativeFilePath}' does not exist.` };
538
+ }
539
+ const stat = await fs.promises.lstat(fullFilePath);
540
+ if (!stat.isFile()) {
541
+ return { success: false, error: `Only untracked files can be discarded: ${relativeFilePath}` };
542
+ }
543
+ await fs.promises.unlink(fullFilePath);
544
+ return { success: true, message: `Untracked file '${relativeFilePath}' has been removed.` };
545
+ }
546
+ const hasChanges = status.modified.includes(relativeFilePath)
547
+ || status.staged.includes(relativeFilePath)
548
+ || status.deleted.includes(relativeFilePath);
549
+ if (!hasChanges) {
550
+ return { success: false, error: `File '${relativeFilePath}' has no changes to discard.` };
551
+ }
552
+ if (status.staged.includes(relativeFilePath)) {
553
+ await git.reset(['HEAD', relativeFilePath]);
554
+ }
555
+ await git.checkout(['HEAD', '--', relativeFilePath]);
556
+ return { success: true, message: `Changes to '${relativeFilePath}' have been discarded.` };
557
+ }
288
558
  escapeSingleQuotedShell(value) {
289
559
  return value.replace(/'/g, `'\\''`);
290
560
  }
@@ -411,21 +681,21 @@ class GithubOperationsService {
411
681
  try {
412
682
  logger.info('Starting git push operation', { repoName });
413
683
  // Combine all commands into a single SSH call
414
- const combinedCommand = `
415
- cd ${repoPath} &&
416
- BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
417
- COMMITS_AHEAD=$(sudo git rev-list --count origin/$BRANCH..HEAD 2>/dev/null || echo 0) &&
418
- if [ "$COMMITS_AHEAD" = "0" ]; then
419
- echo "ERROR:NO_COMMITS";
420
- exit 1;
421
- fi &&
422
- sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
423
- sudo git push origin $BRANCH &&
424
- sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
425
- COMMIT_HASH=$(sudo git rev-parse HEAD) &&
426
- echo "BRANCH:$BRANCH" &&
427
- echo "COMMITS_AHEAD:$COMMITS_AHEAD" &&
428
- echo "COMMIT_HASH:$COMMIT_HASH"
684
+ const combinedCommand = `
685
+ cd ${repoPath} &&
686
+ BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
687
+ COMMITS_AHEAD=$(sudo git rev-list --count origin/$BRANCH..HEAD 2>/dev/null || echo 0) &&
688
+ if [ "$COMMITS_AHEAD" = "0" ]; then
689
+ echo "ERROR:NO_COMMITS";
690
+ exit 1;
691
+ fi &&
692
+ sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
693
+ sudo git push origin $BRANCH &&
694
+ sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
695
+ COMMIT_HASH=$(sudo git rev-parse HEAD) &&
696
+ echo "BRANCH:$BRANCH" &&
697
+ echo "COMMITS_AHEAD:$COMMITS_AHEAD" &&
698
+ echo "COMMIT_HASH:$COMMIT_HASH"
429
699
  `.replace(/\n\s+/g, ' ');
430
700
  const result = await executeShell(combinedCommand);
431
701
  if (!result.success) {
@@ -468,23 +738,23 @@ class GithubOperationsService {
468
738
  try {
469
739
  logger.info('Checking repository status', { repoName });
470
740
  // Combine all git commands into a single SSH call
471
- const combinedCommand = `
472
- cd ${repoPath} &&
473
- BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
474
- sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
475
- sudo git fetch origin &&
476
- sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
477
- BEHIND=$(sudo git rev-list --count HEAD..origin/$BRANCH 2>/dev/null || echo 0) &&
478
- AHEAD=$(sudo git rev-list --count origin/$BRANCH..HEAD 2>/dev/null || echo 0) &&
479
- STATUS=$(sudo git status --porcelain) &&
480
- CURRENT_HASH=$(sudo git rev-parse HEAD) &&
481
- REMOTE_HASH=$(sudo git rev-parse origin/$BRANCH 2>/dev/null || echo unknown) &&
482
- echo "BRANCH:$BRANCH" &&
483
- echo "BEHIND:$BEHIND" &&
484
- echo "AHEAD:$AHEAD" &&
485
- echo "STATUS:$STATUS" &&
486
- echo "CURRENT:$CURRENT_HASH" &&
487
- echo "REMOTE:$REMOTE_HASH"
741
+ const combinedCommand = `
742
+ cd ${repoPath} &&
743
+ BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
744
+ sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
745
+ sudo git fetch origin &&
746
+ sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
747
+ BEHIND=$(sudo git rev-list --count HEAD..origin/$BRANCH 2>/dev/null || echo 0) &&
748
+ AHEAD=$(sudo git rev-list --count origin/$BRANCH..HEAD 2>/dev/null || echo 0) &&
749
+ STATUS=$(sudo git status --porcelain) &&
750
+ CURRENT_HASH=$(sudo git rev-parse HEAD) &&
751
+ REMOTE_HASH=$(sudo git rev-parse origin/$BRANCH 2>/dev/null || echo unknown) &&
752
+ echo "BRANCH:$BRANCH" &&
753
+ echo "BEHIND:$BEHIND" &&
754
+ echo "AHEAD:$AHEAD" &&
755
+ echo "STATUS:$STATUS" &&
756
+ echo "CURRENT:$CURRENT_HASH" &&
757
+ echo "REMOTE:$REMOTE_HASH"
488
758
  `.replace(/\n\s+/g, ' ');
489
759
  const result = await executeShell(combinedCommand);
490
760
  if (!result.success) {
@@ -681,23 +951,23 @@ class GithubOperationsService {
681
951
  const escapedAuthorName = commitAuthorName.replace(/"/g, '\\"');
682
952
  const escapedAuthorEmail = commitAuthorEmail.replace(/"/g, '\\"');
683
953
  // Combine all commands into a single SSH call
684
- const combinedCommand = `
685
- cd ${repoPath} &&
686
- sudo git config user.name "${escapedAuthorName}" &&
687
- sudo git config user.email "${escapedAuthorEmail}" &&
688
- STATUS=$(sudo git status --porcelain) &&
689
- if [ -z "$STATUS" ]; then
690
- echo "ERROR:NO_CHANGES";
691
- exit 1;
692
- fi &&
693
- CHANGED_FILES=$(echo "$STATUS" | wc -l) &&
694
- sudo git add . &&
695
- sudo git commit -m "${escapedCommitMsg}" &&
696
- COMMIT_HASH=$(sudo git rev-parse HEAD) &&
697
- SHORT_HASH=$(sudo git rev-parse --short HEAD) &&
698
- echo "CHANGED_FILES:$CHANGED_FILES" &&
699
- echo "COMMIT_HASH:$COMMIT_HASH" &&
700
- echo "SHORT_HASH:$SHORT_HASH"
954
+ const combinedCommand = `
955
+ cd ${repoPath} &&
956
+ sudo git config user.name "${escapedAuthorName}" &&
957
+ sudo git config user.email "${escapedAuthorEmail}" &&
958
+ STATUS=$(sudo git status --porcelain) &&
959
+ if [ -z "$STATUS" ]; then
960
+ echo "ERROR:NO_CHANGES";
961
+ exit 1;
962
+ fi &&
963
+ CHANGED_FILES=$(echo "$STATUS" | wc -l) &&
964
+ sudo git add . &&
965
+ sudo git commit -m "${escapedCommitMsg}" &&
966
+ COMMIT_HASH=$(sudo git rev-parse HEAD) &&
967
+ SHORT_HASH=$(sudo git rev-parse --short HEAD) &&
968
+ echo "CHANGED_FILES:$CHANGED_FILES" &&
969
+ echo "COMMIT_HASH:$COMMIT_HASH" &&
970
+ echo "SHORT_HASH:$SHORT_HASH"
701
971
  `.replace(/\n\s+/g, ' ');
702
972
  const result = await executeShell(combinedCommand);
703
973
  if (!result.success) {
@@ -782,20 +1052,20 @@ class GithubOperationsService {
782
1052
  try {
783
1053
  logger.info('Creating pull request', { repoName, targetBranch });
784
1054
  // Combine commands into a single SSH call, with remote URL set/unset
785
- const combinedCommand = `
786
- cd ${repoPath} &&
787
- sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
788
- HEAD_BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
789
- sudo git fetch origin ${targetBranch} 2>/dev/null &&
790
- COMMITS_AHEAD=$(sudo git rev-list --count origin/${targetBranch}..$HEAD_BRANCH 2>/dev/null || echo 0) &&
791
- if [ "$COMMITS_AHEAD" = "0" ]; then
792
- echo "ERROR:NO_COMMITS:$HEAD_BRANCH";
793
- sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git;
794
- exit 1;
795
- fi &&
796
- echo "HEAD_BRANCH:$HEAD_BRANCH" &&
797
- echo "COMMITS_AHEAD:$COMMITS_AHEAD" &&
798
- sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git
1055
+ const combinedCommand = `
1056
+ cd ${repoPath} &&
1057
+ sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
1058
+ HEAD_BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
1059
+ sudo git fetch origin ${targetBranch} 2>/dev/null &&
1060
+ COMMITS_AHEAD=$(sudo git rev-list --count origin/${targetBranch}..$HEAD_BRANCH 2>/dev/null || echo 0) &&
1061
+ if [ "$COMMITS_AHEAD" = "0" ]; then
1062
+ echo "ERROR:NO_COMMITS:$HEAD_BRANCH";
1063
+ sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git;
1064
+ exit 1;
1065
+ fi &&
1066
+ echo "HEAD_BRANCH:$HEAD_BRANCH" &&
1067
+ echo "COMMITS_AHEAD:$COMMITS_AHEAD" &&
1068
+ sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git
799
1069
  `.replace(/\n\s+/g, ' ');
800
1070
  let result = await executeShell(combinedCommand);
801
1071
  const output = result.output || '';
@@ -992,20 +1262,20 @@ class GithubOperationsService {
992
1262
  const targetLocalBranch = resolvedTarget.displayBranch;
993
1263
  const safeTargetLocalBranch = this.escapeSingleQuotedShell(targetLocalBranch);
994
1264
  const safeSourceRef = this.escapeSingleQuotedShell(resolvedSource.resolvedRef);
995
- const prepareBranchCommand = `
996
- cd ${repoPath} &&
997
- TARGET_LOCAL='${safeTargetLocalBranch}' &&
998
- if sudo git show-ref --verify --quiet "refs/heads/$TARGET_LOCAL"; then
999
- sudo git checkout "$TARGET_LOCAL";
1000
- elif sudo git show-ref --verify --quiet "refs/remotes/origin/$TARGET_LOCAL"; then
1001
- sudo git checkout -b "$TARGET_LOCAL" "origin/$TARGET_LOCAL";
1002
- else
1003
- echo "ERROR:TARGET_BRANCH_NOT_CHECKOUTABLE";
1004
- exit 1;
1005
- fi &&
1006
- if sudo git show-ref --verify --quiet "refs/remotes/origin/$TARGET_LOCAL"; then
1007
- sudo git pull --ff-only origin "$TARGET_LOCAL" 2>/dev/null || true;
1008
- fi
1265
+ const prepareBranchCommand = `
1266
+ cd ${repoPath} &&
1267
+ TARGET_LOCAL='${safeTargetLocalBranch}' &&
1268
+ if sudo git show-ref --verify --quiet "refs/heads/$TARGET_LOCAL"; then
1269
+ sudo git checkout "$TARGET_LOCAL";
1270
+ elif sudo git show-ref --verify --quiet "refs/remotes/origin/$TARGET_LOCAL"; then
1271
+ sudo git checkout -b "$TARGET_LOCAL" "origin/$TARGET_LOCAL";
1272
+ else
1273
+ echo "ERROR:TARGET_BRANCH_NOT_CHECKOUTABLE";
1274
+ exit 1;
1275
+ fi &&
1276
+ if sudo git show-ref --verify --quiet "refs/remotes/origin/$TARGET_LOCAL"; then
1277
+ sudo git pull --ff-only origin "$TARGET_LOCAL" 2>/dev/null || true;
1278
+ fi
1009
1279
  `.replace(/\n\s+/g, ' ');
1010
1280
  result = await executeShell(prepareBranchCommand);
1011
1281
  if (!result.success) {
@@ -1103,16 +1373,16 @@ class GithubOperationsService {
1103
1373
  try {
1104
1374
  logger.info('Checking if PR exists', { repoName, targetBranch });
1105
1375
  // Get current branch and commits ahead
1106
- const combinedCommand = `
1107
- cd ${repoPath} &&
1108
- sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
1109
- HEAD_BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
1110
- sudo git fetch origin --prune 2>/dev/null || true &&
1111
- sudo git fetch origin ${targetBranch} 2>/dev/null || true &&
1112
- COMMITS_AHEAD=$(sudo git rev-list --count origin/${targetBranch}..$HEAD_BRANCH 2>/dev/null || echo 0) &&
1113
- sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
1114
- echo "HEAD_BRANCH:$HEAD_BRANCH" &&
1115
- echo "COMMITS_AHEAD:$COMMITS_AHEAD"
1376
+ const combinedCommand = `
1377
+ cd ${repoPath} &&
1378
+ sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
1379
+ HEAD_BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
1380
+ sudo git fetch origin --prune 2>/dev/null || true &&
1381
+ sudo git fetch origin ${targetBranch} 2>/dev/null || true &&
1382
+ COMMITS_AHEAD=$(sudo git rev-list --count origin/${targetBranch}..$HEAD_BRANCH 2>/dev/null || echo 0) &&
1383
+ sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
1384
+ echo "HEAD_BRANCH:$HEAD_BRANCH" &&
1385
+ echo "COMMITS_AHEAD:$COMMITS_AHEAD"
1116
1386
  `.replace(/\n\s+/g, ' ');
1117
1387
  const result = await executeShell(combinedCommand);
1118
1388
  // Check for command execution failure
@@ -1387,14 +1657,14 @@ class GithubOperationsService {
1387
1657
  try {
1388
1658
  logger.info('Fetching commit details', { repoName, commitHash });
1389
1659
  // Get commit information and file changes in a single SSH call
1390
- const combinedCommand = `
1391
- cd ${repoPath} &&
1392
- COMMIT_INFO=$(sudo git show --no-patch --pretty=format:"%H|%h|%an|%ae|%ad|%s" --date=iso ${commitHash}) &&
1393
- FILE_STATS=$(sudo git show --stat --pretty="" --numstat ${commitHash}) &&
1394
- echo "COMMIT_INFO:$COMMIT_INFO" &&
1395
- echo "FILE_STATS_START" &&
1396
- echo "$FILE_STATS" &&
1397
- echo "FILE_STATS_END"
1660
+ const combinedCommand = `
1661
+ cd ${repoPath} &&
1662
+ COMMIT_INFO=$(sudo git show --no-patch --pretty=format:"%H|%h|%an|%ae|%ad|%s" --date=iso ${commitHash}) &&
1663
+ FILE_STATS=$(sudo git show --stat --pretty="" --numstat ${commitHash}) &&
1664
+ echo "COMMIT_INFO:$COMMIT_INFO" &&
1665
+ echo "FILE_STATS_START" &&
1666
+ echo "$FILE_STATS" &&
1667
+ echo "FILE_STATS_END"
1398
1668
  `.replace(/\n\s+/g, ' ');
1399
1669
  const result = await executeShell(combinedCommand);
1400
1670
  if (!result.success) {
@@ -1688,144 +1958,6 @@ class GithubOperationsService {
1688
1958
  });
1689
1959
  });
1690
1960
  }
1691
- // ── New git operations ─────────────────────────────────────────────────────
1692
- /**
1693
- * Get all uncommitted git changes (metadata only, no diffs) for a repository.
1694
- * Accepts repoName (looked up under folderPath) or a full absolute repoPath.
1695
- */
1696
- async getGitChanges(repoName) {
1697
- const pathModule = await Promise.resolve().then(() => __importStar(require('path')));
1698
- const fs = await Promise.resolve().then(() => __importStar(require('fs')));
1699
- logger.info(`[getGitChanges] repoName: ${repoName}`);
1700
- try {
1701
- // Resolve the repo path: treat as absolute if it starts with '/', otherwise join with folderPath
1702
- let repoPath = pathModule.isAbsolute(repoName)
1703
- ? repoName
1704
- : pathModule.join(tool_server_1.folderPath, repoName);
1705
- if (!fs.existsSync(repoPath)) {
1706
- return { success: false, error: `Repository path does not exist: ${repoPath}` };
1707
- }
1708
- const gitDir = pathModule.join(repoPath, '.git');
1709
- if (!fs.existsSync(gitDir)) {
1710
- return { success: false, error: `Not a git repository: ${repoPath}` };
1711
- }
1712
- logger.info(`[getGitChanges] Fetching metadata for repo: ${repoPath}`);
1713
- const repoData = await getRepositoryChangesMetadataOnly(repoPath);
1714
- return {
1715
- success: true,
1716
- repositories: [repoData],
1717
- overallSummary: {
1718
- totalRepositories: 1,
1719
- totalFiles: repoData.summary.totalFiles,
1720
- totalInsertions: repoData.summary.totalInsertions,
1721
- totalDeletions: repoData.summary.totalDeletions,
1722
- totalChanges: repoData.summary.totalChanges
1723
- }
1724
- };
1725
- }
1726
- catch (error) {
1727
- logger.error('[getGitChanges] Error:', error);
1728
- return {
1729
- success: false,
1730
- error: error instanceof Error ? error.message : 'Unknown error occurred while getting git changes'
1731
- };
1732
- }
1733
- }
1734
- /**
1735
- * Get the unified diff for a single file in a repository.
1736
- * status codes: 'M' modified, 'MM' modified+staged, 'A' added/staged,
1737
- * '?' untracked, 'D' deleted, 'R' renamed
1738
- */
1739
- async getFileDiff(repoName, filePath, status) {
1740
- const pathModule = await Promise.resolve().then(() => __importStar(require('path')));
1741
- const fs = await Promise.resolve().then(() => __importStar(require('fs')));
1742
- logger.info(`[getFileDiff] repoName: ${repoName} | filePath: ${filePath} | status: ${status}`);
1743
- try {
1744
- let repoPath = pathModule.isAbsolute(repoName)
1745
- ? repoName
1746
- : pathModule.join(tool_server_1.folderPath, repoName);
1747
- if (!fs.existsSync(repoPath)) {
1748
- return { success: false, error: `Repository path does not exist: ${repoPath}`, filePath, repoPath };
1749
- }
1750
- const result = await getSingleFileDiff(repoPath, filePath, status);
1751
- logger.success(`[getFileDiff] Retrieved diff for ${filePath} (${(result.diff || '').length} chars)`);
1752
- return result;
1753
- }
1754
- catch (error) {
1755
- logger.error('[getFileDiff] Error:', error);
1756
- return {
1757
- success: false,
1758
- error: error instanceof Error ? error.message : 'Unknown error occurred while getting file diff',
1759
- filePath,
1760
- repoPath: repoName
1761
- };
1762
- }
1763
- }
1764
- /**
1765
- * Discard changes for a single file in a repository.
1766
- * - Untracked / created files: physically deleted from disk.
1767
- * - Modified / staged / deleted files: unstaged if needed, then restored from HEAD.
1768
- */
1769
- async discardFileChanges(repoName, filePath) {
1770
- const pathModule = await Promise.resolve().then(() => __importStar(require('path')));
1771
- const fs = await Promise.resolve().then(() => __importStar(require('fs')));
1772
- logger.info(`[discardFileChanges] repoName: ${repoName} | filePath: ${filePath}`);
1773
- try {
1774
- const repoPath = pathModule.isAbsolute(repoName)
1775
- ? repoName
1776
- : pathModule.join(tool_server_1.folderPath, repoName);
1777
- if (!fs.existsSync(repoPath)) {
1778
- return { success: false, error: `Repository path does not exist: ${repoPath}` };
1779
- }
1780
- const gitDir = pathModule.join(repoPath, '.git');
1781
- if (!fs.existsSync(gitDir)) {
1782
- return { success: false, error: `Not a git repository: ${repoPath}` };
1783
- }
1784
- const git = (0, simple_git_1.simpleGit)(repoPath);
1785
- const status = await git.status();
1786
- const isModified = status.modified.includes(filePath);
1787
- const isStaged = status.staged.includes(filePath);
1788
- const isDeleted = status.deleted.includes(filePath);
1789
- const isCreated = status.not_added.includes(filePath) || ((status.created || []).includes(filePath));
1790
- const fullFilePath = pathModule.join(repoPath, filePath);
1791
- const fileExistsOnDisk = fs.existsSync(fullFilePath);
1792
- logger.info(`[discardFileChanges] flags — modified:${isModified} staged:${isStaged} deleted:${isDeleted} created:${isCreated} existsOnDisk:${fileExistsOnDisk}`);
1793
- if (isCreated) {
1794
- // Untracked file — remove from disk
1795
- if (fileExistsOnDisk) {
1796
- await fs.promises.unlink(fullFilePath);
1797
- logger.success(`[discardFileChanges] Untracked file removed: ${filePath}`);
1798
- return { success: true, message: `Untracked file '${filePath}' has been removed.` };
1799
- }
1800
- else {
1801
- return { success: false, error: `File '${filePath}' does not exist on disk.` };
1802
- }
1803
- }
1804
- if (isModified || isStaged || isDeleted) {
1805
- try {
1806
- if (isStaged) {
1807
- await git.reset(['HEAD', filePath]);
1808
- logger.info(`[discardFileChanges] Unstaged: ${filePath}`);
1809
- }
1810
- await git.checkout(['HEAD', '--', filePath]);
1811
- logger.success(`[discardFileChanges] Restored from HEAD: ${filePath}`);
1812
- return { success: true, message: `Changes to '${filePath}' have been discarded.` };
1813
- }
1814
- catch (checkoutError) {
1815
- logger.error('[discardFileChanges] git checkout error:', checkoutError);
1816
- return { success: false, error: `Failed to discard changes: ${checkoutError.message}` };
1817
- }
1818
- }
1819
- return { success: false, error: `File '${filePath}' has no changes to discard.` };
1820
- }
1821
- catch (error) {
1822
- logger.error('[discardFileChanges] Error:', error);
1823
- return {
1824
- success: false,
1825
- error: error instanceof Error ? error.message : 'Unknown error occurred while discarding changes'
1826
- };
1827
- }
1828
- }
1829
1961
  }
1830
1962
  exports.GithubOperationsService = GithubOperationsService;
1831
1963
  //# sourceMappingURL=githubOperationsHanlder.js.map