refactor: unify markdown release tracking blocks
This commit is contained in:
1 parent
23f0456622
commit
14dc37046d
8 files changed
+467
-114
No files matched your search
@@ -13,6 +13,73 @@ SCRIPT = Path(__file__).with_name("track_gitea_release.py")
|
||||
SPEC = importlib.util.spec_from_file_location("track_gitea_release", SCRIPT)
|
||||
TRACKER = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(TRACKER)
|
||||
RELEASE_URL = "https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest"
|
||||
FIXTURES = {
|
||||
"COMPATIBILITY.md": """# Gitea Compatibility
|
||||
<!-- DOC-TAGS: {"TRACKER":["LATEST-VERIFIED","VERSION-MAP","HISTORY"]} -->
|
||||
| Template Release | Min Gitea | Max Tested Gitea | Status |
|
||||
|---|---|---|---|
|
||||
| **v28.0.0** | **28.0.0** | **28.0.0** | [PASS] Active |
|
||||
<!-- TRACKER:LATEST-VERIFIED -->
|
||||
> **Latest verified:** Release v28.0.0 passes tests.
|
||||
<!-- /TRACKER:LATEST-VERIFIED -->
|
||||
<!-- TRACKER:VERSION-MAP -->
|
||||
| Gitea version | Template release |
|
||||
|---|---|
|
||||
| 28.1.0 | [PENDING] Compatibility verification; no tested template release yet |
|
||||
| 28.0.0 | **v28.0.0** |
|
||||
<!-- /TRACKER:VERSION-MAP -->
|
||||
<!-- TRACKER:HISTORY -->
|
||||
| Gitea | Release Date | Mail Template Changes | Breaking? |
|
||||
|---|---|---|---|
|
||||
| **28.1.0** | 2026-10-06 | [PENDING] Mail-template compatibility verification | TBD |
|
||||
| **28.0.0** | 2026-09-29 | FormatByteSize | Yes |
|
||||
| **≤ 1.24.x** | — | Old directory | [UNSUPPORTED] |
|
||||
<!-- /TRACKER:HISTORY -->
|
||||
""",
|
||||
"README.md": f"""# Gitea Mail Templates
|
||||
<!-- DOC-TAGS: {{"TRACKER":["BADGE","LATEST-TESTED","UPSTREAM"],"RELEASE":["HEADER","SUMMARY"]}} -->
|
||||
<!-- RELEASE:HEADER -->
|
||||
> Latest Release: [v28.0.0]({RELEASE_URL})
|
||||
<!-- /RELEASE:HEADER -->
|
||||
<!-- TRACKER:BADGE -->
|
||||
[](COMPATIBILITY.md)
|
||||
<!-- /TRACKER:BADGE -->
|
||||
<!-- TRACKER:LATEST-TESTED -->
|
||||
- **Latest tested:** Gitea 28.0.0
|
||||
<!-- /TRACKER:LATEST-TESTED -->
|
||||
<!-- RELEASE:SUMMARY -->
|
||||
- **Latest release:** [v28.0.0]({RELEASE_URL})
|
||||
<!-- /RELEASE:SUMMARY -->
|
||||
<!-- TRACKER:UPSTREAM -->
|
||||
- **Upstream Gitea 28.1.0:** [PENDING]
|
||||
<!-- /TRACKER:UPSTREAM -->
|
||||
""",
|
||||
"AGENTS.md": """# Agents
|
||||
<!-- DOC-TAGS: {"TRACKER":["UPSTREAM"],"RELEASE":["CURRENT"]} -->
|
||||
<!-- RELEASE:CURRENT -->
|
||||
- Current template release: **v28.0.0**.
|
||||
<!-- /RELEASE:CURRENT -->
|
||||
<!-- TRACKER:UPSTREAM -->
|
||||
- Latest upstream Gitea release: 28.1.0 [PENDING].
|
||||
<!-- /TRACKER:UPSTREAM -->
|
||||
""",
|
||||
"docs/README.zh-CN.md": f"""# Gitea 邮件模板
|
||||
<!-- DOC-TAGS: {{"TRACKER":["LATEST-TESTED","UPSTREAM"],"RELEASE":["HEADER","SUMMARY"]}} -->
|
||||
<!-- RELEASE:HEADER -->
|
||||
> 最新发布版:[v28.0.0]({RELEASE_URL})
|
||||
<!-- /RELEASE:HEADER -->
|
||||
<!-- TRACKER:LATEST-TESTED -->
|
||||
- **最新测试:** Gitea 28.0.0
|
||||
<!-- /TRACKER:LATEST-TESTED -->
|
||||
<!-- RELEASE:SUMMARY -->
|
||||
- **最新发布版:** [v28.0.0]({RELEASE_URL})
|
||||
<!-- /RELEASE:SUMMARY -->
|
||||
<!-- TRACKER:UPSTREAM -->
|
||||
- **上游 Gitea 28.1.0:** [PENDING]
|
||||
<!-- /TRACKER:UPSTREAM -->
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
class TrackerTests(unittest.TestCase):
|
||||
@@ -25,7 +92,7 @@ class TrackerTests(unittest.TestCase):
|
||||
for name in self.paths:
|
||||
destination = self.root / name
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
content = (TRACKER.ROOT / name).read_text(encoding="utf-8")
|
||||
content = FIXTURES[name]
|
||||
destination.write_text(content, encoding="utf-8")
|
||||
self.originals[name] = content
|
||||
|
||||
@@ -52,6 +119,13 @@ class TrackerTests(unittest.TestCase):
|
||||
self.assertIn("最新测试:** Gitea 28.0.0", self.read("docs/README.zh-CN.md"))
|
||||
self.assertEqual([], TRACKER.apply_release(self.root, "28.2.0", "2026-11-01"))
|
||||
|
||||
def test_repository_markers_are_valid(self):
|
||||
documents, blocks = TRACKER.documents(TRACKER.ROOT)
|
||||
self.assertTrue({TRACKER.ROOT / name for name in self.paths}.issubset(documents))
|
||||
self.assertIn(("TRACKER", "HISTORY"), {(block.subject, block.content) for block in blocks})
|
||||
self.assertIn(("RELEASE", "HEADER"), {(block.subject, block.content) for block in blocks})
|
||||
TRACKER.latest_tested(documents, blocks)
|
||||
|
||||
def test_existing_version_is_idempotent(self):
|
||||
self.assertEqual([], TRACKER.apply_release(self.root, "28.1.0", "2026-10-06"))
|
||||
self.assert_unchanged()
|
||||
@@ -73,24 +147,64 @@ class TrackerTests(unittest.TestCase):
|
||||
|
||||
def test_missing_marker_fails_without_writing(self):
|
||||
compatibility = self.root / "COMPATIBILITY.md"
|
||||
compatibility.write_text(self.read("COMPATIBILITY.md").replace("<!-- TRACKER:HISTORY -->", ""), encoding="utf-8")
|
||||
compatibility.write_text(self.read("COMPATIBILITY.md").replace("<!-- TRACKER:HISTORY -->\n", ""), encoding="utf-8")
|
||||
with self.assertRaises(ValueError):
|
||||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||||
self.assertEqual(self.originals["README.md"], self.read("README.md"))
|
||||
|
||||
def test_unknown_marker_fails_without_writing(self):
|
||||
news = self.root / "docs/NEWS.md"
|
||||
news.write_text("Gitea 28.1.0 [PENDING] <!-- TRACKER:FUTURE-UNKNOWN -->\n", encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "Unknown TRACKER marker"):
|
||||
news.write_text('<!-- DOC-TAGS: {"TRACKER":["FUTURE-UNKNOWN"]} -->\n', encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "Unknown TRACKER content"):
|
||||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||||
self.assert_unchanged()
|
||||
|
||||
def test_new_marked_document_is_discovered(self):
|
||||
news = self.root / "docs/NEWS.md"
|
||||
news.write_text("Gitea 28.1.0 [PENDING] <!-- TRACKER:UPSTREAM -->\n", encoding="utf-8")
|
||||
news = self.root / "misc/notes/NEWS.md"
|
||||
news.parent.mkdir(parents=True)
|
||||
news.write_text('<!-- DOC-TAGS: {"TRACKER":["UPSTREAM"]} -->\n<!-- TRACKER:UPSTREAM -->\nGitea 28.1.0 [PENDING]\n<!-- /TRACKER:UPSTREAM -->\n', encoding="utf-8")
|
||||
changed = TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||||
self.assertIn(news, changed)
|
||||
self.assertEqual("Gitea 28.2.0 [PENDING] <!-- TRACKER:UPSTREAM -->\n", news.read_text(encoding="utf-8"))
|
||||
self.assertIn("Gitea 28.2.0 [PENDING]", news.read_text(encoding="utf-8"))
|
||||
|
||||
def test_duplicate_block_uses_first_in_document_order(self):
|
||||
english = self.root / "README.md"
|
||||
duplicate = "<!-- TRACKER:UPSTREAM -->\nGitea 28.1.0 [PENDING]\n<!-- /TRACKER:UPSTREAM -->\n"
|
||||
english.write_text(self.read("README.md") + duplicate, encoding="utf-8")
|
||||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||||
self.assertIn("**Upstream Gitea 28.2.0:** [PENDING]", self.read("README.md"))
|
||||
self.assertTrue(self.read("README.md").endswith(duplicate))
|
||||
|
||||
def test_unclosed_block_rejected_without_writing(self):
|
||||
english = self.root / "README.md"
|
||||
english.write_text(self.read("README.md").replace("<!-- /TRACKER:UPSTREAM -->", ""), encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "Unclosed tag block"):
|
||||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||||
self.assertEqual(self.originals["COMPATIBILITY.md"], self.read("COMPATIBILITY.md"))
|
||||
|
||||
def test_undeclared_block_rejected(self):
|
||||
english = self.root / "README.md"
|
||||
english.write_text(self.read("README.md") + "<!-- RELEASE:CURRENT -->\nv28.0.0\n<!-- /RELEASE:CURRENT -->\n", encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "Undeclared RELEASE:CURRENT"):
|
||||
TRACKER.apply_template_release(self.root, "v28.0.1")
|
||||
|
||||
def test_misspelled_subject_rejected(self):
|
||||
english = self.root / "README.md"
|
||||
english.write_text(self.read("README.md").replace("<!-- TRACKER:UPSTREAM -->", "<!-- TRACER:UPSTREAM -->"), encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "Undeclared TRACER:UPSTREAM"):
|
||||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||||
|
||||
def test_block_without_manifest_rejected(self):
|
||||
news = self.root / "NEWS.md"
|
||||
news.write_text("<!-- TRACKER:UPSTREAM -->\nGitea 28.1.0 [PENDING]\n<!-- /TRACKER:UPSTREAM -->\n", encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "without DOC-TAGS"):
|
||||
TRACKER.apply_release(self.root, "28.2.0", "2026-11-01")
|
||||
|
||||
def test_mismatched_close_rejected(self):
|
||||
english = self.root / "README.md"
|
||||
english.write_text(self.read("README.md").replace("<!-- /RELEASE:SUMMARY -->", "<!-- /RELEASE:HEADER -->"), encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "Unmatched closing tag"):
|
||||
TRACKER.apply_template_release(self.root, "v28.0.1")
|
||||
|
||||
def test_disagreeing_tested_versions_fail(self):
|
||||
chinese = self.root / "docs/README.zh-CN.md"
|
||||
@@ -120,6 +234,37 @@ class TrackerTests(unittest.TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
TRACKER.apply_release(self.root, "28.2.0-rc1", "2026-11-01")
|
||||
|
||||
def test_template_release_updates_all_marked_labels(self):
|
||||
changed = TRACKER.apply_template_release(self.root, "v28.0.1")
|
||||
self.assertEqual({self.root / name for name in ("README.md", "docs/README.zh-CN.md", "AGENTS.md")}, set(changed))
|
||||
for name in ("README.md", "docs/README.zh-CN.md"):
|
||||
self.assertEqual(2, self.read(name).count("[v28.0.1](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)"))
|
||||
self.assertNotIn("/releases/tag/v28.0.1", self.read(name))
|
||||
self.assertIn("Current template release: **v28.0.1**", self.read("AGENTS.md"))
|
||||
self.assertEqual(self.originals["COMPATIBILITY.md"], self.read("COMPATIBILITY.md"))
|
||||
self.assertEqual([], TRACKER.apply_template_release(self.root, "v28.0.1"))
|
||||
self.assertEqual([], TRACKER.apply_template_release(self.root, "v28.0.0"))
|
||||
|
||||
def test_template_release_dry_run_does_not_write(self):
|
||||
self.assertEqual(3, len(TRACKER.apply_template_release(self.root, "v28.0.1", dry_run=True)))
|
||||
self.assert_unchanged()
|
||||
|
||||
def test_template_release_rejects_mismatched_labels(self):
|
||||
english = self.root / "README.md"
|
||||
english.write_text(self.read("README.md").replace("Latest Release: [v28.0.0]", "Latest Release: [v28.0.1]"), encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "disagree"):
|
||||
TRACKER.apply_template_release(self.root, "v28.0.2")
|
||||
|
||||
def test_template_release_rejects_tagged_link(self):
|
||||
english = self.root / "README.md"
|
||||
english.write_text(self.read("README.md").replace("[v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/latest)", "[v28.0.0](https://gitea.kenanzhu.com/KenanZhu/GiteaMailTemplates/releases/tag/v28.0.0)", 1), encoding="utf-8")
|
||||
with self.assertRaisesRegex(ValueError, "/releases/latest"):
|
||||
TRACKER.apply_template_release(self.root, "v28.0.1")
|
||||
|
||||
def test_template_release_rejects_invalid_tag(self):
|
||||
with self.assertRaises(ValueError):
|
||||
TRACKER.apply_template_release(self.root, "v28.0.1-rc1")
|
||||
|
||||
def test_release_api_uses_published_date(self):
|
||||
payload = {"tag_name": "v28.2.0", "published_at": "2026-11-01T08:15:00Z", "draft": False, "prerelease": False}
|
||||
with patch.object(TRACKER, "urlopen", return_value=io.BytesIO(json.dumps(payload).encode("utf-8"))) as mocked:
|
||||
|
||||
Reference in new issue
Block a user