Delete branches #226
This file contains 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
name: Delete branches | |
on: | |
workflow_dispatch: | |
inputs: | |
branch: | |
description: "Branch to delete" | |
required: false | |
dry-run: | |
description: "Whether to run in dry run mode" | |
required: false | |
default: 'false' | |
schedule: | |
- cron: '10 8 * * *' # https://crontab.guru/#10_8_*_*_* | |
jobs: | |
delete-branches: | |
name: Delete branches | |
runs-on: ubuntu-latest | |
steps: | |
- name: Delete branches | |
env: | |
BRANCH: ${{ github.event.inputs.branch }} | |
DRY_RUN: ${{ github.event.inputs.dry-run }} | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.UCI_GITHUB_TOKEN }} | |
retries: 0 | |
script: | | |
const request = async function(req, opts) { | |
try { | |
return await req(opts) | |
} catch(err) { | |
opts._attempt = (opts._attempt || 0) + 1 | |
if (err.status === 403) { | |
if (err.response.headers['x-ratelimit-remaining'] === '0') { | |
const retryAfter = err.response.headers['x-ratelimit-reset'] - Math.floor(Date.now() / 1000) || 1 | |
core.info(`Rate limit exceeded, retrying in ${retryAfter} seconds`) | |
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)) | |
return request(req, opts) | |
} | |
if (err.message.toLowerCase().includes('secondary rate limit')) { | |
const retryAfter = Math.pow(2, opts._attempt) | |
core.info(`Secondary rate limit exceeded, retrying in ${retryAfter} seconds`) | |
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)) | |
return request(req, opts) | |
} | |
} | |
throw err | |
} | |
} | |
github.hook.wrap('request', request) | |
core.info(`Looking for repositories the user has direct access to`) | |
const items = await github.paginate(github.rest.repos.listForAuthenticatedUser, { | |
affiliation: 'collaborator' | |
}) | |
core.info(`Filtering out the repositories without branches to delete`) | |
const repoBranches = [] | |
for (const item of items) { | |
if (item.archived) { | |
core.info(`Skipping archived repository ${item.html_url}`) | |
continue | |
} | |
core.info(`Looking for matching branches for ${item.html_url}`) | |
const branches = (await github.paginate(github.rest.repos.listBranches, { | |
owner: item.owner.login, | |
repo: item.name | |
})).filter(branch => { | |
if (process.env.BRANCH) { | |
return branch.name == process.env.BRANCH | |
} else { | |
return branch.name.startsWith('uci/') | |
} | |
}) | |
for (const branch of branches) { | |
core.info(`Checking if ${item.html_url}/tree/${branch.name} can be deleted`) | |
const {data: pulls} = await github.rest.repos.listPullRequestsAssociatedWithCommit({ | |
owner: item.owner.login, | |
repo: item.name, | |
commit_sha: branch.commit.sha | |
}) | |
const openPulls = pulls.filter(pull => pull.state == 'open') | |
if (pulls.length != 0 && openPulls.length == 0) { | |
core.info(`${item.html_url}/tree/${branch.name} has ${pulls.length} PRs, but none are open, deleting`) | |
repoBranches.push({repo: item, branch}) | |
} else { | |
core.info(`${item.html_url}/tree/${branch.name} has ${pulls.length} PRs, ${openPulls.length} are open, skipping`) | |
} | |
} | |
} | |
core.info(`Attempting to delete branches`) | |
const failed = [] | |
for (const {repo, branch} of repoBranches) { | |
core.info(`Deleting ${repo.html_url}/tree/${branch.name}`) | |
if (process.env.DRY_RUN == 'true') { | |
core.info(`Skipping branch deletion because this is a dry run`) | |
continue | |
} | |
try { | |
await github.rest.git.deleteRef({ | |
owner: repo.owner.login, | |
repo: repo.name, | |
ref: `heads/${branch.name}` | |
}) | |
core.info(`${repo.html_url}/tree/${branch.name} deleted successfully`) | |
} catch(error) { | |
core.error(`Couldn't delete ${repo.html_url}/tree/${branch.name}, got: ${error}`) | |
failed.push({repo, branch}) | |
} | |
} | |
if (failed.length != 0) { | |
throw new Error(`Failed to delete ${failed.length} branches`) | |
} |