workflows(release): remove non-patch release tasks from patch release issues (#4051)
When creating a patch release tracking issue (e.g. from /backport
comments or manually), .0-only tasks such as 'Prepare Release' and
'Create Release branch' were incorrectly retained in the template.
Update load_release_tracking_template to filter out .0-only tasks
('Prepare Release', 'Create Release branch', and 'Tag RC*') for patch
releases, and update tests to load the real issue template via runfiles.diff --git a/.github/ISSUE_TEMPLATE/BUILD.bazel b/.github/ISSUE_TEMPLATE/BUILD.bazel
new file mode 100644
index 0000000..436dbca
--- /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 f740cb3..d160dcf 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -190,9 +190,8 @@
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 dc02394..c930d19 100644
--- a/tests/tools/private/release/BUILD.bazel
+++ b/tests/tools/private/release/BUILD.bazel
@@ -18,8 +18,10 @@
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 b4f2efc..5a33d6d 100644
--- a/tests/tools/private/release/add_backports_test.py
+++ b/tests/tools/private/release/add_backports_test.py
@@ -72,6 +72,9 @@
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 81dc7e0..1842a75 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 @@
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 64fa2b5..b95601c 100644
--- a/tests/tools/private/release/release_issue_test.py
+++ b/tests/tools/private/release/release_issue_test.py
@@ -181,10 +181,12 @@
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 0b1c4e0..c1d3143 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 @@
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 @@
@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 c8f2f9d..7a3716f 100644
--- a/tools/private/release/release_issue.py
+++ b/tools/private/release/release_issue.py
@@ -7,11 +7,12 @@
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 @@
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"