Skip to content

Publish Bottles and Merge PR #421

Publish Bottles and Merge PR

Publish Bottles and Merge PR #421

Workflow file for this run

name: Publish Bottles and Merge PR
on:
pull_request_target:
types:
- labeled
workflow_run:
workflows:
- Build, Test and Upload Bottles
types:
- completed
concurrency:
group: publish-${{ github.event.pull_request.number || github.event.workflow_run.pull_requests[0].number || github.run_id }}
cancel-in-progress: false
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
check:
if: >
(github.event_name == 'pull_request_target' && github.event.label.name == 'Automerge') ||
(github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request')
runs-on: ubuntu-22.04
permissions:
actions: read
contents: read
pull-requests: read
outputs:
publish: ${{ steps.check.outputs.publish }}
pr: ${{ steps.check.outputs.pr }}
head_ref: ${{ steps.check.outputs.head_ref }}
head_repo_clone_url: ${{ steps.check.outputs.head_repo_clone_url }}
steps:
- name: Evaluate Publish Readiness
id: check
uses: actions/github-script@v8
with:
github-token: ${{ github.token }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const defaultBranch = context.payload.repository.default_branch;
const ciWorkflowName = 'Build, Test and Upload Bottles';
async function resolvePullRequestNumber() {
if (context.eventName === 'pull_request_target') {
return context.payload.pull_request.number;
}
const run = context.payload.workflow_run;
let number = run.pull_requests?.[0]?.number;
if (number) {
return number;
}
const associated = await github.paginate(
github.rest.repos.listPullRequestsAssociatedWithCommit,
{
owner,
repo,
commit_sha: run.head_sha,
per_page: 100,
},
);
const open = associated.filter((pr) => pr.state === 'open');
if (open.length !== 1) {
core.notice(`Expected exactly one open PR for ${run.head_sha}; found ${open.length}.`);
return null;
}
return open[0].number;
}
async function findSuccessfulCiRun(pullNumber, headSha) {
const runs = await github.paginate(
github.rest.actions.listWorkflowRunsForRepo,
{
owner,
repo,
event: 'pull_request',
head_sha: headSha,
per_page: 100,
},
);
return runs.find((run) =>
run.name === ciWorkflowName &&
run.head_sha === headSha &&
run.conclusion === 'success' &&
(run.pull_requests ?? []).some((pullRequest) => pullRequest.number === pullNumber),
);
}
const number = await resolvePullRequestNumber();
if (!number) {
core.setOutput('publish', 'false');
return;
}
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: number,
});
if (pr.state !== 'open') {
core.notice(`PR #${number} is ${pr.state}; skipping.`);
core.setOutput('publish', 'false');
return;
}
if (pr.draft) {
core.notice(`PR #${number} is draft; skipping.`);
core.setOutput('publish', 'false');
return;
}
if (pr.base.ref !== defaultBranch) {
core.notice(`PR #${number} targets ${pr.base.ref}, not ${defaultBranch}; skipping.`);
core.setOutput('publish', 'false');
return;
}
const ciRun = context.eventName === 'workflow_run'
? context.payload.workflow_run
: await findSuccessfulCiRun(number, pr.head.sha);
if (!ciRun || ciRun.name !== ciWorkflowName || ciRun.conclusion !== 'success') {
core.notice(`PR #${number} does not have a successful ${ciWorkflowName} run yet; skipping.`);
core.setOutput('publish', 'false');
return;
}
const hasAutomergeLabel = pr.labels.some(({ name }) => name === 'Automerge');
if (!hasAutomergeLabel) {
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: number,
per_page: 100,
});
const allowedFile = /^(Formula|Casks)\/[^/]+\.rb$/;
const unexpectedFiles = files
.map(({ filename }) => filename)
.filter((filename) => !allowedFile.test(filename));
const trustedAutobump =
pr.head.repo.full_name === `${owner}/${repo}` &&
pr.head.ref.startsWith('bump-') &&
unexpectedFiles.length === 0;
if (!trustedAutobump) {
core.notice(`PR #${number} is not labeled Automerge and is not a trusted autobump PR; skipping.`);
core.setOutput('publish', 'false');
return;
}
core.notice(`Trusted autobump PR #${number} passed CI; continuing without requiring Automerge.`);
}
core.setOutput('publish', 'true');
core.setOutput('pr', `${number}`);
core.setOutput('head_ref', pr.head.ref);
core.setOutput('head_repo_clone_url', pr.head.repo.clone_url);
publish:
needs:
- check
if: needs.check.outputs.publish == 'true'
runs-on: ubuntu-22.04
container:
image: ghcr.io/homebrew/ubuntu22.04:master
permissions:
contents: write
packages: write
pull-requests: write
env:
PR: ${{ needs.check.outputs.pr }}
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@main
with:
core: false
cask: false
test-bot: false
- name: Cache Homebrew Bundler RubyGems
id: cache
uses: actions/cache@v4
with:
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
restore-keys: ${{ runner.os }}-rubygems-
- name: Install Homebrew Bundler RubyGems
if: steps.cache.outputs.cache-hit != 'true'
run: brew install-bundler-gems
- name: Configure Git User
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Checkout PR Branch
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
env:
GH_TOKEN: ${{ github.token }}
run: gh pr checkout "$PR"
- name: Pull and Publish Bottles
env:
HOMEBREW_GITHUB_API_TOKEN: ${{ github.token }}
HOMEBREW_GITHUB_PACKAGES_TOKEN: ${{ github.token }}
HOMEBREW_GITHUB_PACKAGES_USER: ${{ github.actor }}
PULL_REQUEST: ${{ env.PR }}
run: |
brew pr-pull \
--debug \
--clean \
--no-cherry-pick \
--artifact-pattern 'bottles_*' \
--tap="$GITHUB_REPOSITORY" \
--workflows ci.yml \
"$PR"
- name: Push commits to PR branch
uses: Homebrew/actions/git-try-push@main
with:
token: ${{ github.token }}
branch: ${{ needs.check.outputs.head_ref }}
remote: ${{ needs.check.outputs.head_repo_clone_url }}
directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
- name: Wait for Remote Branch to be Updated
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
run: |
local_head=$(git rev-parse HEAD)
remote_ref="pull/${PR}/head"
attempt=0
max_attempts=10
timeout=1
# Wait (with exponential backoff) until the PR branch is in sync
while [[ "$attempt" -lt "$max_attempts" ]]; do
remote_head="$(git ls-remote origin "$remote_ref" | cut -f1)"
if [[ "$local_head" = "$remote_head" ]]
then
success=1
break
fi
sleep "$timeout"
attempt=$(( attempt + 1 ))
timeout=$(( timeout * 2 ))
done
if [[ "$success" -ne 1 ]]; then
echo "Remote branch not updated after ${max_attempts} attempts"
exit 1
fi
- name: Approve Pull Request
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
env:
GH_TOKEN: ${{ github.token }}
run: gh pr review "$PR" --approve
- name: Enable Auto Merge
working-directory: ${{ steps.set-up-homebrew.outputs.repository-path }}
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr merge "$PR" \
--merge \
--delete-branch