-
Notifications
You must be signed in to change notification settings - Fork 142
Remove timestamp from CI branch names, implement 1:1 PR relationship #493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Copilot
wants to merge
6
commits into
pranaygp/external-collaborator-ci
Choose a base branch
from
copilot/sub-pr-325
base: pranaygp/external-collaborator-ci
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d9a48f2
Initial plan
Copilot 90f931d
Remove timestamp from CI branch name and implement 1:1 PR relationship
Copilot 4caf872
Improve PR detection and ensure consistent labeling
Copilot 3aa68b9
Fix API response handling and improve existing PR detection
Copilot 1e7f2e9
Add PR title validation and clarify label handling
Copilot 0889038
Resolve merge conflicts with base PR #325
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| module.exports = async ({ github, context }) => { | ||
| try { | ||
| const permission = await github.rest.repos.getCollaboratorPermissionLevel({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| username: context.actor | ||
| }); | ||
|
|
||
| const hasPermission = ['admin', 'write'].includes(permission.data.permission); | ||
| console.log(`User ${context.actor} has permission: ${permission.data.permission}`); | ||
| return hasPermission ? 'true' : 'false'; | ||
| } catch (error) { | ||
| console.error('Error checking permissions:', error); | ||
| return 'false'; | ||
| } | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| module.exports = async ({ github, context, core, exec }, prDetails) => { | ||
| const ciBranchName = `ci-test/${prDetails.number}`; | ||
|
|
||
| // Add remote for the external fork if it's from a fork | ||
| if (prDetails.head_repo_full_name !== `${context.repo.owner}/${context.repo.repo}`) { | ||
| await exec.exec('git', ['remote', 'add', 'external', `https://github.com/${prDetails.head_repo_full_name}.git`]); | ||
| await exec.exec('git', ['fetch', 'external', prDetails.head_ref]); | ||
| await exec.exec('git', ['checkout', '-b', ciBranchName, `external/${prDetails.head_ref}`]); | ||
| } else { | ||
| await exec.exec('git', ['fetch', 'origin', prDetails.head_ref]); | ||
| await exec.exec('git', ['checkout', '-b', ciBranchName, `origin/${prDetails.head_ref}`]); | ||
| } | ||
|
|
||
| // Push the branch to origin (force push to update if exists) | ||
| await exec.exec('git', ['push', '-f', 'origin', ciBranchName]); | ||
|
|
||
| // Check if a CI PR already exists for this original PR | ||
| const existingPRs = await github.rest.pulls.list({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| state: 'open', | ||
| head: `${context.repo.owner}:${ciBranchName}` | ||
| }); | ||
|
|
||
| let ciPR; | ||
| let isNewPR = false; | ||
|
|
||
| if (existingPRs.data.length > 0) { | ||
| // Filter to find the CI test PR (should be labeled with 'ci-test') | ||
| const ciTestPR = existingPRs.data.find(pr => | ||
| pr.labels.some(label => label.name === 'ci-test') | ||
| ); | ||
|
|
||
| if (ciTestPR) { | ||
| // Update existing PR | ||
| ciPR = ciTestPR; | ||
| await github.rest.pulls.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: ciPR.number, | ||
| body: `🤖 **Automated CI Test PR** | ||
|
|
||
| This is an automated PR created to run CI tests for PR #${prDetails.number} by @${prDetails.user}. | ||
|
|
||
| **Original PR:** #${prDetails.number} | ||
| **Last triggered by:** @${context.actor} | ||
| **Source branch:** \`${prDetails.head_ref}\` | ||
| **Source SHA:** \`${prDetails.head_sha}\` | ||
|
|
||
| ⚠️ **This PR will be automatically closed once CI completes.** Do not merge this PR. | ||
|
|
||
| --- | ||
| _This PR was last updated in response to the \`/run-ci\` command in #${prDetails.number}_` | ||
| }); | ||
| } else { | ||
| // Existing PR found but it's not labeled as a CI test PR | ||
| // Verify it's actually a CI test PR by checking the title | ||
| const existingPR = existingPRs.data[0]; | ||
| if (existingPR.title.startsWith('[CI Test]')) { | ||
| // Use the existing PR and add the labels | ||
| ciPR = existingPR; | ||
| isNewPR = true; // Treat as new to ensure labels are added | ||
| } | ||
| // If it's not a CI test PR, ciPR remains undefined and a new PR will be created | ||
| } | ||
| } | ||
|
|
||
| if (!ciPR) { | ||
| // Create a new draft PR | ||
| const newPR = await github.rest.pulls.create({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| title: `[CI Test] ${prDetails.title}`, | ||
| head: ciBranchName, | ||
| base: prDetails.base_ref, | ||
| body: `🤖 **Automated CI Test PR** | ||
|
|
||
| This is an automated PR created to run CI tests for PR #${prDetails.number} by @${prDetails.user}. | ||
|
|
||
| **Original PR:** #${prDetails.number} | ||
| **Triggered by:** @${context.actor} | ||
| **Source branch:** \`${prDetails.head_ref}\` | ||
| **Source SHA:** \`${prDetails.head_sha}\` | ||
|
|
||
| ⚠️ **This PR will be automatically closed once CI completes.** Do not merge this PR. | ||
|
|
||
| --- | ||
| _This PR was created in response to the \`/run-ci\` command in #${prDetails.number}_`, | ||
| draft: true | ||
| }); | ||
| ciPR = newPR.data; | ||
| isNewPR = true; | ||
| } | ||
|
|
||
| // Ensure labels are present on the CI PR (handles both new and existing PRs) | ||
| // Labels can be strings or objects with 'name' property depending on the API response | ||
| const currentLabels = ciPR.labels?.map(l => typeof l === 'string' ? l : l.name) || []; | ||
| const requiredLabels = ['ci-test', 'automated']; | ||
| const missingLabels = requiredLabels.filter(label => !currentLabels.includes(label)); | ||
|
|
||
| if (missingLabels.length > 0) { | ||
| await github.rest.issues.addLabels({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: ciPR.number, | ||
| labels: missingLabels | ||
| }); | ||
| } | ||
|
|
||
| // Comment on the original PR | ||
| const prAction = isNewPR ? 'created' : 'updated'; | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: `✅ CI test ${prAction} by @${context.actor}! | ||
|
|
||
| CI is now running in draft PR #${ciPR.number}. You can monitor the progress there. | ||
|
|
||
| Once the tests complete, you can review the results and the draft PR will be automatically closed.` | ||
| }); | ||
|
|
||
| core.setOutput('ci_pr_number', ciPR.number); | ||
| core.setOutput('ci_branch_name', ciBranchName); | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| module.exports = async ({ github, context }) => { | ||
| const pr = await github.rest.pulls.get({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: context.issue.number | ||
| }); | ||
|
|
||
| return { | ||
| head_ref: pr.data.head.ref, | ||
| head_sha: pr.data.head.sha, | ||
| head_repo_full_name: pr.data.head.repo.full_name, | ||
| base_ref: pr.data.base.ref, | ||
| title: pr.data.title, | ||
| number: pr.data.number, | ||
| user: pr.data.user.login | ||
| }; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| module.exports = async ({ github, context, core }) => { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: '❌ Only repository admins and maintainers can trigger CI runs. You have insufficient permissions.' | ||
| }); | ||
| core.setFailed('Insufficient permissions to trigger CI'); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When reusing an existing CI test PR without the 'ci-test' label but with the correct title, the isNewPR flag is incorrectly set to true, causing the comment to say "CI test created" instead of "CI test updated".
View Details
Analysis
Incorrect isNewPR flag causes misleading message for reused CI test PRs
What fails: When reusing an existing CI test PR that lacks the 'ci-test' label but has a title starting with '[CI Test]', the
isNewPRflag is incorrectly set totrueat line 54 of.github/scripts/create-ci-pr.js. This causes the user comment at line 75 to say "✅ CI test created" instead of "✅ CI test updated", which is misleading since the PR is being reused, not newly created.How to reproduce:
/run-cion an external PR that creates a new CI test PR (branch:ci-test/{prNumber})/run-ciagain on the same external PRExpected: The message should say "✅ CI test updated" because the code path represents reusing an existing PR (line 52:
ciPR = existingPR), not creating a new one. The labels are added automatically via the label-adding logic (lines 60-70), so settingisNewPR = trueis unnecessary and incorrect.Fix: Changed line 54 from
isNewPR = truetoisNewPR = false, with an updated comment clarifying that this code path represents an existing PR being reused. The label-adding logic continues to work correctly in all scenarios.