-
Notifications
You must be signed in to change notification settings - Fork 0
ci: add the fork-sync backport queue #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| ################################################################################ | ||
| # | ||
| # fork-sync.yml — weekly backport queue for MiSTer-devel/Linux-Kernel_MiSTer. | ||
| # | ||
| # This repo is the source of truth for the MiSTer kernel, but the fork is a live repo | ||
| # other people commit to. Anything landing there after our last reconciliation is | ||
| # something we do not have and nobody has decided about. Nothing else forces that | ||
| # question -- and the question does not get asked on its own: the fork sat on 5.15.1 | ||
| # through 210 stable releases partly because no moment ever said "these N commits are | ||
| # unaccounted for". | ||
| # | ||
| # So: scripts/check-fork-sync.sh diffs docs/kernel-recon/fork-sync.conf (the last | ||
| # RECONCILED commit per fork branch) against the fork's live HEADs, and this opens an | ||
| # issue listing whatever has landed since. The queue becomes a fact rather than a memory. | ||
| # | ||
| # The direction that matters most is MiSTer-v6.18: once #75 merges upstream, commits | ||
| # landing there are changes made to OUR series by other people, and they must flow back | ||
| # into board/mister/de10nano/linux-patches/ or the next `make export` silently erases | ||
| # them. That is the failure this file exists to prevent. | ||
| # | ||
| # COST: two compare API calls and no clone -- seconds, weekly. It deliberately does NOT | ||
| # check out the kernel (~300MB) or build anything; "what is new upstream" does not need | ||
| # either. This is why it can be a schedule rather than something manual: it is far below | ||
| # the noise floor of the build workflow. (Contrast build.yml, whose cold path is ~3h.) | ||
| # | ||
| # ONE ISSUE, UPDATED -- not one per run. A weekly job that opens a fresh issue is a job | ||
| # people mute, and a muted queue is the same as no queue. It finds its own open issue by | ||
| # label, edits it in place, and closes it when the queue empties. | ||
| # | ||
| ################################################################################ | ||
|
|
||
| name: Fork sync queue | ||
|
|
||
| on: | ||
| schedule: | ||
| # Mondays 06:00 UTC. Weekly is matched to how fast the fork actually moves (11 | ||
| # commits across 2026 so far) -- daily would just re-post the same list six more | ||
| # times before anyone acts on it. | ||
| - cron: "0 6 * * 1" | ||
| workflow_dispatch: | ||
|
|
||
| # Only the issue write. No checkout of anything but this repo, no packages, no build. | ||
| permissions: | ||
| contents: read | ||
| issues: write | ||
|
|
||
| concurrency: | ||
| group: fork-sync | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| check: | ||
| name: Check fork for unreconciled commits | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
|
|
||
| # Exit 1 from the script means "commits need triage", which is a normal state and | ||
| # must not fail the job -- a red X every week trains people to ignore it. Exit 2 is | ||
| # a real error and MUST fail. The report is the product; the exit code only routes it. | ||
| # | ||
| # Run ONCE, with --markdown, and use that output for both the log and the issue | ||
| # body. An earlier cut called the script twice -- plain for the log, --markdown for | ||
| # the body with `|| true` to swallow the expected exit 1 -- which was wrong twice | ||
| # over: `|| true` also swallows exit 2, so a broken run could file an issue with an | ||
| # empty body and still go green; and two invocations are two sets of API calls that | ||
| # can disagree if one hits a transient failure. | ||
| - name: Diff fork HEADs against the reconciled sync points | ||
| id: check | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -o pipefail | ||
| rc=0 | ||
| scripts/check-fork-sync.sh --markdown > body.md 2> err.txt || rc=$? | ||
|
|
||
| case "$rc" in | ||
| 0) echo "drift=false" >> "$GITHUB_OUTPUT" ;; | ||
| 1) echo "drift=true" >> "$GITHUB_OUTPUT" ;; | ||
| *) | ||
| echo "::error title=fork-sync check failed::scripts/check-fork-sync.sh exited $rc" | ||
| cat err.txt >&2 | ||
| exit "$rc" | ||
| ;; | ||
| esac | ||
|
|
||
| # A body we would post must not be empty -- that is the failure mode the single | ||
| # invocation is meant to close, so assert it rather than assume it. | ||
| if [ ! -s body.md ]; then | ||
| echo "::error title=fork-sync produced an empty report::exit was $rc but body.md is empty" | ||
| exit 2 | ||
| fi | ||
|
|
||
| cat body.md | ||
|
|
||
| - name: Open or update the backport-queue issue | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| DRIFT: ${{ steps.check.outputs.drift }} | ||
| REPO: ${{ github.repository }} | ||
| run: | | ||
| set -euo pipefail | ||
| TITLE="Backport queue: unreconciled commits in Linux-Kernel_MiSTer" | ||
| LABEL="fork-sync" | ||
|
|
||
| # Label may not exist yet on a fresh repo; create it once, idempotently. | ||
| gh label create "$LABEL" --repo "$REPO" --color 0E8A16 \ | ||
| --description "Upstream fork commits awaiting a disposition" 2>/dev/null || true | ||
|
|
||
| # --limit 1: only the first match is used, and the default page of 30 is API | ||
| # work we never look at. | ||
| existing="$(gh issue list --repo "$REPO" --label "$LABEL" --state open \ | ||
| --limit 1 --json number --jq '.[0].number // empty')" | ||
|
|
||
| if [ "$DRIFT" = "true" ]; then | ||
| { | ||
| cat body.md | ||
| printf '\n---\n<sub>Generated by `.github/workflows/fork-sync.yml` — ' | ||
| printf 'edited in place each run, so this is always the current queue. ' | ||
| printf 'Closes itself when everything has a disposition.</sub>\n' | ||
| } > issue.md | ||
|
|
||
| if [ -n "$existing" ]; then | ||
| gh issue edit "$existing" --repo "$REPO" --body-file issue.md | ||
| echo "::notice title=Backport queue updated::Issue #$existing refreshed." | ||
| else | ||
| # `gh issue create` has no --json/--jq; it prints the issue URL. Take the | ||
| # number off the end rather than retrying the command -- a `||` fallback | ||
| # around a create is how you end up filing two issues. | ||
| url="$(gh issue create --repo "$REPO" --title "$TITLE" --label "$LABEL" \ | ||
| --body-file issue.md)" | ||
| echo "::notice title=Backport queue opened::Issue ${url##*/} lists commits needing triage." | ||
| fi | ||
| elif [ -n "$existing" ]; then | ||
| gh issue close "$existing" --repo "$REPO" \ | ||
| --comment "Everything on the watched fork branches now has a disposition in \`docs/patch-provenance.md\`, and \`docs/kernel-recon/fork-sync.conf\` has been advanced past it. Reopening automatically if the fork moves again." | ||
| echo "::notice title=Backport queue empty::Closed issue #$existing." | ||
| else | ||
| echo "::notice title=Backport queue empty::Fork fully reconciled; nothing to do." | ||
| fi |
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # fork-sync.conf — how far we have reconciled MiSTer-devel/Linux-Kernel_MiSTer. | ||
| # | ||
| # WHY THIS FILE EXISTS | ||
| # -------------------- | ||
| # This repo is the source of truth for the MiSTer kernel: the patch series in | ||
| # board/mister/de10nano/linux-patches/ is what gets built, and scripts/export-kernel-tree.sh | ||
| # renders it into the fork's tree format. But the fork is a live repo that other people | ||
| # commit to. Anything landing there after our last reconciliation is, by definition, | ||
| # something this repo does not have and nobody has decided about. | ||
| # | ||
| # Left to humans that decision never gets made -- not through neglect, but because there | ||
| # is no moment that forces the question. The fork sat on 5.15.1 for 210 stable releases | ||
| # for exactly this reason: nothing ever said "these N commits are unaccounted for". | ||
| # | ||
| # So this file records the last commit on each fork branch whose content has a disposition | ||
| # in docs/patch-provenance.md, and scripts/check-fork-sync.sh diffs it against the fork's | ||
| # live HEADs. .github/workflows/fork-sync.yml runs that weekly and opens an issue when the | ||
| # answer is non-empty. The queue is then a fact rather than a memory. | ||
| # | ||
| # FORMAT | ||
| # <branch> <full-40-char-sha> | ||
| # Comments and blank lines ignored. Full SHAs only -- an abbreviation that is unambiguous | ||
| # today can collide later, and this is compared, not just displayed. | ||
| # | ||
| # UPDATING | ||
| # Move a line ONLY when the commits it skips past have been dispositioned in | ||
| # docs/patch-provenance.md -- carried into the series, or recorded as deliberately dropped | ||
| # with a reason. Advancing it to silence the issue is the one thing that breaks this: the | ||
| # file stops meaning "reconciled" and starts meaning "seen", which is what it exists to | ||
| # prevent. | ||
| # | ||
| # WHY BOTH BRANCHES | ||
| # MiSTer-v5.15 -- the fork's historical line and the baseline the whole reconciliation | ||
| # was performed against (docs/patch-provenance.md's header names f0fb626ac; this is | ||
| # now ahead of that, see below). Still actively committed to. | ||
| # MiSTer-v6.18 -- upstream's own vanilla 6.18.38 base, created 2026-07-16, which our | ||
| # export now replays onto (MiSTer-devel/Linux-Kernel_MiSTer#75). Once that merges, | ||
| # commits landing here are changes made to OUR series by other people, and they have | ||
| # to flow back into linux-patches/ or the next regeneration silently erases them. | ||
| # This is the direction that matters most for keeping this repo the source of truth. | ||
|
|
||
| # Reconciled: docs/patch-provenance.md dispositioned every commit up to f0fb626ac. | ||
| # Advanced to 794e6f002 on 2026-07-16: the single commit since (794e6f002, "New driver for | ||
| # RTL8821CU") is dispositioned -- that chip is covered by mainline rtw88_8821cu | ||
| # (CONFIG_RTW88_8821CU=m), which is why BR2_PACKAGE_RTL8821CU_MORROWNR is deliberately off | ||
| # in configs/mister_de10nano_defconfig. Nothing to carry. | ||
| MiSTer-v5.15 794e6f002d0f655c504733c126a01f8c1f0bc1d4 | ||
|
|
||
| # Upstream's pristine v6.18.38 base commit, and the parent our export replays onto. | ||
| # Nothing of ours or theirs sits on top of it upstream yet. | ||
| MiSTer-v6.18 d9ac12a691ead295c8bc6438754767b94c0f26a2 |
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # check-fork-sync.sh — report fork commits we have not reconciled yet. | ||
| # | ||
| # Diffs docs/kernel-recon/fork-sync.conf (the last reconciled commit per fork branch) | ||
| # against MiSTer-devel/Linux-Kernel_MiSTer's live HEADs, and prints what has landed since. | ||
| # That list is the backport queue: each commit needs a disposition in | ||
| # docs/patch-provenance.md -- carried into board/mister/de10nano/linux-patches/, or | ||
| # recorded as deliberately dropped with a reason. | ||
| # | ||
| # Read fork-sync.conf's header for why this exists at all; the short version is that | ||
| # nothing else ever forces the question, and the fork sat on 5.15.1 for 210 stable | ||
| # releases partly because of that. | ||
| # | ||
| # Cheap by construction: one compare API call per branch, no clone. The kernel repo is | ||
| # ~300MB and there is no reason to fetch it to answer "what is new". | ||
| # | ||
| # Usage: scripts/check-fork-sync.sh [--markdown] | ||
| # --markdown emit a GitHub-flavoured report (used by the workflow for issue bodies) | ||
| # | ||
| # Exit: 0 = fully reconciled; 1 = commits need triage (report on stdout); 2 = error. | ||
| # The workflow keys off 1 vs 0, so do not make drift fatal-looking; it is normal. | ||
|
|
||
| set -o errexit | ||
| set -o nounset | ||
| set -o pipefail | ||
|
|
||
| # Assigned then marked readonly separately: `readonly X="$(cmd)"` masks cmd's exit status | ||
| # (shellcheck SC2155), and the rest of scripts/ avoids that pattern. | ||
| REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | ||
| readonly REPO_ROOT | ||
| readonly CONF="$REPO_ROOT/docs/kernel-recon/fork-sync.conf" | ||
| readonly FORK="${FORK_REPO:-MiSTer-devel/Linux-Kernel_MiSTer}" | ||
|
|
||
| markdown=false | ||
| [[ ${1:-} == --markdown ]] && markdown=true | ||
|
|
||
| err() { printf 'check-fork-sync: %s\n' "$*" >&2; } | ||
|
|
||
| command -v gh >/dev/null || { err 'gh CLI not found'; exit 2; } | ||
| command -v jq >/dev/null || { err 'jq not found (this script parses the compare API with it)'; exit 2; } | ||
| [[ -f $CONF ]] || { err "no such file: $CONF"; exit 2; } | ||
|
|
||
| drift=0 | ||
| report='' | ||
|
|
||
| # One line per branch: "<branch> <sha>". Comments and blanks stripped. | ||
| while read -r branch sync; do | ||
| [[ -n ${branch:-} ]] || continue | ||
|
|
||
| [[ ${#sync} -eq 40 ]] || { err "$branch: sync point must be a full 40-char SHA, got '$sync'"; exit 2; } | ||
|
|
||
| # compare gives us ahead_by + the commit list without cloning anything. `...` is | ||
| # three-dot on purpose: we want commits reachable from the branch but not from the | ||
| # sync point, which is exactly "what landed since we last looked". | ||
| if ! cmp_json="$(gh api "repos/$FORK/compare/$sync...$branch" 2>/dev/null)"; then | ||
| # A missing branch is worth failing on: it means the fork restructured and this | ||
| # file is now describing something that does not exist. | ||
| err "cannot compare $sync...$branch in $FORK (branch gone, or SHA not an ancestor?)" | ||
| exit 2 | ||
| fi | ||
|
|
||
| ahead="$(jq -r '.ahead_by' <<<"$cmp_json")" | ||
|
|
||
| # Insist on an integer before comparing. This is not defensive padding: bash | ||
| # arithmetic treats a bare word as an unset variable, so BOTH `[[ null -eq 0 ]]` and | ||
| # `[[ "" -eq 0 ]]` evaluate TRUE. A response that parsed but had no ahead_by -- an | ||
| # auth failure, a rate limit, a partial body -- would therefore report "nothing new" | ||
| # and exit 0. A tool whose entire job is to stop commits going unaccounted for must | ||
| # not have a path where it silently says all-clear because it could not tell. | ||
| [[ $ahead =~ ^[0-9]+$ ]] || { | ||
| err "$branch: compare API returned no usable ahead_by (got '$ahead')." | ||
| err "Refusing to report 'reconciled' from a response we cannot read." | ||
| exit 2 | ||
| } | ||
|
|
||
| if [[ $ahead -eq 0 ]]; then | ||
| if $markdown; then | ||
| report+="- ✅ \`$branch\` — reconciled through \`${sync:0:9}\`, nothing new. | ||
| " | ||
| else | ||
| report+=" [ok] $branch — reconciled through ${sync:0:9}, nothing new | ||
| " | ||
| fi | ||
| continue | ||
| fi | ||
|
|
||
| drift=$((drift + 1)) | ||
| if $markdown; then | ||
| report+=" | ||
| ### \`$branch\` — $ahead commit(s) to triage | ||
|
|
||
| Reconciled through [\`${sync:0:9}\`](https://github.com/$FORK/commit/$sync). Since then: | ||
|
|
||
| | commit | subject | author | date | | ||
| |---|---|---|---| | ||
| " | ||
| report+="$(jq -r --arg f "$FORK" '.commits[] | | ||
| "| [`\(.sha[0:9])`](https://github.com/\($f)/commit/\(.sha)) | \(.commit.message | split("\n")[0] | gsub("\\|"; "\\\\|")) | \(.commit.author.name) | \(.commit.author.date[0:10]) |"' <<<"$cmp_json") | ||
| " | ||
| else | ||
| report+=" | ||
| [TRIAGE] $branch — $ahead commit(s) since ${sync:0:9} | ||
| " | ||
| report+="$(jq -r '.commits[] | " \(.sha[0:9]) \(.commit.author.date[0:10]) \(.commit.message | split("\n")[0])"' <<<"$cmp_json") | ||
| " | ||
| fi | ||
| done < <(grep -vE '^\s*(#|$)' "$CONF") | ||
|
|
||
| if $markdown; then | ||
| if ((drift)); then | ||
| printf '%s\n' "The fork has commits with no disposition in this repo. Each needs one of: | ||
|
|
||
| - **carried** → a patch in \`board/mister/de10nano/linux-patches/\`, or | ||
| - **dropped** → a row in \`docs/patch-provenance.md\` saying so, and why (superseded upstream, packaged separately, obsolete…). | ||
|
|
||
| Then advance the branch's line in \`docs/kernel-recon/fork-sync.conf\`. | ||
|
|
||
| > Advancing that file to silence this issue is the one thing that breaks the mechanism — it would stop meaning *reconciled* and start meaning *seen*. | ||
| $report" | ||
| else | ||
| printf '%s\n' "$report | ||
| Nothing to triage." | ||
| fi | ||
| else | ||
| printf '=== fork sync: %s\n%s\n' "$FORK" "$report" | ||
| if ((drift)); then | ||
| printf 'RESULT: %d branch(es) need triage — see docs/patch-provenance.md\n' "$drift" | ||
| else | ||
| printf 'RESULT: fully reconciled\n' | ||
| fi | ||
| fi | ||
|
|
||
| ((drift == 0)) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.