diff --git a/.github/ci/bind920_candidate.py b/.github/ci/bind920_candidate.py index 223e4759d2..33c21e9c89 100644 --- a/.github/ci/bind920_candidate.py +++ b/.github/ci/bind920_candidate.py @@ -12,6 +12,7 @@ BIND_SERIES = (9, 20) +BIND_UPSTREAM_REPOSITORY = "https://github.com/isc-projects/bind9.git" DISTVERSION_PATTERN = re.compile(r"^DISTVERSION[?+]?=\s*([0-9]+\.[0-9]+\.[0-9]+)\s*$", re.MULTILINE) PORTREVISION_PATTERN = re.compile(r"^PORTREVISION[?+]?=\s*([0-9]+)\s*$", re.MULTILINE) DISTINFO_SHA256_PATTERN = re.compile(r"^SHA256\s+\(bind-([0-9]+\.[0-9]+\.[0-9]+)\.tar\.[^)]+\)\s+=\s+(\S+)\s*$", re.MULTILINE) @@ -275,6 +276,27 @@ def render_assessment_markdown(assessment: Assessment) -> str: ) +def upstream_bind_release_tag(version: str) -> str: + distversion = version.split("_", maxsplit=1)[0] + _version_tuple(distversion) + return f"v{distversion}" + + +def render_commit_log_markdown(title: str, commit_log_text: str, empty_message: str) -> str: + lines: list[str] = [] + for raw_line in commit_log_text.splitlines(): + line = raw_line.strip() + if not line: + continue + parts = line.split(maxsplit=1) + commit = parts[0] + subject = parts[1] if len(parts) > 1 else "" + lines.append(f"- `{commit}` {subject}".rstrip()) + if not lines: + lines = [f"- {empty_message}"] + return f"### {title}\n\n" + "\n".join(lines) + "\n" + + def main() -> None: parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest="command", required=True) @@ -295,6 +317,13 @@ def main() -> None: assess_parser.add_argument("--old-makefile", type=Path) assess_parser.add_argument("--new-makefile", type=Path) assess_parser.add_argument("--output", type=Path, required=True) + tag_parser = subparsers.add_parser("upstream-tag") + tag_parser.add_argument("--version", required=True) + render_parser = subparsers.add_parser("render-commit-log") + render_parser.add_argument("--title", required=True) + render_parser.add_argument("--commits", type=Path, required=True) + render_parser.add_argument("--empty-message", required=True) + render_parser.add_argument("--output", type=Path, required=True) arguments = parser.parse_args() if arguments.command == "update-profile": update_profile( @@ -326,6 +355,17 @@ def main() -> None: new_makefile_text, ) arguments.output.write_text(render_assessment_markdown(assessment), encoding="utf-8") + elif arguments.command == "upstream-tag": + print(upstream_bind_release_tag(arguments.version)) + elif arguments.command == "render-commit-log": + arguments.output.write_text( + render_commit_log_markdown( + arguments.title, + arguments.commits.read_text(encoding="utf-8"), + arguments.empty_message, + ), + encoding="utf-8", + ) if __name__ == "__main__": diff --git a/.github/ci/ci-tests/test_bind920_candidate.py b/.github/ci/ci-tests/test_bind920_candidate.py index fbe60fc0ec..9a0fb19050 100644 --- a/.github/ci/ci-tests/test_bind920_candidate.py +++ b/.github/ci/ci-tests/test_bind920_candidate.py @@ -29,6 +29,8 @@ candidate_is_newer = bind920_candidate.candidate_is_newer parse_bind920_makefile = bind920_candidate.parse_bind920_makefile render_updated_profile = bind920_candidate.render_updated_profile +render_commit_log_markdown = bind920_candidate.render_commit_log_markdown +upstream_bind_release_tag = bind920_candidate.upstream_bind_release_tag class Bind920CandidateTest(unittest.TestCase): @@ -188,6 +190,41 @@ def test_assessment_summary_is_stable(self) -> None: result.summary, ) + def test_upstream_bind_release_tag_strips_portrevision(self) -> None: + """Upstream BIND tags must be based on BIND versions, not FreeBSD package revisions.""" + self.assertEqual("v9.20.26", upstream_bind_release_tag("9.20.26_2")) + self.assertEqual("v9.20.27", upstream_bind_release_tag("9.20.27")) + + def test_render_commit_log_markdown_lists_commit_subjects(self) -> None: + """Candidate PRs should summarize upstream release commits without full commit bodies.""" + rendered = render_commit_log_markdown( + "Upstream BIND Changes", + "abc1234 Fix resolver crash\n" + "def5678 Improve DNSSEC validation\n", + "No upstream commits were found.", + ) + + self.assertEqual( + "### Upstream BIND Changes\n\n" + "- `abc1234` Fix resolver crash\n" + "- `def5678` Improve DNSSEC validation\n", + rendered, + ) + + def test_render_commit_log_markdown_uses_fallback_when_empty(self) -> None: + """PR bodies should explain missing upstream history instead of showing a blank section.""" + rendered = render_commit_log_markdown( + "Upstream BIND Changes", + "\n", + "Could not resolve upstream BIND release tags.", + ) + + self.assertEqual( + "### Upstream BIND Changes\n\n" + "- Could not resolve upstream BIND release tags.\n", + rendered, + ) + def test_render_updated_profile_preserves_key_order_and_updates_candidate_values(self) -> None: """Profile rewrites must be stable and limited to candidate values.""" current = Bind920Profile("repo", "old", "oldmake", "olddist", "9.20.26", 1) @@ -426,11 +463,20 @@ def test_candidate_workflow_never_publishes_packages(self) -> None: def test_candidate_workflow_includes_ports_commit_subjects_in_pr_body(self) -> None: """Review PRs must show the FreeBSD Ports commits behind the candidate.""" workflow = self.workflow_text() - self.assertIn("### FreeBSD Ports Changes", workflow) - self.assertIn("while read -r commit subject; do", workflow) - self.assertIn("printf -- '- `%s` %s\\n' \"$commit\" \"$subject\"", workflow) + self.assertIn("render-commit-log", workflow) + self.assertIn("--title \"FreeBSD Ports Changes\"", workflow) self.assertIn("No dns/bind920 commits found between the pinned and candidate Ports commits.", workflow) + def test_candidate_workflow_includes_upstream_bind_commit_subjects_in_pr_body(self) -> None: + """Review PRs must show upstream BIND commits between the old and new release tags.""" + workflow = self.workflow_text() + self.assertIn("https://github.com/isc-projects/bind9.git", workflow) + self.assertIn("old_distversion=", workflow) + self.assertIn("--title \"Upstream BIND Changes\"", workflow) + self.assertIn("upstream-tag", workflow) + self.assertIn("git -C \"$RUNNER_TEMP/bind9\" log --format='%h %s' \"$old_tag..$new_tag\"", workflow) + self.assertIn("Could not resolve upstream BIND release tags.", workflow) + def test_candidate_workflow_uses_pinned_actions(self) -> None: """Workflow actions must stay pinned to immutable SHAs.""" workflow = self.workflow_text() diff --git a/.github/workflows/bind920-candidate.yml b/.github/workflows/bind920-candidate.yml index c9d2e34362..819e65d2b3 100644 --- a/.github/workflows/bind920-candidate.yml +++ b/.github/workflows/bind920-candidate.yml @@ -97,6 +97,7 @@ jobs: package_version = f"{candidate.distversion}_{candidate.portrevision}" print(f"newer={'true' if newer else 'false'}", file=output) print(f"old_version={old_package_version}", file=output) + print(f"old_distversion={current.distversion}", file=output) print(f"new_version={package_version}", file=output) print(f"candidate_distversion={candidate.distversion}", file=output) print(f"portrevision={candidate.portrevision}", file=output) @@ -115,6 +116,7 @@ jobs: PORTS_DIFF: ${{ steps.ports.outputs.ports_diff }} PORTS_LOG: ${{ steps.ports.outputs.ports_log }} CURRENT_MAKEFILE: ${{ steps.ports.outputs.current_makefile }} + BIND_UPSTREAM_REPOSITORY: https://github.com/isc-projects/bind9.git run: | set -euo pipefail printf '%s\n' "$CHANGELOG_TEXT" > "$RUNNER_TEMP/maintainer-notes.txt" @@ -139,18 +141,36 @@ jobs: --old-makefile "$CURRENT_MAKEFILE" \ --new-makefile "$PORTS_DIR/dns/bind920/Makefile" \ --output "$RUNNER_TEMP/assessment.md" + old_tag=$(python3 .github/ci/bind920_candidate.py upstream-tag \ + --version '${{ steps.candidate.outputs.old_distversion }}') + new_tag=$(python3 .github/ci/bind920_candidate.py upstream-tag \ + --version '${{ steps.candidate.outputs.candidate_distversion }}') + upstream_empty_message="No upstream BIND commits were found between $old_tag and $new_tag." + if git clone --filter=blob:none --no-checkout "$BIND_UPSTREAM_REPOSITORY" "$RUNNER_TEMP/bind9" \ + && git -C "$RUNNER_TEMP/bind9" fetch --no-tags origin \ + "refs/tags/$old_tag:refs/tags/$old_tag" \ + "refs/tags/$new_tag:refs/tags/$new_tag"; then + git -C "$RUNNER_TEMP/bind9" log --format='%h %s' "$old_tag..$new_tag" > "$RUNNER_TEMP/upstream.log" + else + upstream_empty_message="Could not resolve upstream BIND release tags." + : > "$RUNNER_TEMP/upstream.log" + fi + python3 .github/ci/bind920_candidate.py render-commit-log \ + --title "Upstream BIND Changes" \ + --commits "$RUNNER_TEMP/upstream.log" \ + --empty-message "$upstream_empty_message" \ + --output "$RUNNER_TEMP/upstream-changes.md" + python3 .github/ci/bind920_candidate.py render-commit-log \ + --title "FreeBSD Ports Changes" \ + --commits "$PORTS_LOG" \ + --empty-message "No dns/bind920 commits found between the pinned and candidate Ports commits." \ + --output "$RUNNER_TEMP/ports-changes.md" { cat "$RUNNER_TEMP/assessment.md" - printf '\n### FreeBSD Ports Changes\n\n' - if [ -s "$PORTS_LOG" ]; then - while read -r commit subject; do - if [ -n "$commit" ]; then - printf -- '- `%s` %s\n' "$commit" "$subject" - fi - done < "$PORTS_LOG" - else - printf -- '- No dns/bind920 commits found between the pinned and candidate Ports commits.\n' - fi + printf '\n' + cat "$RUNNER_TEMP/upstream-changes.md" + printf '\n' + cat "$RUNNER_TEMP/ports-changes.md" printf '\n### Candidate Inputs\n\n' printf -- '- FreeBSD Ports commit: `%s`\n' "$CANDIDATE_COMMIT" printf -- '- Previous FreeBSD Ports commit: `%s`\n' '${{ steps.ports.outputs.current_commit }}'