Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 0 additions & 34 deletions .github/scripts/approve-sync-push.cjs

This file was deleted.

8 changes: 0 additions & 8 deletions .github/scripts/auto-merge-pr.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
// test.yml runs on PRs only; an existing test may also run on master
// (builder-images.yml runs tests/build-isolation.sh with packages: write),
// so editing one still needs a maintainer.
// - Not the upstream sync. It labels its own PR build-approved to release
// GitHub's hold on its pushes, which is no one's approval; it stays on the
// reviewed lane.
const PACKAGES = 'pkgbuilds/';
const TESTS = 'tests/';
const TEST_WORKFLOW = '.github/workflows/test.yml';
Expand All @@ -38,7 +35,6 @@ function packageChange(file) {
if (file.filename === TEST_WORKFLOW) return file.status === 'modified' && onlyRunsTests(file.patch);
return false;
}
const REVIEWED_BRANCHES = /^auto\/sync-upstream(\/|$)/;

function decide({ pr, files, vouchStatus, repository }) {
if (pr.state !== 'open') return { enable: false, reason: 'PR is not open' };
Expand All @@ -55,10 +51,6 @@ function decide({ pr, files, vouchStatus, repository }) {
return { enable: false, reason: `author not trusted to build (${vouchStatus || 'missing'}${labelled ? ', labelled' : ''})` };
}

if (pr.head.repo?.full_name === repository && REVIEWED_BRANCHES.test(pr.head.ref)) {
return { enable: false, reason: `${pr.head.ref} stays on the reviewed lane` };
}

if (!files.length) return { enable: false, reason: 'PR changes no files' };
const outside = files.filter(file => !packageChange(file));
if (outside.length) {
Expand Down
36 changes: 36 additions & 0 deletions .github/scripts/package-pr-groups.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
# Usage: package-pr-groups.sh BRANCH_PREFIX PATCH_OUT
#
# Run from the repository root after a sync has rewritten recipes in the
# working tree. Writes those changes to PATCH_OUT and prints, as one JSON
# line, the PRs they split into: [{"branch": ..., "packages": "a b"}, ...].
#
# One PR per package, so a package whose build fails holds back only itself.
# Packages pinned from the same upstream branch (omarchy-dev and
# omarchy-settings-dev) share a PR: sync-upstream moves them in lockstep, and
# merging one without the other would ship the pair from different commits.
# Branch names come from sync-pr-branch.sh, so a scheduled run and a dispatch
# naming the same packages update the same PR.
set -euo pipefail

prefix=${1:?branch prefix required}
patch=${2:?patch path required}
scripts=$(dirname "$0")

git diff --binary -- pkgbuilds > "$patch"

declare -A members=()
while IFS= read -r package; do
key=$(jq -r '(.upstream.watch? | objects | select(has("git_branch")) | "\(.git_branch)#\(.branch)") // empty' \
"pkgbuilds/$package/.omarchy/package.json" 2>/dev/null || true)
members["${key:-$package}"]+="$package "
done < <(git diff --name-only -- pkgbuilds | cut -d/ -f2 | sort -u)

groups='[]'
for key in "${!members[@]}"; do
read -r -a packages <<<"${members[$key]}"
branch=$("$scripts/sync-pr-branch.sh" "$prefix" "${packages[@]}" | sed -n 's/^branch=//p')
groups=$(jq -c --arg branch "$branch" --arg packages "${packages[*]}" \
'. + [{branch: $branch, packages: $packages}]' <<<"$groups")
done
jq -c 'sort_by(.branch)' <<<"$groups"
4 changes: 2 additions & 2 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:
required: true

# A new push normally cancels the PR's in-flight build. The bots' branches
# (auto/sync-* and auto/track-branches) are the exception: they are
# (auto/sync-* and auto/track-branches-*) are the exception: they are
# force-pushed with fresh upstream releases several times a day, which kept
# cancelling multi-hour aarch64 builds before they could finish. There the
# newest run waits instead (GitHub keeps at most one pending run per group,
Expand All @@ -31,7 +31,7 @@ on:
# runs at once; cancelling the first left a red `result` on the PR (#901).
concurrency:
group: build-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ !(github.event.pull_request.head.repo.full_name == github.repository && (startsWith(github.head_ref, 'auto/sync-') || github.head_ref == 'auto/track-branches')) }}
cancel-in-progress: ${{ !(github.event.pull_request.head.repo.full_name == github.repository && (startsWith(github.head_ref, 'auto/sync-') || startsWith(github.head_ref, 'auto/track-branches'))) }}

jobs:
# Builds cost real machines, so they run only for trusted authors:
Expand Down
134 changes: 134 additions & 0 deletions .github/workflows/package-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Open package PRs

# Shared by the three bot lanes (sync-upstream.yml, sync-rebuilds.yml and
# track-branches.yml). The caller's sync job rewrites recipes, groups the
# changes with .github/scripts/package-pr-groups.sh and uploads them as the
# `package-pins` artifact. This opens or updates one PR per group and arms
# auto-merge, so every green package lands on its own and only the failures
# stay open. A single batch PR let one red package hold back every other one
# (#788, #837).
#
# Everything goes through the PAT in PKGS_BOT_TOKEN: its pushes build without
# an approval hold, and a merge made with it starts publish.yml. A merge made
# with the built-in GITHUB_TOKEN would not publish.

on:
workflow_call:
inputs:
groups:
description: 'JSON from package-pr-groups.sh: [{"branch", "packages"}]'
required: true
type: string
prefix:
description: 'Branch prefix the caller owns, e.g. auto/sync-upstream'
required: true
type: string
title:
description: 'PR title; ": <package> <version>, ..." is appended'
required: true
type: string
body:
description: 'PR body'
required: true
type: string
secrets:
PKGS_BOT_TOKEN:
required: true

permissions:
contents: read

jobs:
pr:
if: ${{ inputs.groups != '[]' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 4
matrix:
group: ${{ fromJSON(inputs.groups) }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Fetch the pins
uses: actions/download-artifact@v4
with:
name: package-pins
path: ${{ runner.temp }}

# The PR title names what moved, so the merged history reads like a
# changelog: "Sync upstream releases: zed 1.23.2-1".
- name: Apply this PR's packages
id: apply
env:
PACKAGES: ${{ matrix.group.packages }}
TITLE: ${{ inputs.title }}
run: |
read -r -a packages <<<"$PACKAGES"
git apply $(printf -- '--include=pkgbuilds/%s/* ' "${packages[@]}") "$RUNNER_TEMP/pins.patch"
git status --porcelain
field() { sed -n "s/^$2=//p" "pkgbuilds/$1/PKGBUILD" | head -1 | tr -d "\"'"; }
versions=$(for package in "${packages[@]}"; do
printf '%s %s-%s, ' "$package" "$(field "$package" pkgver)" "$(field "$package" pkgrel)"
done)
title="$TITLE: ${versions%, }"
echo "title=$title" >> "$GITHUB_OUTPUT"
echo "- $title" >> "$GITHUB_STEP_SUMMARY"

- name: Open or update the PR
id: pr
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.PKGS_BOT_TOKEN }}
commit-message: ${{ steps.apply.outputs.title }}
title: ${{ steps.apply.outputs.title }}
body: ${{ inputs.body }}
branch: ${{ matrix.group.branch }}
delete-branch: true
labels: automated

# Auto-merge, not a direct merge: branch protection still has to see
# `result`, `self-tests` and `build-isolation` green. auto-merge-pr.yml
# may arm the same PR at the same moment; either one arming it is the
# outcome wanted.
- name: Enable auto-merge
if: ${{ steps.pr.outputs.pull-request-number != '' }}
env:
GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }}
PR: ${{ steps.pr.outputs.pull-request-number }}
run: |
armed() { [[ "$(gh pr view "$PR" -R "$GITHUB_REPOSITORY" --json autoMergeRequest --jq '.autoMergeRequest != null')" == true ]]; }
if armed; then
echo "auto-merge already enabled on #$PR"
exit 0
fi
gh pr merge --auto --merge "$PR" -R "$GITHUB_REPOSITORY" || armed

# A PR on this lane's prefix that the run did not refresh, and that
# conflicts with master, is stale: master moved its packages on without it.
# Close it rather than leave a red PR behind. Not being refreshed is not
# enough on its own, since a feed that failed this run (one of dozens timing
# out is routine) leaves a still-valid PR unrefreshed too. The lane's old
# batch PR, on the bare prefix, is retired outright.
close-superseded:
runs-on: ubuntu-latest
steps:
- name: Close superseded PRs
env:
GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }}
GROUPS_JSON: ${{ inputs.groups }}
PREFIX: ${{ inputs.prefix }}
run: |
gh pr list -R "$GITHUB_REPOSITORY" --state open --limit 500 --json number,headRefName,mergeable \
--jq '.[] | select(.headRefName == env.PREFIX or (.headRefName | startswith(env.PREFIX + "-"))) | "\(.number) \(.headRefName) \(.mergeable)"' |
while read -r number branch mergeable; do
jq -e --arg branch "$branch" 'any(.[]; .branch == $branch)' <<<"$GROUPS_JSON" >/dev/null && continue
[[ $branch == "$PREFIX" || $mergeable == CONFLICTING ]] || continue
echo "closing #$number ($branch): superseded"
gh pr close "$number" -R "$GITHUB_REPOSITORY" --delete-branch \
--comment "Superseded: master has moved these packages on, or per-package PRs carry them now."
done
Loading
Loading