Files
kandev/scripts/pr-walkthrough-read-file
T
2026-08-22 21:46:57 +01:00

101 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Read one bounded UTF-8 regular file from an immutable Git commit."""
import argparse
import os
from pathlib import PurePosixPath
import re
import subprocess
import sys
MAX_BYTES = 512 * 1024
SHA_RE = re.compile(r"^[0-9a-f]{40}$")
REGULAR_MODES = {"100644", "100755"}
class ReadError(Exception):
pass
def git(repo: str, *args: str, text: bool = False) -> subprocess.CompletedProcess:
env = {"PATH": os.environ.get("PATH", "/usr/bin:/bin"), "LC_ALL": "C"}
return subprocess.run(
["git", *args],
cwd=repo,
env=env,
capture_output=True,
text=text,
check=False,
)
def validate_path(value: str) -> str:
path = PurePosixPath(value)
parts = value.split("/")
if (
not value
or value.startswith(("/", ":"))
or "\\" in value
or "\x00" in value
or any(part in {"", ".", ".."} for part in parts)
or path.is_absolute()
):
raise ReadError("invalid repository-relative path")
return value
def read_blob(repo: str, head_sha: str, path: str) -> str:
if not SHA_RE.fullmatch(head_sha):
raise ReadError("head SHA must be 40 lowercase hexadecimal characters")
path = validate_path(path)
commit = git(repo, "cat-file", "-e", f"{head_sha}^{{commit}}")
if commit.returncode != 0:
raise ReadError("head SHA is not an available commit")
listing = git(repo, "--literal-pathspecs", "ls-tree", "-z", head_sha, "--", path)
records = [record for record in listing.stdout.split(b"\0") if record]
if listing.returncode != 0 or len(records) != 1:
raise ReadError("path is not a regular file at the requested commit")
metadata, separator, matched_path = records[0].partition(b"\t")
fields = metadata.split()
if not separator or len(fields) != 3 or matched_path != path.encode("utf-8"):
raise ReadError("path is not a regular file at the requested commit")
mode, object_type, object_id = (field.decode("ascii") for field in fields)
if mode not in REGULAR_MODES or object_type != "blob":
raise ReadError("path is not a regular file at the requested commit")
size_result = git(repo, "cat-file", "-s", object_id, text=True)
if size_result.returncode != 0:
raise ReadError("could not inspect file object")
size = int(size_result.stdout.strip())
if size > MAX_BYTES:
raise ReadError(f"file exceeds {MAX_BYTES} bytes")
content = git(repo, "cat-file", "blob", object_id)
if content.returncode != 0:
raise ReadError("could not read file object")
try:
return content.stdout.decode("utf-8")
except UnicodeDecodeError as exc:
raise ReadError("file must be UTF-8 text") from exc
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--repo", required=True)
parser.add_argument("--head-sha", required=True)
parser.add_argument("--path", required=True)
args = parser.parse_args()
try:
sys.stdout.write(read_blob(args.repo, args.head_sha, args.path))
except (OSError, ReadError, ValueError) as exc:
print(f"PR file read failed: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())