Skip to content

Commit 5e4a6df

Browse files
authored
Merge pull request #495 from github/djdefi-combine
2 parents 37ae55f + 4e874ae commit 5e4a6df

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed

.github/workflows/combine-prs.yml

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

0 commit comments

Comments
 (0)