phantomx-tool-client 1.1.6 → 2.1.0
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/Services/Logger.d.ts.map +1 -1
- package/dist/Services/Logger.js +12 -1
- package/dist/Services/Logger.js.map +1 -1
- package/dist/Services/MCPClient.d.ts +30 -0
- package/dist/Services/MCPClient.d.ts.map +1 -0
- package/dist/Services/MCPClient.js +172 -0
- package/dist/Services/MCPClient.js.map +1 -0
- package/dist/githubOperationsHanlder.d.ts +1 -1
- package/dist/githubOperationsHanlder.d.ts.map +1 -1
- package/dist/githubOperationsHanlder.js +462 -493
- package/dist/githubOperationsHanlder.js.map +1 -1
- package/dist/tool-server.d.ts +1 -2
- package/dist/tool-server.d.ts.map +1 -1
- package/dist/tool-server.js +53 -19
- package/dist/tool-server.js.map +1 -1
- package/dist/toolExecutionService.d.ts +1 -0
- package/dist/toolExecutionService.d.ts.map +1 -1
- package/dist/toolExecutionService.js +41 -34
- package/dist/toolExecutionService.js.map +1 -1
- package/package.json +3 -5
|
@@ -64,19 +64,6 @@ function executeShell(command) {
|
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
// Per-repo async mutex — serialises all git remote set-url operations so that
|
|
69
|
-
// concurrent tool calls never race on .git/config.lock for the same repo.
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
const repoLocks = new Map();
|
|
72
|
-
function withRepoLock(repoName, fn) {
|
|
73
|
-
var _a;
|
|
74
|
-
const prev = (_a = repoLocks.get(repoName)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
75
|
-
let releaseLock;
|
|
76
|
-
const lockAcquired = new Promise(res => { releaseLock = res; });
|
|
77
|
-
repoLocks.set(repoName, prev.then(() => lockAcquired));
|
|
78
|
-
return prev.then(() => fn()).finally(() => releaseLock());
|
|
79
|
-
}
|
|
80
67
|
// Use the official 'octokit' package import via require to avoid missing types for '@octokit/rest'
|
|
81
68
|
const { Octokit } = require('octokit');
|
|
82
69
|
const logger = (0, Logger_1.createLogger)('GithubOperationsService');
|
|
@@ -610,26 +597,25 @@ class GithubOperationsService {
|
|
|
610
597
|
const repoPath = `${tool_server_1.folderPath}/${repoName}`;
|
|
611
598
|
const installationToken = await fetchInstallationToken();
|
|
612
599
|
const gitOrgName = await getGithubOrganizationName();
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}); // end withRepoLock
|
|
600
|
+
try {
|
|
601
|
+
logger.info('Getting repository origin/default branch', { repoName });
|
|
602
|
+
// Set authenticated remote URL
|
|
603
|
+
await executeShell(`cd ${repoPath} && sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git`);
|
|
604
|
+
// Fetch remote HEAD to find the default branch
|
|
605
|
+
const result = await executeShell(`cd ${repoPath} && sudo git remote show origin | grep 'HEAD branch' | awk '{print $NF}'`);
|
|
606
|
+
// Restore unauthenticated remote URL
|
|
607
|
+
await executeShell(`cd ${repoPath} && sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git`);
|
|
608
|
+
if (!result.success || !result.output.trim()) {
|
|
609
|
+
throw new Error(`Failed to get origin branch for repo '${repoName}': ${result.error || result.output}`);
|
|
610
|
+
}
|
|
611
|
+
const originBranch = result.output.trim();
|
|
612
|
+
logger.success('Repository origin branch retrieved', { repoName, originBranch });
|
|
613
|
+
return {
|
|
614
|
+
repoName,
|
|
615
|
+
originBranch
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
finally { }
|
|
633
619
|
}
|
|
634
620
|
/**
|
|
635
621
|
* List branches for a repository
|
|
@@ -650,38 +636,40 @@ class GithubOperationsService {
|
|
|
650
636
|
const repoPath = `${tool_server_1.folderPath}/${repoName}`;
|
|
651
637
|
const installationToken = await fetchInstallationToken();
|
|
652
638
|
const gitOrgName = await getGithubOrganizationName();
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
throw new Error(`Failed to get current branch: ${result.error}`);
|
|
659
|
-
}
|
|
660
|
-
const currentBranch = result.output.trim();
|
|
661
|
-
logger.info('starting git pull operation', { repoName, currentBranch });
|
|
662
|
-
// Token-URL, fetch, pull, restore URL — all in one pipeline to prevent
|
|
663
|
-
// concurrent operations from racing on .git/config.lock.
|
|
664
|
-
result = await executeShell(`cd ${repoPath} && rm -f .git/config.lock && ` +
|
|
665
|
-
`git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git && ` +
|
|
666
|
-
`sudo git fetch origin && ` +
|
|
667
|
-
`sudo git pull --no-rebase origin ${currentBranch} ; ` +
|
|
668
|
-
`git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git`);
|
|
669
|
-
if (!result.success && result.code !== 128) {
|
|
670
|
-
throw new Error(`Failed to pull branch ${currentBranch}: ${result.error}`);
|
|
671
|
-
}
|
|
672
|
-
logger.success('Git pull completed successfully', { repoName, currentBranch });
|
|
673
|
-
// Get merge conflicts
|
|
674
|
-
const mergeConflicts = await this.getMergeConflicts(repoName);
|
|
675
|
-
return {
|
|
676
|
-
repoName,
|
|
677
|
-
branchName: currentBranch,
|
|
678
|
-
lastCommit: result.output.trim(),
|
|
679
|
-
mergeConflicts
|
|
680
|
-
};
|
|
639
|
+
try {
|
|
640
|
+
// Get current local branch name
|
|
641
|
+
let result = await executeShell(`cd ${repoPath} && sudo git rev-parse --abbrev-ref HEAD`);
|
|
642
|
+
if (!result.success) {
|
|
643
|
+
throw new Error(`Failed to get current branch: ${result.error}`);
|
|
681
644
|
}
|
|
682
|
-
|
|
645
|
+
const currentBranch = result.output.trim();
|
|
646
|
+
logger.info('starting git pull operation', { repoName, currentBranch });
|
|
647
|
+
// Set remote url with token
|
|
648
|
+
result = await executeShell(`cd ${repoPath} && sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git`);
|
|
649
|
+
if (!result.success) {
|
|
650
|
+
throw new Error(`Failed to set remote URL: ${result.error}`);
|
|
683
651
|
}
|
|
684
|
-
|
|
652
|
+
result = await executeShell(`cd ${repoPath} && sudo git fetch origin`);
|
|
653
|
+
if (!result.success) {
|
|
654
|
+
throw new Error(`Failed to fetch from remote: ${result.error}`);
|
|
655
|
+
}
|
|
656
|
+
result = await executeShell(`cd ${repoPath} && sudo git pull --no-rebase origin ${currentBranch}`);
|
|
657
|
+
if (!result.success && result.code !== 128) {
|
|
658
|
+
throw new Error(`Failed to pull branch ${currentBranch}: ${result.error}`);
|
|
659
|
+
}
|
|
660
|
+
await executeShell(`cd ${repoPath} && sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git`);
|
|
661
|
+
logger.success('Git pull completed successfully', { repoName, currentBranch });
|
|
662
|
+
// Get merge conflicts
|
|
663
|
+
const mergeConflicts = await this.getMergeConflicts(socket, repoName);
|
|
664
|
+
return {
|
|
665
|
+
repoName,
|
|
666
|
+
branchName: currentBranch,
|
|
667
|
+
lastCommit: result.output.trim(),
|
|
668
|
+
mergeConflicts
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
finally {
|
|
672
|
+
}
|
|
685
673
|
}
|
|
686
674
|
/**
|
|
687
675
|
* Push repository to remote
|
|
@@ -690,58 +678,55 @@ class GithubOperationsService {
|
|
|
690
678
|
const repoPath = `${tool_server_1.folderPath}/${repoName}`;
|
|
691
679
|
const installationToken = await fetchInstallationToken();
|
|
692
680
|
const gitOrgName = await getGithubOrganizationName();
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const combinedCommand = `
|
|
681
|
+
try {
|
|
682
|
+
logger.info('Starting git push operation', { repoName });
|
|
683
|
+
// Combine all commands into a single SSH call
|
|
684
|
+
const combinedCommand = `
|
|
698
685
|
cd ${repoPath} &&
|
|
699
|
-
rm -f .git/config.lock &&
|
|
700
686
|
BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
|
|
701
687
|
COMMITS_AHEAD=$(sudo git rev-list --count origin/$BRANCH..HEAD 2>/dev/null || echo 0) &&
|
|
702
688
|
if [ "$COMMITS_AHEAD" = "0" ]; then
|
|
703
689
|
echo "ERROR:NO_COMMITS";
|
|
704
690
|
exit 1;
|
|
705
691
|
fi &&
|
|
706
|
-
git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
|
|
692
|
+
sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
|
|
707
693
|
sudo git push origin $BRANCH &&
|
|
708
|
-
git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
|
|
694
|
+
sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
|
|
709
695
|
COMMIT_HASH=$(sudo git rev-parse HEAD) &&
|
|
710
696
|
echo "BRANCH:$BRANCH" &&
|
|
711
697
|
echo "COMMITS_AHEAD:$COMMITS_AHEAD" &&
|
|
712
698
|
echo "COMMIT_HASH:$COMMIT_HASH"
|
|
713
699
|
`.replace(/\n\s+/g, ' ');
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
719
|
-
throw new Error('Failed to push repository');
|
|
720
|
-
}
|
|
721
|
-
// Parse output
|
|
722
|
-
const output = result.output;
|
|
723
|
-
const branchMatch = output.match(/BRANCH:([^\n]+)/);
|
|
724
|
-
const commitsAheadMatch = output.match(/COMMITS_AHEAD:(\d+)/);
|
|
725
|
-
const commitHashMatch = output.match(/COMMIT_HASH:([a-f0-9]+)/);
|
|
726
|
-
const currentBranch = branchMatch ? branchMatch[1].trim() : 'unknown';
|
|
727
|
-
const commitsAhead = commitsAheadMatch ? parseInt(commitsAheadMatch[1]) : 0;
|
|
728
|
-
const commitHash = commitHashMatch ? commitHashMatch[1].trim() : 'unknown';
|
|
729
|
-
logger.success('Git push completed successfully', { repoName, currentBranch, commitHash, commitsAhead });
|
|
730
|
-
// Emit socket event to refresh repo on client (only when called by AI agent)
|
|
731
|
-
if (socket) {
|
|
732
|
-
socket.emit('refresh_repo', { repoName });
|
|
700
|
+
const result = await executeShell(combinedCommand);
|
|
701
|
+
if (!result.success) {
|
|
702
|
+
if (result.output && result.output.includes('ERROR:NO_COMMITS')) {
|
|
703
|
+
throw new Error('No commits to push');
|
|
733
704
|
}
|
|
734
|
-
|
|
735
|
-
repoName,
|
|
736
|
-
branchName: currentBranch,
|
|
737
|
-
commitHash,
|
|
738
|
-
commitsAhead,
|
|
739
|
-
pushedAt: new Date().toISOString()
|
|
740
|
-
};
|
|
705
|
+
throw new Error('Failed to push repository');
|
|
741
706
|
}
|
|
742
|
-
|
|
707
|
+
// Parse output
|
|
708
|
+
const output = result.output;
|
|
709
|
+
const branchMatch = output.match(/BRANCH:([^\n]+)/);
|
|
710
|
+
const commitsAheadMatch = output.match(/COMMITS_AHEAD:(\d+)/);
|
|
711
|
+
const commitHashMatch = output.match(/COMMIT_HASH:([a-f0-9]+)/);
|
|
712
|
+
const currentBranch = branchMatch ? branchMatch[1].trim() : 'unknown';
|
|
713
|
+
const commitsAhead = commitsAheadMatch ? parseInt(commitsAheadMatch[1]) : 0;
|
|
714
|
+
const commitHash = commitHashMatch ? commitHashMatch[1].trim() : 'unknown';
|
|
715
|
+
logger.success('Git push completed successfully', { repoName, currentBranch, commitHash, commitsAhead });
|
|
716
|
+
// Emit socket event to refresh repo on client (only when called by AI agent)
|
|
717
|
+
if (socket) {
|
|
718
|
+
socket.emit('refresh_repo', { repoName });
|
|
743
719
|
}
|
|
744
|
-
|
|
720
|
+
return {
|
|
721
|
+
repoName,
|
|
722
|
+
branchName: currentBranch,
|
|
723
|
+
commitHash,
|
|
724
|
+
commitsAhead,
|
|
725
|
+
pushedAt: new Date().toISOString()
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
finally {
|
|
729
|
+
}
|
|
745
730
|
}
|
|
746
731
|
/**
|
|
747
732
|
* Check repository status
|
|
@@ -750,21 +735,15 @@ class GithubOperationsService {
|
|
|
750
735
|
const repoPath = `${tool_server_1.folderPath}/${repoName}`;
|
|
751
736
|
const installationToken = await fetchInstallationToken();
|
|
752
737
|
const gitOrgName = await getGithubOrganizationName();
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
// Remove any stale .git/config.lock before touching remote config
|
|
758
|
-
// (a crashed/interrupted previous run can leave this lock behind).
|
|
759
|
-
// Also avoid sudo for git remote set-url so the lock file is never
|
|
760
|
-
// created as root-owned, which would block subsequent cleanup.
|
|
761
|
-
const combinedCommand = `
|
|
738
|
+
try {
|
|
739
|
+
logger.info('Checking repository status', { repoName });
|
|
740
|
+
// Combine all git commands into a single SSH call
|
|
741
|
+
const combinedCommand = `
|
|
762
742
|
cd ${repoPath} &&
|
|
763
|
-
rm -f .git/config.lock &&
|
|
764
743
|
BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
|
|
765
|
-
git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
|
|
744
|
+
sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
|
|
766
745
|
sudo git fetch origin &&
|
|
767
|
-
git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
|
|
746
|
+
sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
|
|
768
747
|
BEHIND=$(sudo git rev-list --count HEAD..origin/$BRANCH 2>/dev/null || echo 0) &&
|
|
769
748
|
AHEAD=$(sudo git rev-list --count origin/$BRANCH..HEAD 2>/dev/null || echo 0) &&
|
|
770
749
|
STATUS=$(sudo git status --porcelain) &&
|
|
@@ -777,55 +756,54 @@ class GithubOperationsService {
|
|
|
777
756
|
echo "CURRENT:$CURRENT_HASH" &&
|
|
778
757
|
echo "REMOTE:$REMOTE_HASH"
|
|
779
758
|
`.replace(/\n\s+/g, ' ');
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}
|
|
784
|
-
// Parse the output
|
|
785
|
-
const output = result.output;
|
|
786
|
-
const branchMatch = output.match(/BRANCH:([^\n]+)/);
|
|
787
|
-
const behindMatch = output.match(/BEHIND:(\d+)/);
|
|
788
|
-
const aheadMatch = output.match(/AHEAD:(\d+)/);
|
|
789
|
-
const statusMatch = output.match(/STATUS:([\s\S]*?)(?=CURRENT:|$)/);
|
|
790
|
-
const currentMatch = output.match(/CURRENT:([a-f0-9]+)/);
|
|
791
|
-
const remoteMatch = output.match(/REMOTE:([a-f0-9]+|unknown)/);
|
|
792
|
-
const currentBranch = branchMatch ? branchMatch[1].trim() : 'unknown';
|
|
793
|
-
const commitsBehind = behindMatch ? parseInt(behindMatch[1]) : 0;
|
|
794
|
-
const commitsAhead = aheadMatch ? parseInt(aheadMatch[1]) : 0;
|
|
795
|
-
const statusOutput = statusMatch ? statusMatch[1].trim() : '';
|
|
796
|
-
const hasUncommittedChanges = statusOutput.length > 0;
|
|
797
|
-
const currentCommitHash = currentMatch ? currentMatch[1].trim() : 'unknown';
|
|
798
|
-
const remoteCommitHash = remoteMatch ? remoteMatch[1].trim() : 'unknown';
|
|
799
|
-
const isUpToDate = commitsBehind === 0 && commitsAhead === 0;
|
|
800
|
-
const needsPull = commitsBehind > 0;
|
|
801
|
-
const needsPush = commitsAhead > 0;
|
|
802
|
-
logger.info('Repository status checked', {
|
|
803
|
-
repoName,
|
|
804
|
-
commitsBehind,
|
|
805
|
-
commitsAhead,
|
|
806
|
-
hasUncommittedChanges,
|
|
807
|
-
isUpToDate
|
|
808
|
-
});
|
|
809
|
-
// Check for merge conflicts
|
|
810
|
-
const mergeConflicts = await this.getMergeConflicts(repoName);
|
|
811
|
-
return {
|
|
812
|
-
repoName,
|
|
813
|
-
branchName: currentBranch,
|
|
814
|
-
currentCommitHash,
|
|
815
|
-
remoteCommitHash,
|
|
816
|
-
commitsBehind,
|
|
817
|
-
commitsAhead,
|
|
818
|
-
hasUncommittedChanges,
|
|
819
|
-
isUpToDate,
|
|
820
|
-
needsPull,
|
|
821
|
-
needsPush,
|
|
822
|
-
mergeConflicts,
|
|
823
|
-
status: isUpToDate ? 'up-to-date' : (needsPull && needsPush ? 'diverged' : needsPull ? 'behind' : 'ahead')
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
finally {
|
|
759
|
+
const result = await executeShell(combinedCommand);
|
|
760
|
+
if (!result.success) {
|
|
761
|
+
throw new Error('Failed to check repository status');
|
|
827
762
|
}
|
|
828
|
-
|
|
763
|
+
// Parse the output
|
|
764
|
+
const output = result.output;
|
|
765
|
+
const branchMatch = output.match(/BRANCH:([^\n]+)/);
|
|
766
|
+
const behindMatch = output.match(/BEHIND:(\d+)/);
|
|
767
|
+
const aheadMatch = output.match(/AHEAD:(\d+)/);
|
|
768
|
+
const statusMatch = output.match(/STATUS:([\s\S]*?)(?=CURRENT:|$)/);
|
|
769
|
+
const currentMatch = output.match(/CURRENT:([a-f0-9]+)/);
|
|
770
|
+
const remoteMatch = output.match(/REMOTE:([a-f0-9]+|unknown)/);
|
|
771
|
+
const currentBranch = branchMatch ? branchMatch[1].trim() : 'unknown';
|
|
772
|
+
const commitsBehind = behindMatch ? parseInt(behindMatch[1]) : 0;
|
|
773
|
+
const commitsAhead = aheadMatch ? parseInt(aheadMatch[1]) : 0;
|
|
774
|
+
const statusOutput = statusMatch ? statusMatch[1].trim() : '';
|
|
775
|
+
const hasUncommittedChanges = statusOutput.length > 0;
|
|
776
|
+
const currentCommitHash = currentMatch ? currentMatch[1].trim() : 'unknown';
|
|
777
|
+
const remoteCommitHash = remoteMatch ? remoteMatch[1].trim() : 'unknown';
|
|
778
|
+
const isUpToDate = commitsBehind === 0 && commitsAhead === 0;
|
|
779
|
+
const needsPull = commitsBehind > 0;
|
|
780
|
+
const needsPush = commitsAhead > 0;
|
|
781
|
+
logger.info('Repository status checked', {
|
|
782
|
+
repoName,
|
|
783
|
+
commitsBehind,
|
|
784
|
+
commitsAhead,
|
|
785
|
+
hasUncommittedChanges,
|
|
786
|
+
isUpToDate
|
|
787
|
+
});
|
|
788
|
+
// Check for merge conflicts
|
|
789
|
+
const mergeConflicts = await this.getMergeConflicts(socket, repoName);
|
|
790
|
+
return {
|
|
791
|
+
repoName,
|
|
792
|
+
branchName: currentBranch,
|
|
793
|
+
currentCommitHash,
|
|
794
|
+
remoteCommitHash,
|
|
795
|
+
commitsBehind,
|
|
796
|
+
commitsAhead,
|
|
797
|
+
hasUncommittedChanges,
|
|
798
|
+
isUpToDate,
|
|
799
|
+
needsPull,
|
|
800
|
+
needsPush,
|
|
801
|
+
mergeConflicts,
|
|
802
|
+
status: isUpToDate ? 'up-to-date' : (needsPull && needsPush ? 'diverged' : needsPull ? 'behind' : 'ahead')
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
finally {
|
|
806
|
+
}
|
|
829
807
|
}
|
|
830
808
|
/**
|
|
831
809
|
* Get repository history
|
|
@@ -1071,186 +1049,183 @@ class GithubOperationsService {
|
|
|
1071
1049
|
const repoPath = `${tool_server_1.folderPath}/${repoName}`;
|
|
1072
1050
|
const installationToken = await fetchInstallationToken();
|
|
1073
1051
|
const gitOrgName = await getGithubOrganizationName();
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
const combinedCommand = `
|
|
1052
|
+
try {
|
|
1053
|
+
logger.info('Creating pull request', { repoName, targetBranch });
|
|
1054
|
+
// Combine commands into a single SSH call, with remote URL set/unset
|
|
1055
|
+
const combinedCommand = `
|
|
1079
1056
|
cd ${repoPath} &&
|
|
1080
|
-
|
|
1081
|
-
git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
|
|
1057
|
+
sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
|
|
1082
1058
|
HEAD_BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
|
|
1083
1059
|
sudo git fetch origin ${targetBranch} 2>/dev/null &&
|
|
1084
1060
|
COMMITS_AHEAD=$(sudo git rev-list --count origin/${targetBranch}..$HEAD_BRANCH 2>/dev/null || echo 0) &&
|
|
1085
1061
|
if [ "$COMMITS_AHEAD" = "0" ]; then
|
|
1086
1062
|
echo "ERROR:NO_COMMITS:$HEAD_BRANCH";
|
|
1087
|
-
git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git;
|
|
1063
|
+
sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git;
|
|
1088
1064
|
exit 1;
|
|
1089
1065
|
fi &&
|
|
1090
1066
|
echo "HEAD_BRANCH:$HEAD_BRANCH" &&
|
|
1091
1067
|
echo "COMMITS_AHEAD:$COMMITS_AHEAD" &&
|
|
1092
|
-
git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git
|
|
1068
|
+
sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git
|
|
1093
1069
|
`.replace(/\n\s+/g, ' ');
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1070
|
+
let result = await executeShell(combinedCommand);
|
|
1071
|
+
const output = result.output || '';
|
|
1072
|
+
const headBranchMatch = output.match(/HEAD_BRANCH:([^\n]+)/);
|
|
1073
|
+
const commitsAheadMatch = output.match(/COMMITS_AHEAD:(\d+)/);
|
|
1074
|
+
const errorMatch = output.match(/ERROR:NO_COMMITS:([^\n]+)/);
|
|
1075
|
+
let headBranch = headBranchMatch ? headBranchMatch[1].trim() : 'unknown';
|
|
1076
|
+
const commitsAhead = commitsAheadMatch ? parseInt(commitsAheadMatch[1]) : 0;
|
|
1077
|
+
if (!result.success && errorMatch) {
|
|
1078
|
+
headBranch = errorMatch[1].trim();
|
|
1079
|
+
}
|
|
1080
|
+
// using 'octokit' package (already installed, @octokit/rest is not installed)
|
|
1081
|
+
const { Octokit } = require('octokit');
|
|
1082
|
+
const octokit = new Octokit({
|
|
1083
|
+
auth: installationToken
|
|
1084
|
+
});
|
|
1085
|
+
// Always check for existing PR first even if there are no new commits
|
|
1086
|
+
try {
|
|
1087
|
+
const existingPRs = await octokit.rest.pulls.list({
|
|
1088
|
+
owner: gitOrgName,
|
|
1089
|
+
repo: repoName,
|
|
1090
|
+
head: `${gitOrgName}:${headBranch}`,
|
|
1091
|
+
base: targetBranch,
|
|
1092
|
+
state: 'open'
|
|
1108
1093
|
});
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
owner: gitOrgName,
|
|
1113
|
-
repo: repoName,
|
|
1114
|
-
head: `${gitOrgName}:${headBranch}`,
|
|
1115
|
-
base: targetBranch,
|
|
1116
|
-
state: 'open'
|
|
1117
|
-
});
|
|
1118
|
-
if (existingPRs.data.length > 0) {
|
|
1119
|
-
const existingPR = existingPRs.data[0];
|
|
1120
|
-
logger.info('Pull request already exists', {
|
|
1121
|
-
repoName,
|
|
1122
|
-
prNumber: existingPR.number,
|
|
1123
|
-
prUrl: existingPR.html_url
|
|
1124
|
-
});
|
|
1125
|
-
const resultData = {
|
|
1126
|
-
repoName,
|
|
1127
|
-
headBranch,
|
|
1128
|
-
targetBranch,
|
|
1129
|
-
prNumber: existingPR.number,
|
|
1130
|
-
prUrl: existingPR.html_url,
|
|
1131
|
-
prTitle: existingPR.title,
|
|
1132
|
-
commitsAhead,
|
|
1133
|
-
createdAt: existingPR.created_at,
|
|
1134
|
-
alreadyExists: true
|
|
1135
|
-
};
|
|
1136
|
-
// Emit socket event to refresh PR status on client
|
|
1137
|
-
if (socket) {
|
|
1138
|
-
socket.emit('refresh_pr_status', { repoName, prNumber: existingPR.number });
|
|
1139
|
-
}
|
|
1140
|
-
// the socket server our main server will automatically update the pr metadata in db when i recvs the result of the given tool.
|
|
1141
|
-
// this.updatePRMetadata(userId, resultData);
|
|
1142
|
-
return resultData;
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
catch (fetchError) {
|
|
1146
|
-
logger.error('Failed to check for existing PR', fetchError);
|
|
1147
|
-
}
|
|
1148
|
-
// No existing PR found - check if we have commits to create one
|
|
1149
|
-
if (!result.success) {
|
|
1150
|
-
if (errorMatch) {
|
|
1151
|
-
throw new Error(`No commits to create PR. Branch '${headBranch}' is not ahead of '${targetBranch}'`);
|
|
1152
|
-
}
|
|
1153
|
-
throw new Error('Failed to prepare pull request');
|
|
1154
|
-
}
|
|
1155
|
-
// Create new PR
|
|
1156
|
-
const prTitle = title || `Merge ${headBranch} into ${targetBranch}`;
|
|
1157
|
-
const prBody = body || `This pull request merges changes from ${headBranch} into ${targetBranch}.\n\nCreated by AI-Playgrounds`;
|
|
1158
|
-
try {
|
|
1159
|
-
const prResponse = await octokit.rest.pulls.create({
|
|
1160
|
-
owner: gitOrgName,
|
|
1161
|
-
repo: repoName,
|
|
1162
|
-
title: prTitle,
|
|
1163
|
-
body: prBody,
|
|
1164
|
-
head: headBranch,
|
|
1165
|
-
base: targetBranch
|
|
1166
|
-
});
|
|
1167
|
-
const prUrl = prResponse.data.html_url;
|
|
1168
|
-
const prNumber = prResponse.data.number;
|
|
1169
|
-
logger.success('Pull request created successfully', {
|
|
1094
|
+
if (existingPRs.data.length > 0) {
|
|
1095
|
+
const existingPR = existingPRs.data[0];
|
|
1096
|
+
logger.info('Pull request already exists', {
|
|
1170
1097
|
repoName,
|
|
1171
|
-
prNumber,
|
|
1172
|
-
prUrl
|
|
1098
|
+
prNumber: existingPR.number,
|
|
1099
|
+
prUrl: existingPR.html_url
|
|
1173
1100
|
});
|
|
1174
|
-
// taskInfo.status = TaskStatus.Completed;
|
|
1175
|
-
// taskInfo.nonRunningSince = new Date();
|
|
1176
|
-
// //updating the task status in the db
|
|
1177
|
-
// let dbService = await getDBService();
|
|
1178
|
-
// let taskHanlder = dbService.getRepository<Task>(ds.UserInfo.get(userId as any)?.dbName, CollectionNames.TASKS);
|
|
1179
|
-
// taskHanlder.updateOne({
|
|
1180
|
-
// taskId: taskId
|
|
1181
|
-
// },
|
|
1182
|
-
// {
|
|
1183
|
-
// "$set": {
|
|
1184
|
-
// status: TaskStatus.Completed
|
|
1185
|
-
// }
|
|
1186
|
-
// });
|
|
1187
1101
|
const resultData = {
|
|
1188
1102
|
repoName,
|
|
1189
1103
|
headBranch,
|
|
1190
1104
|
targetBranch,
|
|
1191
|
-
prNumber,
|
|
1192
|
-
prUrl,
|
|
1193
|
-
prTitle,
|
|
1105
|
+
prNumber: existingPR.number,
|
|
1106
|
+
prUrl: existingPR.html_url,
|
|
1107
|
+
prTitle: existingPR.title,
|
|
1194
1108
|
commitsAhead,
|
|
1195
|
-
createdAt:
|
|
1109
|
+
createdAt: existingPR.created_at,
|
|
1110
|
+
alreadyExists: true
|
|
1196
1111
|
};
|
|
1197
|
-
//
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
//
|
|
1202
|
-
//
|
|
1112
|
+
// Emit socket event to refresh PR status on client
|
|
1113
|
+
if (socket) {
|
|
1114
|
+
socket.emit('refresh_pr_status', { repoName, prNumber: existingPR.number });
|
|
1115
|
+
}
|
|
1116
|
+
// the socket server our main server will automatically update the pr metadata in db when i recvs the result of the given tool.
|
|
1117
|
+
// this.updatePRMetadata(userId, resultData);
|
|
1203
1118
|
return resultData;
|
|
1204
1119
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1120
|
+
}
|
|
1121
|
+
catch (fetchError) {
|
|
1122
|
+
logger.error('Failed to check for existing PR', fetchError);
|
|
1123
|
+
}
|
|
1124
|
+
// No existing PR found - check if we have commits to create one
|
|
1125
|
+
if (!result.success) {
|
|
1126
|
+
if (errorMatch) {
|
|
1127
|
+
throw new Error(`No commits to create PR. Branch '${headBranch}' is not ahead of '${targetBranch}'`);
|
|
1128
|
+
}
|
|
1129
|
+
throw new Error('Failed to prepare pull request');
|
|
1130
|
+
}
|
|
1131
|
+
// Create new PR
|
|
1132
|
+
const prTitle = title || `Merge ${headBranch} into ${targetBranch}`;
|
|
1133
|
+
const prBody = body || `This pull request merges changes from ${headBranch} into ${targetBranch}.\n\nCreated by AI-Playgrounds`;
|
|
1134
|
+
try {
|
|
1135
|
+
const prResponse = await octokit.rest.pulls.create({
|
|
1136
|
+
owner: gitOrgName,
|
|
1137
|
+
repo: repoName,
|
|
1138
|
+
title: prTitle,
|
|
1139
|
+
body: prBody,
|
|
1140
|
+
head: headBranch,
|
|
1141
|
+
base: targetBranch
|
|
1142
|
+
});
|
|
1143
|
+
const prUrl = prResponse.data.html_url;
|
|
1144
|
+
const prNumber = prResponse.data.number;
|
|
1145
|
+
logger.success('Pull request created successfully', {
|
|
1146
|
+
repoName,
|
|
1147
|
+
prNumber,
|
|
1148
|
+
prUrl
|
|
1149
|
+
});
|
|
1150
|
+
// taskInfo.status = TaskStatus.Completed;
|
|
1151
|
+
// taskInfo.nonRunningSince = new Date();
|
|
1152
|
+
// //updating the task status in the db
|
|
1153
|
+
// let dbService = await getDBService();
|
|
1154
|
+
// let taskHanlder = dbService.getRepository<Task>(ds.UserInfo.get(userId as any)?.dbName, CollectionNames.TASKS);
|
|
1155
|
+
// taskHanlder.updateOne({
|
|
1156
|
+
// taskId: taskId
|
|
1157
|
+
// },
|
|
1158
|
+
// {
|
|
1159
|
+
// "$set": {
|
|
1160
|
+
// status: TaskStatus.Completed
|
|
1161
|
+
// }
|
|
1162
|
+
// });
|
|
1163
|
+
const resultData = {
|
|
1164
|
+
repoName,
|
|
1165
|
+
headBranch,
|
|
1166
|
+
targetBranch,
|
|
1167
|
+
prNumber,
|
|
1168
|
+
prUrl,
|
|
1169
|
+
prTitle,
|
|
1170
|
+
commitsAhead,
|
|
1171
|
+
createdAt: new Date().toISOString()
|
|
1172
|
+
};
|
|
1173
|
+
// // Emit socket event to refresh PR status on client
|
|
1174
|
+
// if (socket) {
|
|
1175
|
+
// socket.emit('refresh_pr_status', { repoName, prNumber });
|
|
1176
|
+
// }
|
|
1177
|
+
// // Update database asynchronously (non-blocking)
|
|
1178
|
+
// this.updatePRMetadata(userId, resultData);
|
|
1179
|
+
return resultData;
|
|
1180
|
+
}
|
|
1181
|
+
catch (prError) {
|
|
1182
|
+
// Check if PR already exists
|
|
1183
|
+
if (prError.status === 422) {
|
|
1184
|
+
// Fetch existing PR
|
|
1185
|
+
try {
|
|
1186
|
+
const existingPRs = await octokit.rest.pulls.list({
|
|
1187
|
+
owner: gitOrgName,
|
|
1188
|
+
repo: repoName,
|
|
1189
|
+
head: `${gitOrgName}:${headBranch}`,
|
|
1190
|
+
base: targetBranch,
|
|
1191
|
+
state: 'open'
|
|
1192
|
+
});
|
|
1193
|
+
if (existingPRs.data.length > 0) {
|
|
1194
|
+
const existingPR = existingPRs.data[0];
|
|
1195
|
+
logger.info('Pull request already exists', {
|
|
1196
|
+
repoName,
|
|
1197
|
+
prNumber: existingPR.number,
|
|
1198
|
+
prUrl: existingPR.html_url
|
|
1216
1199
|
});
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
commitsAhead,
|
|
1232
|
-
createdAt: existingPR.created_at,
|
|
1233
|
-
alreadyExists: true
|
|
1234
|
-
};
|
|
1235
|
-
// Emit socket event to refresh PR status on client
|
|
1236
|
-
if (socket) {
|
|
1237
|
-
socket.emit('refresh_pr_status', { repoName, prNumber: existingPR.number });
|
|
1238
|
-
}
|
|
1239
|
-
// Update database asynchronously (non-blocking)
|
|
1240
|
-
// this.updatePRMetadata(userId, resultData);
|
|
1241
|
-
return resultData;
|
|
1200
|
+
const resultData = {
|
|
1201
|
+
repoName,
|
|
1202
|
+
headBranch,
|
|
1203
|
+
targetBranch,
|
|
1204
|
+
prNumber: existingPR.number,
|
|
1205
|
+
prUrl: existingPR.html_url,
|
|
1206
|
+
prTitle: existingPR.title,
|
|
1207
|
+
commitsAhead,
|
|
1208
|
+
createdAt: existingPR.created_at,
|
|
1209
|
+
alreadyExists: true
|
|
1210
|
+
};
|
|
1211
|
+
// Emit socket event to refresh PR status on client
|
|
1212
|
+
if (socket) {
|
|
1213
|
+
socket.emit('refresh_pr_status', { repoName, prNumber: existingPR.number });
|
|
1242
1214
|
}
|
|
1215
|
+
// Update database asynchronously (non-blocking)
|
|
1216
|
+
// this.updatePRMetadata(userId, resultData);
|
|
1217
|
+
return resultData;
|
|
1243
1218
|
}
|
|
1244
|
-
catch (fetchError) {
|
|
1245
|
-
logger.error('Failed to fetch existing PR', fetchError);
|
|
1246
|
-
}
|
|
1247
1219
|
}
|
|
1248
|
-
|
|
1220
|
+
catch (fetchError) {
|
|
1221
|
+
logger.error('Failed to fetch existing PR', fetchError);
|
|
1222
|
+
}
|
|
1249
1223
|
}
|
|
1224
|
+
throw prError;
|
|
1250
1225
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
}
|
|
1226
|
+
}
|
|
1227
|
+
finally {
|
|
1228
|
+
}
|
|
1254
1229
|
}
|
|
1255
1230
|
/**
|
|
1256
1231
|
* Check if pull request exists
|
|
@@ -1259,37 +1234,35 @@ class GithubOperationsService {
|
|
|
1259
1234
|
const repoPath = `${tool_server_1.folderPath}/${repoName}`;
|
|
1260
1235
|
const installationToken = await fetchInstallationToken();
|
|
1261
1236
|
const gitOrgName = await getGithubOrganizationName();
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
const safeSourceRef = this.escapeSingleQuotedShell(resolvedSource.resolvedRef);
|
|
1292
|
-
const prepareBranchCommand = `
|
|
1237
|
+
try {
|
|
1238
|
+
const trimmedSourceBranch = (sourceBranch || '').trim();
|
|
1239
|
+
const trimmedTargetBranch = (targetBranch || '').trim();
|
|
1240
|
+
if (!trimmedSourceBranch || !trimmedTargetBranch) {
|
|
1241
|
+
throw new Error('Both sourceBranch and targetBranch are required');
|
|
1242
|
+
}
|
|
1243
|
+
if (trimmedSourceBranch === trimmedTargetBranch) {
|
|
1244
|
+
throw new Error('sourceBranch and targetBranch cannot be the same');
|
|
1245
|
+
}
|
|
1246
|
+
logger.info('Starting branch merge operation', {
|
|
1247
|
+
repoName,
|
|
1248
|
+
sourceBranch: trimmedSourceBranch,
|
|
1249
|
+
targetBranch: trimmedTargetBranch,
|
|
1250
|
+
pushToRemote
|
|
1251
|
+
});
|
|
1252
|
+
let result = await executeShell(`cd ${repoPath} && sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git`);
|
|
1253
|
+
if (!result.success) {
|
|
1254
|
+
throw new Error(`Failed to set authenticated remote URL: ${result.error || result.output || 'Unknown error'}`);
|
|
1255
|
+
}
|
|
1256
|
+
const fetchResult = await executeShell(`cd ${repoPath} && sudo git fetch origin --prune`);
|
|
1257
|
+
if (!fetchResult.success) {
|
|
1258
|
+
throw new Error(`Failed to fetch remote branches: ${fetchResult.error || fetchResult.output || 'Unknown error'}`);
|
|
1259
|
+
}
|
|
1260
|
+
const resolvedSource = await this.resolveBranchReference(repoPath, repoName, trimmedSourceBranch);
|
|
1261
|
+
const resolvedTarget = await this.resolveBranchReference(repoPath, repoName, trimmedTargetBranch);
|
|
1262
|
+
const targetLocalBranch = resolvedTarget.displayBranch;
|
|
1263
|
+
const safeTargetLocalBranch = this.escapeSingleQuotedShell(targetLocalBranch);
|
|
1264
|
+
const safeSourceRef = this.escapeSingleQuotedShell(resolvedSource.resolvedRef);
|
|
1265
|
+
const prepareBranchCommand = `
|
|
1293
1266
|
cd ${repoPath} &&
|
|
1294
1267
|
TARGET_LOCAL='${safeTargetLocalBranch}' &&
|
|
1295
1268
|
if sudo git show-ref --verify --quiet "refs/heads/$TARGET_LOCAL"; then
|
|
@@ -1304,178 +1277,174 @@ class GithubOperationsService {
|
|
|
1304
1277
|
sudo git pull --ff-only origin "$TARGET_LOCAL" 2>/dev/null || true;
|
|
1305
1278
|
fi
|
|
1306
1279
|
`.replace(/\n\s+/g, ' ');
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
}
|
|
1339
|
-
throw new Error(`Failed to merge branches: ${mergeResult.error || mergeResult.output || 'Unknown error'}`);
|
|
1340
|
-
}
|
|
1341
|
-
const summaryResult = await executeShell(`cd ${repoPath} && COMMIT_HASH=$(sudo git rev-parse HEAD) && CURRENT_BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) && echo "COMMIT_HASH:$COMMIT_HASH" && echo "CURRENT_BRANCH:$CURRENT_BRANCH"`);
|
|
1342
|
-
if (!summaryResult.success) {
|
|
1343
|
-
throw new Error(`Merge succeeded but failed to fetch merge summary: ${summaryResult.error || summaryResult.output || 'Unknown error'}`);
|
|
1280
|
+
result = await executeShell(prepareBranchCommand);
|
|
1281
|
+
if (!result.success) {
|
|
1282
|
+
throw new Error(`Failed to prepare target branch for merge: ${result.error || result.output || 'Unknown error'}`);
|
|
1283
|
+
}
|
|
1284
|
+
const mergeCommand = `cd ${repoPath} && SOURCE_REF='${safeSourceRef}' && sudo git merge --no-ff --no-edit "$SOURCE_REF"`;
|
|
1285
|
+
const mergeResult = await executeShell(mergeCommand);
|
|
1286
|
+
if (!mergeResult.success) {
|
|
1287
|
+
const conflictResult = await executeShell(`cd ${repoPath} && sudo git diff --name-only --diff-filter=U`);
|
|
1288
|
+
const conflictFiles = (conflictResult.output || '')
|
|
1289
|
+
.split('\n')
|
|
1290
|
+
.map((line) => line.trim())
|
|
1291
|
+
.filter((line) => line.length > 0);
|
|
1292
|
+
if (conflictFiles.length > 0) {
|
|
1293
|
+
logger.warn('Merge completed with conflicts', {
|
|
1294
|
+
repoName,
|
|
1295
|
+
sourceBranch: resolvedSource.displayBranch,
|
|
1296
|
+
targetBranch: targetLocalBranch,
|
|
1297
|
+
conflictFiles
|
|
1298
|
+
});
|
|
1299
|
+
return {
|
|
1300
|
+
repoName,
|
|
1301
|
+
sourceBranch: resolvedSource.displayBranch,
|
|
1302
|
+
targetBranch: targetLocalBranch,
|
|
1303
|
+
merged: false,
|
|
1304
|
+
hasConflicts: true,
|
|
1305
|
+
conflictFiles,
|
|
1306
|
+
currentBranch: targetLocalBranch,
|
|
1307
|
+
pushedToRemote: false,
|
|
1308
|
+
mergedAt: new Date().toISOString(),
|
|
1309
|
+
message: `Merge has conflicts. Resolve conflicts in target branch '${targetLocalBranch}'.`
|
|
1310
|
+
};
|
|
1344
1311
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1312
|
+
throw new Error(`Failed to merge branches: ${mergeResult.error || mergeResult.output || 'Unknown error'}`);
|
|
1313
|
+
}
|
|
1314
|
+
const summaryResult = await executeShell(`cd ${repoPath} && COMMIT_HASH=$(sudo git rev-parse HEAD) && CURRENT_BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) && echo "COMMIT_HASH:$COMMIT_HASH" && echo "CURRENT_BRANCH:$CURRENT_BRANCH"`);
|
|
1315
|
+
if (!summaryResult.success) {
|
|
1316
|
+
throw new Error(`Merge succeeded but failed to fetch merge summary: ${summaryResult.error || summaryResult.output || 'Unknown error'}`);
|
|
1317
|
+
}
|
|
1318
|
+
const summaryOutput = summaryResult.output || '';
|
|
1319
|
+
const commitHashMatch = summaryOutput.match(/COMMIT_HASH:([^\n]+)/);
|
|
1320
|
+
const currentBranchMatch = summaryOutput.match(/CURRENT_BRANCH:([^\n]+)/);
|
|
1321
|
+
const commitHash = commitHashMatch ? commitHashMatch[1].trim() : '';
|
|
1322
|
+
const currentBranch = currentBranchMatch ? currentBranchMatch[1].trim() : targetLocalBranch;
|
|
1323
|
+
let pushedToRemote = false;
|
|
1324
|
+
if (pushToRemote) {
|
|
1325
|
+
const pushResult = await executeShell(`cd ${repoPath} && sudo git push origin '${safeTargetLocalBranch}'`);
|
|
1326
|
+
if (!pushResult.success) {
|
|
1327
|
+
throw new Error(`Merge succeeded but push failed: ${pushResult.error || pushResult.output || 'Unknown error'}`);
|
|
1357
1328
|
}
|
|
1358
|
-
|
|
1329
|
+
pushedToRemote = true;
|
|
1330
|
+
}
|
|
1331
|
+
logger.success('Branch merge completed successfully', {
|
|
1332
|
+
repoName,
|
|
1333
|
+
sourceBranch: resolvedSource.displayBranch,
|
|
1334
|
+
targetBranch: targetLocalBranch,
|
|
1335
|
+
commitHash,
|
|
1336
|
+
pushedToRemote
|
|
1337
|
+
});
|
|
1338
|
+
if (socket) {
|
|
1339
|
+
socket.emit('refresh_repo', { repoName });
|
|
1340
|
+
}
|
|
1341
|
+
return {
|
|
1342
|
+
repoName,
|
|
1343
|
+
sourceBranch: resolvedSource.displayBranch,
|
|
1344
|
+
targetBranch: targetLocalBranch,
|
|
1345
|
+
merged: true,
|
|
1346
|
+
hasConflicts: false,
|
|
1347
|
+
conflictFiles: [],
|
|
1348
|
+
currentBranch,
|
|
1349
|
+
commitHash,
|
|
1350
|
+
pushedToRemote,
|
|
1351
|
+
mergedAt: new Date().toISOString(),
|
|
1352
|
+
message: pushedToRemote
|
|
1353
|
+
? `Merged '${resolvedSource.displayBranch}' into '${targetLocalBranch}' and pushed to remote.`
|
|
1354
|
+
: `Merged '${resolvedSource.displayBranch}' into '${targetLocalBranch}' locally.`
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
finally {
|
|
1358
|
+
try {
|
|
1359
|
+
await executeShell(`cd ${repoPath} && sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git`);
|
|
1360
|
+
}
|
|
1361
|
+
catch (restoreError) {
|
|
1362
|
+
logger.error('Failed to restore unauthenticated remote URL after merge operation', {
|
|
1359
1363
|
repoName,
|
|
1360
|
-
|
|
1361
|
-
targetBranch: targetLocalBranch,
|
|
1362
|
-
commitHash,
|
|
1363
|
-
pushedToRemote
|
|
1364
|
+
error: restoreError instanceof Error ? restoreError.message : String(restoreError)
|
|
1364
1365
|
});
|
|
1365
|
-
if (socket) {
|
|
1366
|
-
socket.emit('refresh_repo', { repoName });
|
|
1367
|
-
}
|
|
1368
|
-
return {
|
|
1369
|
-
repoName,
|
|
1370
|
-
sourceBranch: resolvedSource.displayBranch,
|
|
1371
|
-
targetBranch: targetLocalBranch,
|
|
1372
|
-
merged: true,
|
|
1373
|
-
hasConflicts: false,
|
|
1374
|
-
conflictFiles: [],
|
|
1375
|
-
currentBranch,
|
|
1376
|
-
commitHash,
|
|
1377
|
-
pushedToRemote,
|
|
1378
|
-
mergedAt: new Date().toISOString(),
|
|
1379
|
-
message: pushedToRemote
|
|
1380
|
-
? `Merged '${resolvedSource.displayBranch}' into '${targetLocalBranch}' and pushed to remote.`
|
|
1381
|
-
: `Merged '${resolvedSource.displayBranch}' into '${targetLocalBranch}' locally.`
|
|
1382
|
-
};
|
|
1383
1366
|
}
|
|
1384
|
-
|
|
1385
|
-
try {
|
|
1386
|
-
await executeShell(`cd ${repoPath} && git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git`);
|
|
1387
|
-
}
|
|
1388
|
-
catch (restoreError) {
|
|
1389
|
-
logger.error('Failed to restore unauthenticated remote URL after merge operation', {
|
|
1390
|
-
repoName,
|
|
1391
|
-
error: restoreError instanceof Error ? restoreError.message : String(restoreError)
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
}); // end withRepoLock
|
|
1367
|
+
}
|
|
1396
1368
|
}
|
|
1397
1369
|
async checkPullRequestExists(repoName, targetBranch) {
|
|
1398
1370
|
const repoPath = `${tool_server_1.folderPath}/${repoName}`;
|
|
1399
1371
|
const installationToken = await fetchInstallationToken();
|
|
1400
1372
|
const gitOrgName = await getGithubOrganizationName();
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
const combinedCommand = `
|
|
1373
|
+
try {
|
|
1374
|
+
logger.info('Checking if PR exists', { repoName, targetBranch });
|
|
1375
|
+
// Get current branch and commits ahead
|
|
1376
|
+
const combinedCommand = `
|
|
1406
1377
|
cd ${repoPath} &&
|
|
1407
|
-
|
|
1408
|
-
git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
|
|
1378
|
+
sudo git remote set-url origin https://x-access-token:${installationToken}@github.com/${gitOrgName}/${repoName}.git &&
|
|
1409
1379
|
HEAD_BRANCH=$(sudo git rev-parse --abbrev-ref HEAD) &&
|
|
1410
1380
|
sudo git fetch origin --prune 2>/dev/null || true &&
|
|
1411
1381
|
sudo git fetch origin ${targetBranch} 2>/dev/null || true &&
|
|
1412
1382
|
COMMITS_AHEAD=$(sudo git rev-list --count origin/${targetBranch}..$HEAD_BRANCH 2>/dev/null || echo 0) &&
|
|
1413
|
-
git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
|
|
1383
|
+
sudo git remote set-url origin https://github.com/${gitOrgName}/${repoName}.git &&
|
|
1414
1384
|
echo "HEAD_BRANCH:$HEAD_BRANCH" &&
|
|
1415
1385
|
echo "COMMITS_AHEAD:$COMMITS_AHEAD"
|
|
1416
1386
|
`.replace(/\n\s+/g, ' ');
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
});
|
|
1426
|
-
throw new Error(`Failed to check PR status: ${result.error}`);
|
|
1427
|
-
}
|
|
1428
|
-
const output = result.output || '';
|
|
1429
|
-
const headBranchMatch = output.match(/HEAD_BRANCH:([^\n]+)/);
|
|
1430
|
-
const commitsAheadMatch = output.match(/COMMITS_AHEAD:(\d+)/);
|
|
1431
|
-
const headBranch = headBranchMatch ? headBranchMatch[1].trim() : 'unknown';
|
|
1432
|
-
const commitsAhead = commitsAheadMatch ? parseInt(commitsAheadMatch[1]) : 0;
|
|
1433
|
-
logger.info('Branch info retrieved', { headBranch, commitsAhead });
|
|
1434
|
-
// Check for existing PR - using 'octokit' package (already installed)
|
|
1435
|
-
const { Octokit } = require('octokit');
|
|
1436
|
-
const octokit = new Octokit({
|
|
1437
|
-
auth: installationToken
|
|
1387
|
+
const result = await executeShell(combinedCommand);
|
|
1388
|
+
// Check for command execution failure
|
|
1389
|
+
if (!result.success) {
|
|
1390
|
+
logger.error('Failed to check PR status', {
|
|
1391
|
+
repoName,
|
|
1392
|
+
error: result.error,
|
|
1393
|
+
code: result.code,
|
|
1394
|
+
output: result.output
|
|
1438
1395
|
});
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1396
|
+
throw new Error(`Failed to check PR status: ${result.error}`);
|
|
1397
|
+
}
|
|
1398
|
+
const output = result.output || '';
|
|
1399
|
+
const headBranchMatch = output.match(/HEAD_BRANCH:([^\n]+)/);
|
|
1400
|
+
const commitsAheadMatch = output.match(/COMMITS_AHEAD:(\d+)/);
|
|
1401
|
+
const headBranch = headBranchMatch ? headBranchMatch[1].trim() : 'unknown';
|
|
1402
|
+
const commitsAhead = commitsAheadMatch ? parseInt(commitsAheadMatch[1]) : 0;
|
|
1403
|
+
logger.info('Branch info retrieved', { headBranch, commitsAhead });
|
|
1404
|
+
// Check for existing PR - using 'octokit' package (already installed)
|
|
1405
|
+
const { Octokit } = require('octokit');
|
|
1406
|
+
const octokit = new Octokit({
|
|
1407
|
+
auth: installationToken
|
|
1408
|
+
});
|
|
1409
|
+
const existingPRs = await octokit.rest.pulls.list({
|
|
1410
|
+
owner: gitOrgName,
|
|
1411
|
+
repo: repoName,
|
|
1412
|
+
head: `${gitOrgName}:${headBranch}`,
|
|
1413
|
+
base: targetBranch,
|
|
1414
|
+
state: 'open'
|
|
1415
|
+
});
|
|
1416
|
+
if (existingPRs.data.length > 0) {
|
|
1417
|
+
const existingPR = existingPRs.data[0];
|
|
1418
|
+
logger.info('Pull request exists', {
|
|
1419
|
+
repoName,
|
|
1420
|
+
prNumber: existingPR.number,
|
|
1421
|
+
prUrl: existingPR.html_url
|
|
1445
1422
|
});
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
prExists: true,
|
|
1458
|
-
prNumber: existingPR.number,
|
|
1459
|
-
prUrl: existingPR.html_url,
|
|
1460
|
-
prTitle: existingPR.title,
|
|
1461
|
-
commitsAhead,
|
|
1462
|
-
createdAt: existingPR.created_at
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
else {
|
|
1466
|
-
logger.info('No pull request found', { repoName, headBranch, targetBranch });
|
|
1467
|
-
return {
|
|
1468
|
-
repoName,
|
|
1469
|
-
headBranch,
|
|
1470
|
-
targetBranch,
|
|
1471
|
-
prExists: false,
|
|
1472
|
-
commitsAhead
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1423
|
+
return {
|
|
1424
|
+
repoName,
|
|
1425
|
+
headBranch,
|
|
1426
|
+
targetBranch,
|
|
1427
|
+
prExists: true,
|
|
1428
|
+
prNumber: existingPR.number,
|
|
1429
|
+
prUrl: existingPR.html_url,
|
|
1430
|
+
prTitle: existingPR.title,
|
|
1431
|
+
commitsAhead,
|
|
1432
|
+
createdAt: existingPR.created_at
|
|
1433
|
+
};
|
|
1475
1434
|
}
|
|
1476
|
-
|
|
1435
|
+
else {
|
|
1436
|
+
logger.info('No pull request found', { repoName, headBranch, targetBranch });
|
|
1437
|
+
return {
|
|
1438
|
+
repoName,
|
|
1439
|
+
headBranch,
|
|
1440
|
+
targetBranch,
|
|
1441
|
+
prExists: false,
|
|
1442
|
+
commitsAhead
|
|
1443
|
+
};
|
|
1477
1444
|
}
|
|
1478
|
-
}
|
|
1445
|
+
}
|
|
1446
|
+
finally {
|
|
1447
|
+
}
|
|
1479
1448
|
}
|
|
1480
1449
|
/**
|
|
1481
1450
|
* Get commit list with commit IDs for a branch
|
|
@@ -1974,7 +1943,7 @@ class GithubOperationsService {
|
|
|
1974
1943
|
/**
|
|
1975
1944
|
* Helper method to get merge conflicts
|
|
1976
1945
|
*/
|
|
1977
|
-
async getMergeConflicts(repoName) {
|
|
1946
|
+
async getMergeConflicts(socket, repoName) {
|
|
1978
1947
|
return new Promise((resolve) => {
|
|
1979
1948
|
this.getMergeConflictsImplementation(repoName, tool_server_1.folderPath).then((mergeData) => {
|
|
1980
1949
|
resolve(mergeData);
|