Skip to content

Commit 9dee8d5

Browse files
Merge pull request #612 from NHSDigital/develop
ERSSUP-65488 - APIM-R 1.16 Cut Merge Request
2 parents a78b23c + 943c9b7 commit 9dee8d5

File tree

7 files changed

+1306
-1082
lines changed

7 files changed

+1306
-1082
lines changed

.github/workflows/combine-prs.yml

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: 'Combine PRs'
2+
3+
# Controls when the action will run - in this case triggered manually
4+
on:
5+
workflow_dispatch:
6+
inputs:
7+
titlePrefix:
8+
type: choice
9+
description: 'Branch title prefix to find combinable PRs based on'
10+
required: true
11+
options:
12+
- 'npm'
13+
- 'sandbox-npm'
14+
- 'pip'
15+
- 'sandbox-docker'
16+
- 'github-actions'
17+
default: 'npm'
18+
jiraNumber:
19+
description: 'Enter the Jira Number associated to these PRs'
20+
required: true
21+
mustBeGreen:
22+
type: boolean
23+
description: 'Only combine PRs that are green (status is success)'
24+
required: true
25+
default: true
26+
combineBranchName:
27+
description: 'Base Name of the branch to combine PRs into'
28+
required: true
29+
default: 'combine-prs'
30+
ignoreLabel:
31+
description: 'Exclude PRs with this label'
32+
required: true
33+
default: 'nocombine'
34+
35+
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
36+
jobs:
37+
# This workflow contains a single job called "combine-prs"
38+
combine-prs:
39+
# The type of runner that the job will run on
40+
runs-on: ubuntu-latest
41+
42+
# Steps represent a sequence of tasks that will be executed as part of the job
43+
steps:
44+
- uses: actions/github-script@v6
45+
id: create-combined-pr
46+
name: Create Combined PR
47+
with:
48+
github-token: ${{secrets.GITHUB_TOKEN}}
49+
script: |
50+
const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', {
51+
owner: context.repo.owner,
52+
repo: context.repo.repo
53+
});
54+
let branchesAndPRStrings = [];
55+
let baseBranch = null;
56+
let baseBranchSHA = null;
57+
let today = new Date();
58+
let fulldepsPrefix = '${{ github.event.inputs.titlePrefix }}' + '(deps):';
59+
let fulldevPrefix = '${{ github.event.inputs.titlePrefix }}' + '(deps-dev):';
60+
let fullBranchName=['${{ github.event.inputs.jiraNumber }}', '${{ github.event.inputs.combineBranchName }}', '${{ github.event.inputs.titlePrefix }}', today.toISOString().split('T')[0]].join('-');
61+
let fullTitle=['${{ github.event.inputs.jiraNumber }}', '- Combine PRs for', '${{ github.event.inputs.titlePrefix }}', today.toISOString().split('T')[0]].join(' ');
62+
for (const pull of pulls) {
63+
const branch = pull['head']['ref'];
64+
const title = pull['title'];
65+
console.log('Pull for branch: ' + branch);
66+
if (title.startsWith(fulldepsPrefix) || title.startsWith(fulldevPrefix)) {
67+
console.log('Title matched prefix: ' + title);
68+
let statusOK = true;
69+
if(${{ github.event.inputs.mustBeGreen }}) {
70+
console.log('Checking green status: ' + branch);
71+
const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) {
72+
repository(owner: $owner, name: $repo) {
73+
pullRequest(number:$pull_number) {
74+
commits(last: 1) {
75+
nodes {
76+
commit {
77+
statusCheckRollup {
78+
state
79+
}
80+
}
81+
}
82+
}
83+
}
84+
}
85+
}`
86+
const vars = {
87+
owner: context.repo.owner,
88+
repo: context.repo.repo,
89+
pull_number: pull['number']
90+
};
91+
const result = await github.graphql(stateQuery, vars);
92+
const [{ commit }] = result.repository.pullRequest.commits.nodes;
93+
const state = commit.statusCheckRollup.state
94+
console.log('Validating status: ' + state);
95+
if(state != 'SUCCESS') {
96+
console.log('Discarding ' + branch + ' with status ' + state);
97+
statusOK = false;
98+
}
99+
}
100+
console.log('Checking labels: ' + branch);
101+
const labels = pull['labels'];
102+
for(const label of labels) {
103+
const labelName = label['name'];
104+
console.log('Checking label: ' + labelName);
105+
if(labelName == '${{ github.event.inputs.ignoreLabel }}') {
106+
console.log('Discarding ' + branch + ' with label ' + labelName);
107+
statusOK = false;
108+
}
109+
}
110+
console.log('Checking user')
111+
const username = pull['user']['login']
112+
if(!username.startsWith('dependabot')){
113+
console.log('Discarding ' + branch + ' with user ' + userName);
114+
statusOK = false;
115+
}
116+
if (statusOK) {
117+
console.log('Adding branch to array: ' + branch);
118+
const prString = '#' + pull['number'] + ' ' + pull['title'];
119+
branchesAndPRStrings.push({ branch, prString });
120+
baseBranch = pull['base']['ref'];
121+
baseBranchSHA = pull['base']['sha'];
122+
}
123+
}
124+
}
125+
if (branchesAndPRStrings.length == 0) {
126+
core.setFailed('No PRs/branches matched criteria');
127+
return;
128+
}
129+
try {
130+
await github.rest.git.createRef({
131+
owner: context.repo.owner,
132+
repo: context.repo.repo,
133+
ref: 'refs/heads/' + fullBranchName,
134+
sha: baseBranchSHA
135+
});
136+
} catch (error) {
137+
console.log(error);
138+
core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?');
139+
return;
140+
}
141+
142+
let combinedPRs = [];
143+
let mergeFailedPRs = [];
144+
for(const { branch, prString } of branchesAndPRStrings) {
145+
try {
146+
await github.rest.repos.merge({
147+
owner: context.repo.owner,
148+
repo: context.repo.repo,
149+
base: fullBranchName,
150+
head: branch,
151+
});
152+
console.log('Merged branch ' + branch);
153+
combinedPRs.push(prString);
154+
} catch (error) {
155+
console.log('Failed to merge branch ' + branch);
156+
mergeFailedPRs.push(prString);
157+
}
158+
}
159+
160+
console.log('Creating combined PR');
161+
const combinedPRsString = combinedPRs.join('\n');
162+
let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString;
163+
if(mergeFailedPRs.length > 0) {
164+
const mergeFailedPRsString = mergeFailedPRs.join('\n');
165+
body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString
166+
}
167+
await github.rest.pulls.create({
168+
owner: context.repo.owner,
169+
repo: context.repo.repo,
170+
title: fullTitle,
171+
head: fullBranchName,
172+
base: baseBranch,
173+
body: body
174+
});

0 commit comments

Comments
 (0)