From 517ca8163f7c313bdeef565bdcb9b055ce05526c Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Mon, 15 Jun 2026 20:59:52 +0300 Subject: [PATCH] ci: fix preview release baseline after stable --- .github/workflows/preview.yml | 11 ++++- scripts/preview.py | 38 +++++++++++++++-- scripts/test_preview.py | 79 +++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 7ecdef01..17830de6 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -272,13 +272,18 @@ jobs: if [ -z "$previous" ]; then previous="$(git describe --tags --match 'v[0-9]*' --abbrev=0)" fi + range_base="$(python3 scripts/preview.py range-base \ + --previous "$previous" \ + --commit '${{ needs.preflight.outputs.commit }}')" echo "commit=$previous" >> "$GITHUB_OUTPUT" + echo "range_base=$range_base" >> "$GITHUB_OUTPUT" - name: Generate notes and checksums shell: bash run: | set -euo pipefail python3 scripts/preview.py notes \ + --previous '${{ steps.previous-preview.outputs.range_base }}' \ --commit '${{ needs.preflight.outputs.commit }}' \ --build-id '${{ needs.preflight.outputs.build_id }}' \ --base-version '${{ needs.preflight.outputs.base_version }}' \ @@ -349,10 +354,12 @@ jobs: PREVIEW_TAG='${{ needs.preflight.outputs.tag }}' PREVIEW_BUILD_ID='${{ needs.preflight.outputs.build_id }}' PREVIOUS_PREVIEW_COMMIT='${{ steps.previous-preview.outputs.commit }}' + PREVIEW_RANGE_BASE='${{ steps.previous-preview.outputs.range_base }}' - echo "Scanning preview commits in $PREVIOUS_PREVIEW_COMMIT..$PREVIEW_COMMIT for refs # mentions." + echo "Previous preview commit: $PREVIOUS_PREVIEW_COMMIT" + echo "Scanning preview commits in $PREVIEW_RANGE_BASE..$PREVIEW_COMMIT for refs # mentions." mapfile -t ISSUES < <( - git log --format='%s%n%b' "$PREVIOUS_PREVIEW_COMMIT..$PREVIEW_COMMIT" \ + git log --format='%s%n%b' "$PREVIEW_RANGE_BASE..$PREVIEW_COMMIT" \ | perl -ne 'print "$1\n" if /\brefs\s+#([0-9]+)\b/i' \ | sort -nu ) diff --git a/scripts/preview.py b/scripts/preview.py index 746a157a..e173f865 100644 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -20,7 +20,6 @@ EXPECTED_ASSET_NAMES = { "windows-x86_64": "herdr-windows-x86_64.exe", } HIDDEN_SUBJECTS = ( - "release:", "docs: update website manifest", "docs: update preview manifest", "chore: approve contributor", @@ -48,8 +47,21 @@ def normalize_version(version: str) -> str: return version.strip().removeprefix("v") -def latest_stable_tag() -> str: - return run_git(["describe", "--tags", "--match", "v[0-9]*", "--abbrev=0"]) +def latest_stable_tag(ref: str | None = None) -> str: + args = ["describe", "--tags", "--match", "v[0-9]*", "--abbrev=0"] + if ref: + args.append(ref) + return run_git(args) + + +def git_is_ancestor(ancestor: str, descendant: str) -> bool: + result = subprocess.run( + ["git", "merge-base", "--is-ancestor", ancestor, descendant], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + return result.returncode == 0 def read_json(path: Path) -> dict[str, Any] | None: @@ -95,6 +107,16 @@ def commit_subjects(previous: str, commit: str) -> list[str]: return subjects +def preview_range_base(previous: str, commit: str) -> str: + try: + stable = latest_stable_tag(commit) + except subprocess.CalledProcessError: + return previous + if git_is_ancestor(previous, stable) and git_is_ancestor(stable, commit): + return stable + return previous + + def humanize_subject(subject: str) -> tuple[str, str]: match = COMMIT_RE.match(subject) if not match: @@ -258,6 +280,11 @@ def cmd_select_commit(args: argparse.Namespace) -> int: return 0 +def cmd_range_base(args: argparse.Namespace) -> int: + print(preview_range_base(args.previous, args.commit)) + return 0 + + def main() -> int: parser = argparse.ArgumentParser(description="Preview channel release helpers") sub = parser.add_subparsers(required=True) @@ -294,6 +321,11 @@ def main() -> int: select.add_argument("--ref", default="origin/master") select.set_defaults(func=cmd_select_commit) + range_base = sub.add_parser("range-base") + range_base.add_argument("--previous", required=True) + range_base.add_argument("--commit", required=True) + range_base.set_defaults(func=cmd_range_base) + args = parser.parse_args() return args.func(args) diff --git a/scripts/test_preview.py b/scripts/test_preview.py index 5cd5be93..db80b1a7 100644 --- a/scripts/test_preview.py +++ b/scripts/test_preview.py @@ -1,7 +1,9 @@ import json +import os import subprocess import tempfile import unittest +from unittest import mock from pathlib import Path import scripts.conventional_commits as conventional_commits @@ -55,8 +57,85 @@ class PreviewNotesTests(unittest.TestCase): def test_hidden_subjects_include_preview_manifest_commits(self): self.assertTrue(preview.hidden_subject("docs: update preview manifest")) + self.assertTrue(preview.hidden_subject("docs: update website manifest")) + self.assertFalse(preview.hidden_subject("release: v0.7.0")) self.assertFalse(preview.hidden_subject("fix: repair preview manifest")) + def test_latest_publishable_commit_keeps_release_commits(self): + output = "\n".join( + [ + "manifest\x00docs: update website manifest for v0.7.0", + "release\x00release: v0.7.0", + "feature\x00feat: add plugin v1 system", + ] + ) + with mock.patch.object(preview, "run_git", return_value=output): + self.assertEqual(preview.latest_publishable_commit("origin/master"), "release") + + def test_preview_range_base_advances_to_stable_tag(self): + with ( + mock.patch.object(preview, "latest_stable_tag", return_value="v0.7.0"), + mock.patch.object(preview, "git_is_ancestor", return_value=True), + ): + self.assertEqual( + preview.preview_range_base("previous-preview", "release"), + "v0.7.0", + ) + + def test_preview_range_base_keeps_previous_preview_for_unreleased_work(self): + def is_ancestor(ancestor: str, descendant: str) -> bool: + return (ancestor, descendant) == ("v0.7.0", "new-feature") + + with ( + mock.patch.object(preview, "latest_stable_tag", return_value="v0.7.0"), + mock.patch.object(preview, "git_is_ancestor", side_effect=is_ancestor), + ): + self.assertEqual( + preview.preview_range_base("previous-preview", "new-feature"), + "previous-preview", + ) + + def test_post_stable_history_selects_release_and_bases_range_on_stable_tag(self): + with tempfile.TemporaryDirectory() as tmp: + repo = Path(tmp) + + def git(*args: str) -> str: + return subprocess.check_output( + ["git", *args], + cwd=repo, + text=True, + stderr=subprocess.DEVNULL, + ).strip() + + git("init") + git("config", "user.email", "test@example.com") + git("config", "user.name", "Test User") + + marker = repo / "marker.txt" + marker.write_text("preview\n", encoding="utf-8") + git("add", "marker.txt") + git("commit", "-m", "feat: previous preview") + previous_preview = git("rev-parse", "HEAD") + + marker.write_text("release\n", encoding="utf-8") + git("commit", "-am", "release: v0.7.0") + release = git("rev-parse", "HEAD") + git("tag", "v0.7.0") + + marker.write_text("manifest\n", encoding="utf-8") + git("commit", "-am", "docs: update website manifest for v0.7.0") + + original_cwd = os.getcwd() + try: + os.chdir(repo) + self.assertEqual(preview.latest_publishable_commit("HEAD"), release) + self.assertEqual( + preview.preview_range_base(previous_preview, release), + "v0.7.0", + ) + finally: + os.chdir(original_cwd) + def test_preview_docs_rewrite_links_to_preview_namespace(self): source = """--- title: Install Herdr