Skip to content

Commit 0889038

Browse files
Copilotpranaygp
andcommitted
Resolve merge conflicts with base PR #325
Co-authored-by: pranaygp <[email protected]>
1 parent 1e7f2e9 commit 0889038

File tree

5 files changed

+175
-159
lines changed

5 files changed

+175
-159
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = async ({ github, context }) => {
2+
try {
3+
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
4+
owner: context.repo.owner,
5+
repo: context.repo.repo,
6+
username: context.actor
7+
});
8+
9+
const hasPermission = ['admin', 'write'].includes(permission.data.permission);
10+
console.log(`User ${context.actor} has permission: ${permission.data.permission}`);
11+
return hasPermission ? 'true' : 'false';
12+
} catch (error) {
13+
console.error('Error checking permissions:', error);
14+
return 'false';
15+
}
16+
};

.github/scripts/create-ci-pr.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
module.exports = async ({ github, context, core, exec }, prDetails) => {
2+
const ciBranchName = `ci-test/${prDetails.number}`;
3+
4+
// Add remote for the external fork if it's from a fork
5+
if (prDetails.head_repo_full_name !== `${context.repo.owner}/${context.repo.repo}`) {
6+
await exec.exec('git', ['remote', 'add', 'external', `https://github.com/${prDetails.head_repo_full_name}.git`]);
7+
await exec.exec('git', ['fetch', 'external', prDetails.head_ref]);
8+
await exec.exec('git', ['checkout', '-b', ciBranchName, `external/${prDetails.head_ref}`]);
9+
} else {
10+
await exec.exec('git', ['fetch', 'origin', prDetails.head_ref]);
11+
await exec.exec('git', ['checkout', '-b', ciBranchName, `origin/${prDetails.head_ref}`]);
12+
}
13+
14+
// Push the branch to origin (force push to update if exists)
15+
await exec.exec('git', ['push', '-f', 'origin', ciBranchName]);
16+
17+
// Check if a CI PR already exists for this original PR
18+
const existingPRs = await github.rest.pulls.list({
19+
owner: context.repo.owner,
20+
repo: context.repo.repo,
21+
state: 'open',
22+
head: `${context.repo.owner}:${ciBranchName}`
23+
});
24+
25+
let ciPR;
26+
let isNewPR = false;
27+
28+
if (existingPRs.data.length > 0) {
29+
// Filter to find the CI test PR (should be labeled with 'ci-test')
30+
const ciTestPR = existingPRs.data.find(pr =>
31+
pr.labels.some(label => label.name === 'ci-test')
32+
);
33+
34+
if (ciTestPR) {
35+
// Update existing PR
36+
ciPR = ciTestPR;
37+
await github.rest.pulls.update({
38+
owner: context.repo.owner,
39+
repo: context.repo.repo,
40+
pull_number: ciPR.number,
41+
body: `🤖 **Automated CI Test PR**
42+
43+
This is an automated PR created to run CI tests for PR #${prDetails.number} by @${prDetails.user}.
44+
45+
**Original PR:** #${prDetails.number}
46+
**Last triggered by:** @${context.actor}
47+
**Source branch:** \`${prDetails.head_ref}\`
48+
**Source SHA:** \`${prDetails.head_sha}\`
49+
50+
⚠️ **This PR will be automatically closed once CI completes.** Do not merge this PR.
51+
52+
---
53+
_This PR was last updated in response to the \`/run-ci\` command in #${prDetails.number}_`
54+
});
55+
} else {
56+
// Existing PR found but it's not labeled as a CI test PR
57+
// Verify it's actually a CI test PR by checking the title
58+
const existingPR = existingPRs.data[0];
59+
if (existingPR.title.startsWith('[CI Test]')) {
60+
// Use the existing PR and add the labels
61+
ciPR = existingPR;
62+
isNewPR = true; // Treat as new to ensure labels are added
63+
}
64+
// If it's not a CI test PR, ciPR remains undefined and a new PR will be created
65+
}
66+
}
67+
68+
if (!ciPR) {
69+
// Create a new draft PR
70+
const newPR = await github.rest.pulls.create({
71+
owner: context.repo.owner,
72+
repo: context.repo.repo,
73+
title: `[CI Test] ${prDetails.title}`,
74+
head: ciBranchName,
75+
base: prDetails.base_ref,
76+
body: `🤖 **Automated CI Test PR**
77+
78+
This is an automated PR created to run CI tests for PR #${prDetails.number} by @${prDetails.user}.
79+
80+
**Original PR:** #${prDetails.number}
81+
**Triggered by:** @${context.actor}
82+
**Source branch:** \`${prDetails.head_ref}\`
83+
**Source SHA:** \`${prDetails.head_sha}\`
84+
85+
⚠️ **This PR will be automatically closed once CI completes.** Do not merge this PR.
86+
87+
---
88+
_This PR was created in response to the \`/run-ci\` command in #${prDetails.number}_`,
89+
draft: true
90+
});
91+
ciPR = newPR.data;
92+
isNewPR = true;
93+
}
94+
95+
// Ensure labels are present on the CI PR (handles both new and existing PRs)
96+
// Labels can be strings or objects with 'name' property depending on the API response
97+
const currentLabels = ciPR.labels?.map(l => typeof l === 'string' ? l : l.name) || [];
98+
const requiredLabels = ['ci-test', 'automated'];
99+
const missingLabels = requiredLabels.filter(label => !currentLabels.includes(label));
100+
101+
if (missingLabels.length > 0) {
102+
await github.rest.issues.addLabels({
103+
owner: context.repo.owner,
104+
repo: context.repo.repo,
105+
issue_number: ciPR.number,
106+
labels: missingLabels
107+
});
108+
}
109+
110+
// Comment on the original PR
111+
const prAction = isNewPR ? 'created' : 'updated';
112+
await github.rest.issues.createComment({
113+
owner: context.repo.owner,
114+
repo: context.repo.repo,
115+
issue_number: context.issue.number,
116+
body: `✅ CI test ${prAction} by @${context.actor}!
117+
118+
CI is now running in draft PR #${ciPR.number}. You can monitor the progress there.
119+
120+
Once the tests complete, you can review the results and the draft PR will be automatically closed.`
121+
});
122+
123+
core.setOutput('ci_pr_number', ciPR.number);
124+
core.setOutput('ci_branch_name', ciBranchName);
125+
};

.github/scripts/get-pr-details.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = async ({ github, context }) => {
2+
const pr = await github.rest.pulls.get({
3+
owner: context.repo.owner,
4+
repo: context.repo.repo,
5+
pull_number: context.issue.number
6+
});
7+
8+
return {
9+
head_ref: pr.data.head.ref,
10+
head_sha: pr.data.head.sha,
11+
head_repo_full_name: pr.data.head.repo.full_name,
12+
base_ref: pr.data.base.ref,
13+
title: pr.data.title,
14+
number: pr.data.number,
15+
user: pr.data.user.login
16+
};
17+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = async ({ github, context, core }) => {
2+
await github.rest.issues.createComment({
3+
owner: context.repo.owner,
4+
repo: context.repo.repo,
5+
issue_number: context.issue.number,
6+
body: '❌ Only repository admins and maintainers can trigger CI runs. You have insufficient permissions.'
7+
});
8+
core.setFailed('Insufficient permissions to trigger CI');
9+
};

.github/workflows/trigger-ci.yml

Lines changed: 8 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,17 @@ jobs:
2424
github-token: ${{ secrets.GITHUB_TOKEN }}
2525
result-encoding: string
2626
script: |
27-
try {
28-
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
29-
owner: context.repo.owner,
30-
repo: context.repo.repo,
31-
username: context.actor
32-
});
33-
34-
const hasPermission = ['admin', 'write'].includes(permission.data.permission);
35-
console.log(`User ${context.actor} has permission: ${permission.data.permission}`);
36-
return hasPermission ? 'true' : 'false';
37-
} catch (error) {
38-
console.error('Error checking permissions:', error);
39-
return 'false';
40-
}
27+
const script = require('./.github/scripts/check-permissions.js');
28+
return await script({ github, context });
4129
4230
- name: Exit if unauthorized
4331
if: steps.check-permissions.outputs.result != 'true'
4432
uses: actions/github-script@v7
4533
with:
4634
github-token: ${{ secrets.GITHUB_TOKEN }}
4735
script: |
48-
await github.rest.issues.createComment({
49-
owner: context.repo.owner,
50-
repo: context.repo.repo,
51-
issue_number: context.issue.number,
52-
body: '❌ Only repository admins and maintainers can trigger CI runs. You have insufficient permissions.'
53-
});
54-
core.setFailed('Insufficient permissions to trigger CI');
36+
const script = require('./.github/scripts/post-unauthorized-comment.js');
37+
await script({ github, context, core });
5538
5639
- name: Get PR details
5740
if: steps.check-permissions.outputs.result == 'true'
@@ -60,21 +43,8 @@ jobs:
6043
with:
6144
github-token: ${{ secrets.GITHUB_TOKEN }}
6245
script: |
63-
const pr = await github.rest.pulls.get({
64-
owner: context.repo.owner,
65-
repo: context.repo.repo,
66-
pull_number: context.issue.number
67-
});
68-
69-
return {
70-
head_ref: pr.data.head.ref,
71-
head_sha: pr.data.head.sha,
72-
head_repo_full_name: pr.data.head.repo.full_name,
73-
base_ref: pr.data.base.ref,
74-
title: pr.data.title,
75-
number: pr.data.number,
76-
user: pr.data.user.login
77-
};
46+
const script = require('./.github/scripts/get-pr-details.js');
47+
return await script({ github, context });
7848
7949
- name: Checkout repo
8050
if: steps.check-permissions.outputs.result == 'true'
@@ -88,128 +58,7 @@ jobs:
8858
with:
8959
github-token: ${{ secrets.GITHUB_TOKEN }}
9060
script: |
61+
const script = require('./.github/scripts/create-ci-pr.js');
9162
const prDetails = ${{ steps.pr-details.outputs.result }};
92-
const ciBranchName = `ci-test/${prDetails.number}`;
93-
94-
// Add remote for the external fork if it's from a fork
95-
if (prDetails.head_repo_full_name !== `${context.repo.owner}/${context.repo.repo}`) {
96-
await exec.exec('git', ['remote', 'add', 'external', `https://github.com/${prDetails.head_repo_full_name}.git`]);
97-
await exec.exec('git', ['fetch', 'external', prDetails.head_ref]);
98-
await exec.exec('git', ['checkout', '-b', ciBranchName, `external/${prDetails.head_ref}`]);
99-
} else {
100-
await exec.exec('git', ['fetch', 'origin', prDetails.head_ref]);
101-
await exec.exec('git', ['checkout', '-b', ciBranchName, `origin/${prDetails.head_ref}`]);
102-
}
103-
104-
// Push the branch to origin (force push to update if exists)
105-
await exec.exec('git', ['push', '-f', 'origin', ciBranchName]);
106-
107-
// Check if a CI PR already exists for this original PR
108-
const existingPRs = await github.rest.pulls.list({
109-
owner: context.repo.owner,
110-
repo: context.repo.repo,
111-
state: 'open',
112-
head: `${context.repo.owner}:${ciBranchName}`
113-
});
114-
115-
let ciPR;
116-
let isNewPR = false;
117-
118-
if (existingPRs.data.length > 0) {
119-
// Filter to find the CI test PR (should be labeled with 'ci-test')
120-
const ciTestPR = existingPRs.data.find(pr =>
121-
pr.labels.some(label => label.name === 'ci-test')
122-
);
123-
124-
if (ciTestPR) {
125-
// Update existing PR
126-
ciPR = ciTestPR;
127-
await github.rest.pulls.update({
128-
owner: context.repo.owner,
129-
repo: context.repo.repo,
130-
pull_number: ciPR.number,
131-
body: `🤖 **Automated CI Test PR**
132-
133-
This is an automated PR created to run CI tests for PR #${prDetails.number} by @${prDetails.user}.
134-
135-
**Original PR:** #${prDetails.number}
136-
**Last triggered by:** @${context.actor}
137-
**Source branch:** \`${prDetails.head_ref}\`
138-
**Source SHA:** \`${prDetails.head_sha}\`
139-
140-
⚠️ **This PR will be automatically closed once CI completes.** Do not merge this PR.
141-
142-
---
143-
_This PR was last updated in response to the \`/run-ci\` command in #${prDetails.number}_`
144-
});
145-
} else {
146-
// Existing PR found but it's not labeled as a CI test PR
147-
// Verify it's actually a CI test PR by checking the title
148-
const existingPR = existingPRs.data[0];
149-
if (existingPR.title.startsWith('[CI Test]')) {
150-
// Use the existing PR and add the labels
151-
ciPR = existingPR;
152-
isNewPR = true; // Treat as new to ensure labels are added
153-
}
154-
// If it's not a CI test PR, ciPR remains undefined and a new PR will be created
155-
}
156-
}
157-
158-
if (!ciPR) {
159-
// Create a new draft PR
160-
const newPR = await github.rest.pulls.create({
161-
owner: context.repo.owner,
162-
repo: context.repo.repo,
163-
title: `[CI Test] ${prDetails.title}`,
164-
head: ciBranchName,
165-
base: prDetails.base_ref,
166-
body: `🤖 **Automated CI Test PR**
167-
168-
This is an automated PR created to run CI tests for PR #${prDetails.number} by @${prDetails.user}.
169-
170-
**Original PR:** #${prDetails.number}
171-
**Triggered by:** @${context.actor}
172-
**Source branch:** \`${prDetails.head_ref}\`
173-
**Source SHA:** \`${prDetails.head_sha}\`
174-
175-
⚠️ **This PR will be automatically closed once CI completes.** Do not merge this PR.
176-
177-
---
178-
_This PR was created in response to the \`/run-ci\` command in #${prDetails.number}_`,
179-
draft: true
180-
});
181-
ciPR = newPR.data;
182-
isNewPR = true;
183-
}
184-
185-
// Ensure labels are present on the CI PR (handles both new and existing PRs)
186-
// Labels can be strings or objects with 'name' property depending on the API response
187-
const currentLabels = ciPR.labels?.map(l => typeof l === 'string' ? l : l.name) || [];
188-
const requiredLabels = ['ci-test', 'automated'];
189-
const missingLabels = requiredLabels.filter(label => !currentLabels.includes(label));
190-
191-
if (missingLabels.length > 0) {
192-
await github.rest.issues.addLabels({
193-
owner: context.repo.owner,
194-
repo: context.repo.repo,
195-
issue_number: ciPR.number,
196-
labels: missingLabels
197-
});
198-
}
199-
200-
// Comment on the original PR
201-
const prAction = isNewPR ? 'created' : 'updated';
202-
await github.rest.issues.createComment({
203-
owner: context.repo.owner,
204-
repo: context.repo.repo,
205-
issue_number: context.issue.number,
206-
body: `✅ CI test ${prAction} by @${context.actor}!
207-
208-
CI is now running in draft PR #${ciPR.number}. You can monitor the progress there.
209-
210-
Once the tests complete, you can review the results and the draft PR will be automatically closed.`
211-
});
212-
213-
core.setOutput('ci_pr_number', ciPR.number);
214-
core.setOutput('ci_branch_name', ciBranchName);
63+
await script({ github, context, core, exec }, prDetails);
21564

0 commit comments

Comments
 (0)