diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 5018e7046..21642c491 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -54,10 +54,33 @@ permissions: contents: read concurrency: - # A pull request's newer push supersedes its older run. Runs on main are - # never cancelled: desktop-release.yml needs every main commit's own result. - group: packages-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} + # A pull request's newer push supersedes its older run. + # + # On main, desktop-release.yml waits for the Packages run of the newest + # first-parent commit that changed packages/desktop (#1398). A push whose + # commits touch packages/desktop/ therefore keeps its own group and is never + # cancelled. Every other push to main shares one group, so a newer one + # cancels it instead of queueing behind it. The test is conservative: any + # mention of packages/desktop/ in the push payload (file lists or messages) + # counts as a Desktop change. A push with no commit list, or with 20 or more + # commits (GitHub truncates the payload's list at 20, which could hide a + # Desktop change), keeps its own group. Merge queue groups and dispatches + # are never cancelled. + group: >- + packages-${{ + github.event.pull_request.number + || (github.event_name == 'push' + && github.event.head_commit + && !github.event.commits[19] + && !contains(toJSON(github.event.commits), 'packages/desktop/') + && 'main-without-desktop') + || github.sha }} + cancel-in-progress: >- + ${{ github.event_name == 'pull_request' + || (github.event_name == 'push' + && github.event.head_commit != null + && !github.event.commits[19] + && !contains(toJSON(github.event.commits), 'packages/desktop/')) }} env: PIP_DISABLE_PIP_VERSION_CHECK: "1" diff --git a/tests/test_packages_concurrency.py b/tests/test_packages_concurrency.py new file mode 100644 index 000000000..bae67ecef --- /dev/null +++ b/tests/test_packages_concurrency.py @@ -0,0 +1,37 @@ +"""Packages runs on main: only runs the Desktop release never reads are cancelled. + +desktop-release.yml waits for the Packages push run of the newest first-parent +commit that changed packages/desktop (#1398). That run must never be cancelled. +Any other main run may be superseded by a newer push. +""" + +from __future__ import annotations + +from pathlib import Path + +import yaml + +WORKFLOW = ( + Path(__file__).resolve().parents[1] / ".github" / "workflows" / "packages.yml" +) + + +def _concurrency() -> dict[str, str]: + return yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))["concurrency"] + + +def test_desktop_changes_keep_their_own_uncancelled_group() -> None: + group = " ".join(_concurrency()["group"].split()) + cancel = " ".join(_concurrency()["cancel-in-progress"].split()) + desktop = "!contains(toJSON(github.event.commits), 'packages/desktop/')" + # GitHub truncates the payload's commit list at 20, so a push of 20 or more + # commits could hide a Desktop change: it keeps its own group. + short = "!github.event.commits[19]" + shared = f"github.event_name == 'push' && github.event.head_commit && {short} && {desktop}" + # Only a short push without a Desktop change joins the cancellable group. + assert f"{shared} && 'main-without-desktop'" in group + assert group.endswith("|| github.sha }}") + pushed = "github.event_name == 'push' && github.event.head_commit != null" + assert f"{pushed} && {short} && {desktop}" in cancel + # Merge queue groups and dispatches are never cancelled. + assert "merge_group" not in cancel and "workflow_dispatch" not in cancel