mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-26 05:37:20 -05:00
feat: carry new upstream names and hashes in json
--detect-new-files and --watch-hashes were printed only by the text report and refused with --json, so check_freshness --profiles never saw a name or hash the cited code gained. Both now live on the report, and check_freshness passes them and marks such a profile stale.
This commit is contained in:
1 parent
fd9a9b14f8
commit
6877512af7
5 files changed
+181
-34
No files matched your search
@@ -1253,6 +1253,8 @@ def check_profiles(run: bool, offline: bool) -> list[Finding]:
|
||||
"--all",
|
||||
"--json",
|
||||
"--check-version",
|
||||
"--detect-new-files",
|
||||
"--watch-hashes",
|
||||
]
|
||||
if offline:
|
||||
cmd.append("--offline")
|
||||
@@ -1273,7 +1275,9 @@ def summarize_profile_sync(report: object) -> list[Finding]:
|
||||
The JSON carries the ref verdicts and the pin against the tip. A profile
|
||||
whose refs need review is stale, one the forge could not serve is unknown,
|
||||
and one whose upstream moved while every ref still anchors is reported as
|
||||
such without being counted as stale.
|
||||
such without being counted as stale. A cited file that gained a file name
|
||||
since the pin, or a hash the profile does not declare, is stale too: the
|
||||
upstream may load a file the profile does not document.
|
||||
"""
|
||||
if not isinstance(report, list):
|
||||
return [Finding("profiles", "profile_sync", ERROR, detail="report is not a list")]
|
||||
@@ -1303,7 +1307,18 @@ def summarize_profile_sync(report: object) -> list[Finding]:
|
||||
continue
|
||||
review = row.get("needs_review") or 0
|
||||
pin, head = str(row.get("pin") or ""), str(row.get("head") or "")
|
||||
if review:
|
||||
added = sorted({
|
||||
str(item.get("name"))
|
||||
for item in row.get("new_files") or ()
|
||||
if isinstance(item, dict) and item.get("since_pin")
|
||||
})
|
||||
hashes = len(row.get("new_hashes") or ())
|
||||
reasons = [f"{review} refs to review"] if review else []
|
||||
if added:
|
||||
reasons.append(f"new file names since the pin: {', '.join(added)}")
|
||||
if hashes:
|
||||
reasons.append(f"{hashes} undeclared hashes added since the pin")
|
||||
if reasons:
|
||||
findings.append(
|
||||
Finding(
|
||||
"profiles",
|
||||
@@ -1311,7 +1326,7 @@ def summarize_profile_sync(report: object) -> list[Finding]:
|
||||
STALE,
|
||||
local=pin[:8],
|
||||
remote=head[:8],
|
||||
detail=f"{review} refs to review",
|
||||
detail=", ".join(reasons),
|
||||
)
|
||||
)
|
||||
elif pin and head and pin != head:
|
||||
|
||||
+76
-25
@@ -966,6 +966,14 @@ class ProfileReport:
|
||||
# release, kept on the report so every output carries it.
|
||||
version: VersionReport | None = None
|
||||
version_warning: str | None = None
|
||||
# --detect-new-files and --watch-hashes, kept on the report so --json
|
||||
# carries them to check_freshness. None when not asked. A new file is
|
||||
# {path, line, name, since_pin}: since_pin when the pinned revision of
|
||||
# the same file does not carry the name. A new hash is {path, kind, value}.
|
||||
new_files: list[dict] | None = None
|
||||
new_files_elided: dict[str, int] = field(default_factory=dict)
|
||||
new_hashes: list[dict] | None = None
|
||||
detection_skipped: str | None = None
|
||||
|
||||
# Skips that say the profile cannot be checked as written: a repository
|
||||
# that no longer answers, no pin to read, a death the forge contradicts.
|
||||
@@ -3756,37 +3764,84 @@ def _cited_paths(report: ProfileReport) -> list[str]:
|
||||
)
|
||||
|
||||
|
||||
def _print_detection(
|
||||
def _attach_detection(
|
||||
args: argparse.Namespace, profile: dict, report: ProfileReport
|
||||
) -> None:
|
||||
"""Record undeclared names and hashes of the cited files on the report.
|
||||
|
||||
Kept on the report rather than printed, so --json carries them as the
|
||||
text report does. A forge that refuses is named on the report: the ref
|
||||
report already muted it, and one refusal must not end a pass over every
|
||||
profile. A quota signal still stops everything.
|
||||
"""
|
||||
if not report.head or not (args.detect_new_files or args.watch_hashes):
|
||||
return
|
||||
repo = select_repo(profile)
|
||||
if repo is None:
|
||||
return
|
||||
try:
|
||||
_detect(args, profile, report, repo)
|
||||
except upstream.RateLimitError:
|
||||
raise
|
||||
except upstream.UpstreamError as exc:
|
||||
report.detection_skipped = str(exc)
|
||||
|
||||
|
||||
def _detect(
|
||||
args: argparse.Namespace, profile: dict, report: ProfileReport,
|
||||
repo: upstream.Repo,
|
||||
) -> None:
|
||||
names, hashes = declared_names(profile), declared_hashes(profile)
|
||||
new_files: list[dict] = []
|
||||
new_hashes: list[dict] = []
|
||||
for path in _cited_paths(report):
|
||||
head_lines = upstream.fetch_file(
|
||||
repo, report.head, path, args.cache_dir, args.offline
|
||||
)
|
||||
if head_lines is None:
|
||||
continue
|
||||
pin_lines = (
|
||||
upstream.fetch_file(repo, report.pin, path, args.cache_dir, args.offline)
|
||||
or []
|
||||
)
|
||||
if args.detect_new_files:
|
||||
found, elided = detect_new_files(head_lines, names)
|
||||
for number, name in found:
|
||||
print(f" new file: {path}:{number} {name}")
|
||||
if elided:
|
||||
print(f" new file: {elided} further matches not shown")
|
||||
if args.watch_hashes:
|
||||
pin_lines = (
|
||||
upstream.fetch_file(
|
||||
repo, report.pin, path, args.cache_dir, args.offline
|
||||
)
|
||||
or []
|
||||
at_pin = {
|
||||
name.casefold()
|
||||
for _, name in detect_new_files(pin_lines, names, cap=sys.maxsize)[0]
|
||||
}
|
||||
new_files.extend(
|
||||
{"path": path, "line": number, "name": name,
|
||||
"since_pin": name.casefold() not in at_pin}
|
||||
for number, name in found
|
||||
)
|
||||
if elided:
|
||||
report.new_files_elided[path] = elided
|
||||
if args.watch_hashes:
|
||||
added = [
|
||||
line
|
||||
for line in difflib.unified_diff(pin_lines, head_lines, n=0)
|
||||
if line.startswith("+") and not line.startswith("+++")
|
||||
]
|
||||
for kind, value in watch_hashes(added, hashes):
|
||||
print(f" new {kind}: {path} {value}")
|
||||
new_hashes.extend(
|
||||
{"path": path, "kind": kind, "value": value}
|
||||
for kind, value in watch_hashes(added, hashes)
|
||||
)
|
||||
if args.detect_new_files:
|
||||
report.new_files = new_files
|
||||
if args.watch_hashes:
|
||||
report.new_hashes = new_hashes
|
||||
|
||||
|
||||
def _print_detection(report: ProfileReport) -> None:
|
||||
if report.detection_skipped:
|
||||
print(f" extras skipped: {report.detection_skipped}")
|
||||
for item in report.new_files or ():
|
||||
print(f" new file: {item['path']}:{item['line']} {item['name']}")
|
||||
for path, elided in report.new_files_elided.items():
|
||||
print(f" new file: {path}: {elided} further matches not shown")
|
||||
for item in report.new_hashes or ():
|
||||
print(f" new {item['kind']}: {item['path']} {item['value']}")
|
||||
|
||||
|
||||
def _attach_version(
|
||||
@@ -3820,13 +3875,8 @@ def _print_extras(
|
||||
instead of ending a pass over every profile. A quota signal still
|
||||
stops everything.
|
||||
"""
|
||||
wants = (
|
||||
args.detect_new_files
|
||||
or args.watch_hashes
|
||||
or args.full_diff
|
||||
or args.tree_diff
|
||||
)
|
||||
if not wants or not report.head:
|
||||
_print_detection(report)
|
||||
if not (args.full_diff or args.tree_diff) or not report.head:
|
||||
return
|
||||
repo = select_repo(profile)
|
||||
if repo is None:
|
||||
@@ -3843,8 +3893,6 @@ def _print_extras_from(
|
||||
args: argparse.Namespace, profile: dict, report: ProfileReport,
|
||||
repo: upstream.Repo,
|
||||
) -> None:
|
||||
if args.detect_new_files or args.watch_hashes:
|
||||
_print_detection(args, profile, report, repo)
|
||||
if args.full_diff:
|
||||
for path in _cited_paths(report):
|
||||
if args.ref and args.ref not in path:
|
||||
@@ -3991,13 +4039,15 @@ def _refuse_flag_combinations(args: argparse.Namespace) -> None:
|
||||
]
|
||||
# The version check lives on the report, so the outputs built from the
|
||||
# report (text, --json, --markdown) carry it. A URL list and a ranking
|
||||
# do not.
|
||||
# do not. Detection lives on the report too, and --json carries it for
|
||||
# check_freshness; the markdown table has no column for it.
|
||||
lists_only = args.fetch_plan or args.triage
|
||||
off_json = lists_only or args.markdown
|
||||
extras = [
|
||||
flag for flag, on in (
|
||||
("--check-version", args.check_version and lists_only),
|
||||
("--detect-new-files", args.detect_new_files),
|
||||
("--watch-hashes", args.watch_hashes),
|
||||
("--detect-new-files", args.detect_new_files and off_json),
|
||||
("--watch-hashes", args.watch_hashes and off_json),
|
||||
("--full-diff", args.full_diff),
|
||||
("--tree-diff", args.tree_diff),
|
||||
) if on
|
||||
@@ -4094,6 +4144,7 @@ def main() -> None:
|
||||
profile["version_source"].get("no_genuine_copy") or "") or None
|
||||
if args.check_version:
|
||||
_attach_version(args, profile, report)
|
||||
_attach_detection(args, profile, report)
|
||||
reports.append(report)
|
||||
if writes:
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user