--- /dev/null
+name: 'Combine PRs'
+
+# Controls when the action will run - in this case triggered manually
+on:
+ workflow_dispatch:
+ inputs:
+ branchPrefix:
+ description: 'Branch prefix to find combinable PRs based on'
+ required: true
+ default: 'dependabot'
+ mustBeGreen:
+ description: 'Only combine PRs that are green (status is success)'
+ required: true
+ default: true
+ combineBranchName:
+ description: 'Name of the branch to combine PRs into'
+ required: true
+ default: 'combine-prs-branch'
+ ignoreLabel:
+ description: 'Exclude PRs with this label'
+ required: true
+ default: 'nocombine'
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ # This workflow contains a single job called "combine-prs"
+ combine-prs:
+ # The type of runner that the job will run on
+ runs-on: ubuntu-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ - uses: actions/github-script@v6
+ id: create-combined-pr
+ name: Create Combined PR
+ with:
+ github-token: ${{secrets.GITHUB_TOKEN}}
+ script: |
+ const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', {
+ owner: context.repo.owner,
+ repo: context.repo.repo
+ });
+ let branchesAndPRStrings = [];
+ let baseBranch = null;
+ let baseBranchSHA = null;
+ for (const pull of pulls) {
+ const branch = pull['head']['ref'];
+ console.log('Pull for branch: ' + branch);
+ if (branch.startsWith('${{ github.event.inputs.branchPrefix }}')) {
+ console.log('Branch matched prefix: ' + branch);
+ let statusOK = true;
+ if(${{ github.event.inputs.mustBeGreen }}) {
+ console.log('Checking green status: ' + branch);
+ const statusResponse = await github.rest.repos.getCombinedStatusForRef({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ ref: branch
+ });
+ const state = statusResponse['data']['state'];
+ console.log('Validating status: ' + state);
+ if(state != 'success') {
+ console.log('Discarding ' + branch + ' with status ' + state);
+ statusOK = false;
+ }
+ }
+ console.log('Checking labels: ' + branch);
+ const labels = pull['labels'];
+ for(const label of labels) {
+ const labelName = label['name'];
+ console.log('Checking label: ' + labelName);
+ if(labelName == '${{ github.event.inputs.ignoreLabel }}') {
+ console.log('Discarding ' + branch + ' with label ' + labelName);
+ statusOK = false;
+ }
+ }
+ if (statusOK) {
+ console.log('Adding branch to array: ' + branch);
+ const prString = '#' + pull['number'] + ' ' + pull['title'];
+ branchesAndPRStrings.push({ branch, prString });
+ baseBranch = pull['base']['ref'];
+ baseBranchSHA = pull['base']['sha'];
+ }
+ }
+ }
+ if (branchesAndPRStrings.length == 0) {
+ core.setFailed('No PRs/branches matched criteria');
+ return;
+ }
+ try {
+ await github.rest.git.createRef({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}',
+ sha: baseBranchSHA
+ });
+ } catch (error) {
+ console.log(error);
+ core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?');
+ return;
+ }
+
+ let combinedPRs = [];
+ let mergeFailedPRs = [];
+ for(const { branch, prString } of branchesAndPRStrings) {
+ try {
+ await github.rest.repos.merge({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ base: '${{ github.event.inputs.combineBranchName }}',
+ head: branch,
+ });
+ console.log('Merged branch ' + branch);
+ combinedPRs.push(prString);
+ } catch (error) {
+ console.log('Failed to merge branch ' + branch);
+ mergeFailedPRs.push(prString);
+ }
+ }
+
+ console.log('Creating combined PR');
+ const combinedPRsString = combinedPRs.join('\n');
+ let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString;
+ if(mergeFailedPRs.length > 0) {
+ const mergeFailedPRsString = mergeFailedPRs.join('\n');
+ body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString
+ }
+ await github.rest.pulls.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title: 'Combined PR',
+ head: '${{ github.event.inputs.combineBranchName }}',
+ base: baseBranch,
+ body: body
+ });