- Use v39 metadata DB template for legacy symbol metadata test cases in metadata_schema_catch2_test.cpp and kg_store_catch2_test.cpp to avoid running against schema v40 where symbol tables are dropped. - Use yams::test::make_temp_sqlite_path instead of raw getenv in metadata_schema_catch2_test.cpp and prune the stale portability allowlist entry. - Add site directory to SKIP_PARTS in check_repository_metadata.py to prevent local docs builds from triggering retired marker warnings. Signed-off-by: trvon <me@trevon.dev>
112 lines
3.7 KiB
Python
Executable file
112 lines
3.7 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Validate canonical and mirrored repository metadata."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
|
|
FORGEJO_URL = "https://git.trevon.dev/trevon/yams"
|
|
GITHUB_URL = "https://github.com/trvon/yams"
|
|
SOURCEHUT_MARKERS = ("https://sr.ht/~trvon/yams", "https://git.sr.ht/~trevon/yams")
|
|
|
|
EXPECTED_TEXT = {
|
|
"README.md": (GITHUB_URL, FORGEJO_URL),
|
|
"docs/index.md": (GITHUB_URL, FORGEJO_URL),
|
|
"CITATION.cff": (
|
|
f'repository-code: "{GITHUB_URL}"',
|
|
f'repository-artifact: "{FORGEJO_URL}"',
|
|
),
|
|
"mkdocs.yml": (
|
|
f"repo_url: {FORGEJO_URL}",
|
|
f"link: {GITHUB_URL}",
|
|
f"link: {FORGEJO_URL}",
|
|
),
|
|
"scripts/build-deb.sh": (
|
|
f"Homepage: {FORGEJO_URL}",
|
|
f"URL: {FORGEJO_URL}",
|
|
),
|
|
}
|
|
|
|
# These references describe historical releases or the still-active newsletter list.
|
|
SOURCEHUT_ALLOWLIST = {
|
|
"CHANGELOG.md",
|
|
"docs/newsletter.md",
|
|
"tests/scripts/check_repository_metadata.py",
|
|
}
|
|
SKIP_PARTS = {".git", "build", "builddir", "node_modules", "site", "subprojects", "third_party"}
|
|
RETIRED_PATHS = (".build.yml", "scripts/srht-collect-artifacts.sh")
|
|
|
|
|
|
def repository_files(root: Path):
|
|
# Prune before descent: filtering rglob results still traverses build/cache trees.
|
|
for directory, directories, filenames in os.walk(root, followlinks=False):
|
|
directories[:] = sorted(name for name in directories if name not in SKIP_PARTS)
|
|
for name in sorted(filenames):
|
|
path = Path(directory) / name
|
|
if name not in SKIP_PARTS and path.is_file():
|
|
yield path
|
|
|
|
|
|
def find_text_markers(path: Path, markers: tuple[str, ...]) -> set[str]:
|
|
"""Scan strict UTF-8 with bounded memory, including cross-chunk matches.
|
|
|
|
Consume the whole file even after finding every marker: invalid UTF-8 near
|
|
EOF must still reject the file exactly as the former read_text did.
|
|
"""
|
|
found: set[str] = set()
|
|
overlap = max((len(marker) for marker in markers), default=1) - 1
|
|
tail = ""
|
|
with path.open("r", encoding="utf-8") as stream:
|
|
while chunk := stream.read(65536):
|
|
text = tail + chunk
|
|
found.update(marker for marker in markers if marker in text)
|
|
tail = text[-overlap:] if overlap else ""
|
|
return found
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--root", type=Path, default=Path.cwd())
|
|
args = parser.parse_args()
|
|
root = args.root.resolve()
|
|
failures: list[str] = []
|
|
|
|
for relative, required in EXPECTED_TEXT.items():
|
|
path = root / relative
|
|
if not path.is_file():
|
|
failures.append(f"missing metadata file: {relative}")
|
|
continue
|
|
found = find_text_markers(path, required)
|
|
for value in required:
|
|
if value not in found:
|
|
failures.append(f"{relative}: missing {value!r}")
|
|
|
|
for relative in RETIRED_PATHS:
|
|
if (root / relative).exists():
|
|
failures.append(f"retired SourceHut path remains: {relative}")
|
|
|
|
for path in repository_files(root):
|
|
relative = path.relative_to(root).as_posix()
|
|
if relative in SOURCEHUT_ALLOWLIST:
|
|
continue
|
|
try:
|
|
found = find_text_markers(path, SOURCEHUT_MARKERS)
|
|
except UnicodeDecodeError:
|
|
continue
|
|
for marker in SOURCEHUT_MARKERS:
|
|
if marker in found:
|
|
failures.append(f"{relative}: retired repository URL remains: {marker}")
|
|
|
|
if failures:
|
|
for failure in failures:
|
|
print(failure)
|
|
return 1
|
|
|
|
print("repository metadata policy: clean")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|