Replace 46 blind `except Exception` handlers with the exception types the guarded code can actually raise, so an unforeseen failure surfaces instead of being silently swallowed. The types were derived per site from the try block: file reads get (OSError, UnicodeDecodeError), TOML and YAML parsing add their decode errors, int() guards get ValueError, imports get ImportError, subprocess calls get (OSError, subprocess.SubprocessError), git queries get GitRunError, and importlib.metadata lookups get PackageNotFoundError. Three handlers stay blind, now with a stated reason: the per-repository loops in install and update are batch boundaries where one failing repository must never abort the run. Three imports come along because the narrowing needs them: GitRunError in the changelog and version commands, and http.client in token validation -- urllib raises HTTPException, which is not an OSError subclass and would otherwise have escaped. Add tests/integration/test_error_path_degradation.py, which drives 44 of the 46 handlers into their except branch and asserts the documented degradation. Faults are injected for real rather than mocked wherever possible: undecodable bytes, malformed YAML, digit strings past sys.get_int_max_str_digits(), a real HTTPError with an unreadable body. That approach paid for itself immediately -- tomllib.load() decodes internally and raises UnicodeDecodeError, which was missing from the pyproject handlers and would have turned a malformed pyproject.toml into a traceback. The two handlers left uncovered are the branch-close path in _release_impl, reachable only through a full release, and the jinja2 import guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
81 lines
2.1 KiB
Python
81 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from datetime import datetime
|
|
|
|
from pkgmgr.core.git.queries import get_config_value
|
|
|
|
|
|
def _get_debian_author() -> tuple[str, str]:
|
|
name = os.environ.get("DEBFULLNAME")
|
|
email = os.environ.get("DEBEMAIL")
|
|
|
|
if not name:
|
|
name = os.environ.get("GIT_AUTHOR_NAME")
|
|
if not email:
|
|
email = os.environ.get("GIT_AUTHOR_EMAIL")
|
|
|
|
if not name:
|
|
name = get_config_value("user.name")
|
|
if not email:
|
|
email = get_config_value("user.email")
|
|
|
|
if not name:
|
|
name = "Unknown Maintainer"
|
|
if not email:
|
|
email = "unknown@example.com"
|
|
|
|
return name, email
|
|
|
|
|
|
def update_debian_changelog(
|
|
debian_changelog_path: str,
|
|
package_name: str,
|
|
new_version: str,
|
|
message: str | None = None,
|
|
preview: bool = False,
|
|
) -> None:
|
|
if not os.path.exists(debian_changelog_path):
|
|
print("[INFO] debian/changelog not found, skipping.")
|
|
return
|
|
|
|
debian_version = f"{new_version}-1"
|
|
now = datetime.now().astimezone()
|
|
date_str = now.strftime("%a, %d %b %Y %H:%M:%S %z")
|
|
|
|
author_name, author_email = _get_debian_author()
|
|
|
|
first_line = f"{package_name} ({debian_version}) unstable; urgency=medium"
|
|
body = (
|
|
message.strip()
|
|
if message and message.strip()
|
|
else f"Automated release {new_version}."
|
|
)
|
|
indented_body = "\n".join(
|
|
f" {line}" if line.strip() else line for line in body.split("\n")
|
|
)
|
|
stanza = (
|
|
f"{first_line}\n\n"
|
|
f"{indented_body}\n\n"
|
|
f" -- {author_name} <{author_email}> {date_str}\n\n"
|
|
)
|
|
|
|
if preview:
|
|
print(
|
|
"[PREVIEW] Would prepend the following stanza to debian/changelog:\n"
|
|
f"{stanza}"
|
|
)
|
|
return
|
|
|
|
try:
|
|
with open(debian_changelog_path, encoding="utf-8") as f:
|
|
existing = f.read()
|
|
except (OSError, UnicodeDecodeError) as exc:
|
|
print(f"[WARN] Could not read debian/changelog: {exc}")
|
|
existing = ""
|
|
|
|
with open(debian_changelog_path, "w", encoding="utf-8") as f:
|
|
f.write(stanza + existing)
|
|
|
|
print(f"Updated debian/changelog with version {debian_version}")
|