-
Notifications
You must be signed in to change notification settings - Fork 3.4k
118 lines (102 loc) · 5.07 KB
/
validate-pr-target-branch.yml
File metadata and controls
118 lines (102 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# This workflow automatically closes PRs from external contributors (those without push permissions)
# that target release/* branches. It posts a comment asking them to resubmit with the correct target branch.
#
# External contributors should not target release/* branches per our contribution guidelines.
# Internal contributors (with push permissions) can target any branch as needed.
name: Validate PR Target Branch
on:
pull_request_target:
types: [opened, edited, reopened]
permissions:
pull-requests: write
issues: write
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check PR target branch and author permissions
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
const targetBranch = pr.base.ref;
const prNumber = pr.number;
const prAuthor = pr.user.login;
const action = context.payload.action;
const triggeredBy = context.actor;
console.log(`PR #${prNumber} by ${prAuthor} targets branch: ${targetBranch}`);
console.log(`Action: ${action}, triggered by: ${triggeredBy}`);
// Helper function to check if a user is a bot
const isBot = (username) => {
return username === 'copilot' ||
username === 'dotnet-bot' ||
username.startsWith('app/') ||
username.includes('[bot]');
};
// If action is 'edited', check if the base branch was actually changed
if (action === 'edited' && !context.payload.changes?.base) {
console.log('PR was edited but base branch was not changed - skipping');
return;
}
// Skip if triggered by a bot or PR is authored by a bot
if (isBot(triggeredBy) || isBot(prAuthor)) {
console.log('Bot detected - skipping');
return;
}
// Check if the user who triggered the action has push permissions
let hasWriteAccess = false;
try {
const { data: permissions } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: triggeredBy
});
hasWriteAccess = ['admin', 'write'].includes(permissions.permission);
console.log(`User ${triggeredBy} has permission level: ${permissions.permission}`);
} catch (error) {
console.error('Error checking permissions:', error);
// If we can't determine permissions, assume external contributor
}
// Check if target branch is a release branch
if (!targetBranch.startsWith('release/')) {
// For new PRs by external contributors, add community-contribution label
if (action === 'opened' && !hasWriteAccess) {
try {
console.log('Adding community-contribution label');
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: ['community-contribution']
});
} catch (error) {
console.error('Error adding label:', error);
}
}
console.log('PR does not target a release branch - allowed');
return;
}
// If user has write access, allow PR to release branch
if (hasWriteAccess) {
console.log('User has write access - allowed');
return;
}
// External contributor targeting release branch - close and comment
console.log('External contributor targeting release branch - closing PR');
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `Thank you for your contribution! However, this PR targets the \`${targetBranch}\` branch.\n\nExternal contributions should not target release branches. This pull request has been closed automatically; please open a new pull request targeting \`main\` or another non-release branch.\n\nFor more information, see our [contribution guidelines](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/.github/CONTRIBUTING.md).`
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
state: 'closed'
});
console.log('PR closed successfully');
} catch (error) {
console.error('Error closing PR:', error);
}