diff --git a/scripts/auto_fetch.py b/scripts/auto_fetch.py index e7272524a..08d7b3afe 100644 --- a/scripts/auto_fetch.py +++ b/scripts/auto_fetch.py @@ -6,7 +6,8 @@ Pipeline: 2. Scan old branches (git show origin/branch:path) 3. Search public BIOS repos on GitHub 4. Search archive.org collections -5. Create GitHub Issue for community help +5. Print an issue body for the files still missing (--create-issues). + It posts nothing. Usage: python scripts/auto_fetch.py --platform retroarch [--dry-run] @@ -459,43 +460,37 @@ def fetch_missing( ) stats["errors"] += 1 continue - print(" [5] Not found - needs community contribution") + print(" [5] Not found, open for contributions") still_missing.append(entry) stats["not_found"] += 1 return {"stats": stats, "still_missing": still_missing} +CONTRIBUTING_URL = "https://github.com/Abdess/retrobios/blob/main/CONTRIBUTING.md" + + def generate_issue_body(missing: list[dict], platform: str) -> str: - """Generate a GitHub Issue body for missing BIOS files.""" + """An issue body for the files a platform still needs. + + The hashes are written in full: a contributor matches a file against + them, and a 12-character prefix confirms nothing. + """ lines = [ - f"## Missing BIOS Files for {platform}", - "", - "The following BIOS files are required but not available in the repository.", - "If you have any of these files, please submit a Pull Request!", + f"## Files {platform} still needs", "", "| File | System | SHA1 | MD5 |", "|------|--------|------|-----|", ] - for entry in missing: - sha1 = ", ".join(sorted(_accepted(entry, "sha1"))) or "N/A" - md5 = ", ".join(sorted(_accepted(entry, "md5"))) or "N/A" - lines.append( - f"| `{entry['name']}` | {entry['system']} | `{sha1[:12]}...` | `{md5[:12]}...` |" - ) - - lines.extend( - [ - "", - "### How to Contribute", - "", - "1. Fork this repository", - "2. Add the BIOS file to `bios/Manufacturer/Console/`", - "3. Create a Pull Request - checksums are verified automatically", - ] - ) - + sha1 = ", ".join(f"`{h}`" for h in sorted(_accepted(entry, "sha1"))) or "none declared" + md5 = ", ".join(f"`{h}`" for h in sorted(_accepted(entry, "md5"))) or "none declared" + lines.append(f"| `{entry['name']}` | {entry['system']} | {sha1} | {md5} |") + lines += [ + "", + "A file with one of these hashes can be added through the upload link in" + f" [CONTRIBUTING.md]({CONTRIBUTING_URL}). CI checks it on arrival.", + ] return "\n".join(lines) @@ -563,7 +558,7 @@ def main() -> None: result = fetch_missing(missing, db, args.bios_dir, args.dry_run) if result["still_missing"]: - all_still_missing[platform] = result["still_missing"] + all_still_missing[config.get("platform") or platform] = result["still_missing"] stats = result["stats"] print( diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index a93c0a8c8..34744187d 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -7414,5 +7414,36 @@ class TupleAnnotationsMatchTheirReturns(unittest.TestCase): self.assertEqual(mismatched, []) +class AutoFetchIssueBodyReadsAsWritten(unittest.TestCase): + """The --create-issues body was a template: Title Case headings, "The + following...", an exclamation mark, numbered orders to the reader, a + spaced dash, and hashes cut to 12 characters a contributor cannot + match a file against.""" + + ENTRY = {"name": "scph5501.bin", "system": "sony-playstation", + "sha1": "b056ee5a4d65937e1a3a17e1e78f3258ea49c38e", + "md5": "490f666e1afb15b7362b406ed1cea246"} + + def test_full_hashes_and_no_template_phrasing(self): + from scripts import auto_fetch + + body = auto_fetch.generate_issue_body([self.ENTRY], "RetroArch") + self.assertIn(f"`{self.ENTRY['sha1']}`", body) + self.assertIn(f"`{self.ENTRY['md5']}`", body) + self.assertNotIn("...", body) + self.assertNotIn("!", body) + self.assertNotRegex(body, r"(?i)the following|please| - |\bfork\b") + self.assertNotRegex(body, r"(?m)^\d+\. ") + for heading in re.findall(r"(?m)^#+ (.*)$", body): + later = [w for w in heading.split()[1:] if w != "RetroArch"] + self.assertEqual(later, [w.lower() for w in later], heading) + + def test_the_docstring_says_nothing_is_posted(self): + from scripts import auto_fetch + + self.assertNotIn("Create GitHub Issue", auto_fetch.__doc__) + self.assertIn("posts nothing", auto_fetch.__doc__) + + if __name__ == "__main__": unittest.main() diff --git a/wiki/tools.md b/wiki/tools.md index a8ad76c26..ce8c14359 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -1389,7 +1389,7 @@ Further options, by script: | Script | Options | |--------|---------| -| `auto_fetch.py` | `--create-issues` prints a GitHub issue body per file still missing. It posts nothing | +| `auto_fetch.py` | `--create-issues` prints a GitHub issue body per platform for the files still missing, with their full hashes. It posts nothing | | `check_buildbot_system.py` | `--update` refreshes the entries found stale, `--json`, `--registry` | | `check_freshness.py` | `--cache-dir`, `--native-cache-dir` (originals of the native export) and `--truth-dir` move the caches it reads | | `check_release.py` | `--tag`, `--sums dist/SHA256SUMS.txt`, `--record` (default `release.json`), `--json` |