diff --git a/.github/ISSUE_TEMPLATE/BUILD.bazel b/.github/ISSUE_TEMPLATE/BUILD.bazel new file mode 100644 index 0000000000..436dbcae75 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUILD.bazel @@ -0,0 +1,5 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files(glob(["*"])) diff --git a/RELEASING.md b/RELEASING.md index f740cb3fa9..d160dcf4d5 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -190,9 +190,8 @@ verification of cherry-picks using a Backport Tracking Issue. workflow. * This will automatically create a standard Release Tracking Issue for each target version (e.g., `Release 1.7.1`, `Release 1.8.1`, etc.). - * For patch releases, the created release tracking issues will have `Tag - RC` tasks automatically removed, as release candidates are not - required for patch releases. + * For patch releases, the created release tracking issue will have + non-patch release tasks removed. * The backport PR will be automatically added to the checklist of each created release tracking issue. diff --git a/tests/tools/private/release/BUILD.bazel b/tests/tools/private/release/BUILD.bazel index dc02394960..c930d194c8 100644 --- a/tests/tools/private/release/BUILD.bazel +++ b/tests/tools/private/release/BUILD.bazel @@ -18,8 +18,10 @@ py_library( name = "release_test_helper", testonly = True, srcs = ["release_test_helper.py"], + data = ["//.github/ISSUE_TEMPLATE:release_tracking_template.md"], target_compatible_with = NOT_WINDOWS, deps = [ + "//python/runfiles", "//tools/private/release:mock_gh", "//tools/private/release:release_lib", "@pypi//pytest", diff --git a/tests/tools/private/release/add_backports_test.py b/tests/tools/private/release/add_backports_test.py index b4f2efc583..5a33d6df4b 100644 --- a/tests/tools/private/release/add_backports_test.py +++ b/tests/tools/private/release/add_backports_test.py @@ -72,6 +72,9 @@ def test_add_backports_auto_discover_no_issues_creates_patch_release( assert "- [ ] #124" in body assert "- [ ] Sync Changelog #124" in body assert "Tag RC" not in body + assert "Prepare Release" not in body + assert "Create Release branch" not in body + assert "- [ ] Tag Final" in body def test_add_backports_patch_release_no_rc_added(mock_gh): diff --git a/tests/tools/private/release/backport_create_releases_test.py b/tests/tools/private/release/backport_create_releases_test.py index 81dc7e00ce..1842a75083 100644 --- a/tests/tools/private/release/backport_create_releases_test.py +++ b/tests/tools/private/release/backport_create_releases_test.py @@ -44,14 +44,23 @@ def test_create_releases_all_success(release_tool_env, mock_gh): assert 1002 in gh.issues assert 1003 in gh.issues - # 1.7.2 (patch) should not have Tag RC0 + # 1.7.2 (patch) should not have Tag RC0, Prepare Release, or Create Release branch assert gh.issues[1001]["title"] == "Release 1.7.2" assert "Tag RC0" not in gh.issues[1001]["body"] - assert "## Backports\n- [ ] #456" in gh.issues[1001]["body"] + assert "Prepare Release" not in gh.issues[1001]["body"] + assert "Create Release branch" not in gh.issues[1001]["body"] + assert "Tag Final" in gh.issues[1001]["body"] + assert "- [ ] #456" in gh.issues[1001]["body"] + assert "- [ ] Sync Changelog #456" in gh.issues[1001]["body"] - # 1.9.0 (minor) should have Tag RC0 + # 1.9.0 (minor) should have Tag RC0, Prepare Release, and Create Release branch assert gh.issues[1003]["title"] == "Release 1.9.0" assert "Tag RC0" in gh.issues[1003]["body"] + assert "Prepare Release" in gh.issues[1003]["body"] + assert "Create Release branch" in gh.issues[1003]["body"] + assert "Tag Final" in gh.issues[1003]["body"] + assert "- [ ] #456" in gh.issues[1003]["body"] + assert "- [ ] Sync Changelog #456" in gh.issues[1003]["body"] # Verify backport issue updated expected_updated_backport_body = """* PR: #456 diff --git a/tests/tools/private/release/release_issue_test.py b/tests/tools/private/release/release_issue_test.py index 64fa2b5c0c..b95601c541 100644 --- a/tests/tools/private/release/release_issue_test.py +++ b/tests/tools/private/release/release_issue_test.py @@ -181,10 +181,12 @@ def test_load_release_tracking_template(tmp_path): assert "- [ ] Tag RC0" in full_template assert "- [ ] Tag RC1" in full_template - # Patch release version (strips RC tasks) + # Patch release version (strips .0-only release tasks: Prepare Release, Create Release branch, Tag RC) patch_template = load_release_tracking_template( version="1.2.1", template_path=template_file ) assert "Tag RC" not in patch_template - assert "- [ ] Prepare Release" in patch_template + assert "Prepare Release" not in patch_template + assert "Create Release branch" not in patch_template assert "- [ ] Tag Final" in patch_template + assert "## Backports" in patch_template diff --git a/tests/tools/private/release/release_test_helper.py b/tests/tools/private/release/release_test_helper.py index 0b1c4e0351..c1d314360f 100644 --- a/tests/tools/private/release/release_test_helper.py +++ b/tests/tools/private/release/release_test_helper.py @@ -1,9 +1,11 @@ import dataclasses +import shutil from pathlib import Path from unittest.mock import MagicMock, patch import pytest +from python.runfiles import runfiles from tools.private.release.mock_gh import MockGitHub @@ -20,14 +22,17 @@ class ReleaseToolEnv: github_output_file: Path -DEFAULT_RELEASE_TEMPLATE_CONTENT = ( - "template content\n" - "- [ ] Prepare Release\n" - "- [ ] Tag RC0\n" - "- [ ] Tag Final\n" - "\n" - "## Backports\n" -) +def _find_real_template_path() -> Path: + r = runfiles.Create() + path = r.Rlocation( + "rules_python/.github/ISSUE_TEMPLATE/release_tracking_template.md" + ) + if not path or not Path(path).is_file(): + raise FileNotFoundError( + "Could not locate .github/ISSUE_TEMPLATE/release_tracking_template.md" + f" in runfiles: {path}" + ) + return Path(path) @pytest.fixture(name="mock_git") @@ -52,11 +57,12 @@ def fixture_mock_gh(): @pytest.fixture(name="release_tool_env") def fixture_release_tool_env(tmp_path, monkeypatch): """Fixture providing a temp cwd with release template set up.""" + source_template = _find_real_template_path() monkeypatch.chdir(tmp_path) template_dir = tmp_path / ".github" / "ISSUE_TEMPLATE" template_dir.mkdir(parents=True, exist_ok=True) template_file = template_dir / "release_tracking_template.md" - template_file.write_text(DEFAULT_RELEASE_TEMPLATE_CONTENT, encoding="utf-8") + shutil.copy2(source_template, template_file) github_output_file = tmp_path / "github_output" monkeypatch.setenv("GITHUB_OUTPUT", str(github_output_file)) yield ReleaseToolEnv(git_root=tmp_path, github_output_file=github_output_file) diff --git a/tools/private/release/release_issue.py b/tools/private/release/release_issue.py index c8f2f9d1ff..7a3716fab4 100644 --- a/tools/private/release/release_issue.py +++ b/tools/private/release/release_issue.py @@ -7,11 +7,12 @@ def load_release_tracking_template( version: str | None = None, template_path: pathlib.Path | None = None, ) -> str: - """Loads the release tracking issue template, stripping RC tasks for patch releases. + """Loads the release tracking issue template, stripping non-patch tasks for patch releases. Args: version: Optional version string (e.g. '1.2.1'). If provided and represents a - patch release (i.e. does not end in '.0'), strips Tag RC tasks from the template. + patch release (i.e. does not end in '.0'), strips .0-only release tasks + ('Prepare Release', 'Create Release branch', and 'Tag RC' tasks) from the template. template_path: Optional path to the template file. Defaults to .github/ISSUE_TEMPLATE/release_tracking_template.md. @@ -29,8 +30,19 @@ def load_release_tracking_template( is_patch = version is not None and not version.endswith(".0") if is_patch: lines = template_content.splitlines() - lines = [line for line in lines if not re.search(r"Tag RC\d+", line)] - template_content = "\n".join(lines) + filtered_lines = [] + for line in lines: + parsed = parse_metadata_line(line) + if parsed: + name_lower = parsed["name"].lower() + if "prepare release" in name_lower: + continue + if "create release branch" in name_lower: + continue + if re.match(r"^tag rc\d+", name_lower): + continue + filtered_lines.append(line) + template_content = "\n".join(filtered_lines) if not template_content.endswith("\n"): template_content += "\n"