Skip to content

Analyze a public Git repository

Readiness and exposure can acquire a public HTTPS Git repository directly. Private repositories through an explicit SSH agent use the same snapshot API. Install Git separately; this adapter is included in the minimal Apizr core and does not install or invoke plugins. Python 3.11–3.14 on macOS/Linux is supported.

One explicit revision

Run these commands from an empty local working directory. The policies are local files you control; Apizr does not load policies or apizr.toml from the remote. This example selects the small calculator source already present in Apizr's repository at the specified immutable commit:

REPOSITORY=https://github.com/Alien6-Studio/outerspace-apizr.git
REF=4898669e4990a6d090637436b5884d4d2c2dd41c
SUBDIR=examples/project-config/src

cat > readiness.json <<'JSON'
{"execution":{"modes":["direct"]}}
JSON
cat > exposure.json <<'JSON'
{"selection":{"include":["python:calculator:add"]},"interfaces":["rest","mcp"],"execution":{"allowed":["direct"]}}
JSON

apizr readiness --git "$REPOSITORY" --ref "$REF" --subdir "$SUBDIR" \
  --policy readiness.json --report
apizr expose plan --git "$REPOSITORY" --ref "$REF" --subdir "$SUBDIR" \
  --policy exposure.json --readiness-policy readiness.json --plan
apizr expose build rest --git "$REPOSITORY" --ref "$REF" --subdir "$SUBDIR" \
  --policy exposure.json --readiness-policy readiness.json --output-dir build/rest
apizr expose build mcp --git "$REPOSITORY" --ref "$REF" --subdir "$SUBDIR" \
  --policy exposure.json --readiness-policy readiness.json --output-dir build/mcp

--ref is mandatory: a branch, tag (including annotated tags), or full commit object ID. A short name shared by a branch and tag is refused even if both point to the same commit; qualify it as refs/heads/name or refs/tags/name. No default branch or abbreviated-commit fallback is used. The server must allow fetching the resolved object; if it disappears or access is refused, acquisition fails rather than silently selecting a newer revision.

The resolved commit is printed to stderr, leaving canonical JSON on stdout. --subdir selects a directory inside the snapshot. --source-root retains its scanner meaning, relative to that selected directory. --git cannot be combined with a positional local root or --project; --ref and --subdir require --git. Commands without --git retain their existing behavior and exit codes.

Python API

Save this as snapshot.py alongside the two policy files above, then run python snapshot.py "$REPOSITORY" "$REF" "$SUBDIR". Use fresh output directories.

import sys
from pathlib import Path

from apizr.compiler import prepare_exposure, render_bundle
from apizr.exposure import ExposurePolicy
from apizr.git_source import acquire_snapshot
from apizr.repository_interfaces.output import write_bundle
from apizr.repository_readiness import RepositoryReadinessPolicy

repository, reference, subdir = sys.argv[1:]
exposure = ExposurePolicy.model_validate_json(Path("exposure.json").read_bytes())
readiness = RepositoryReadinessPolicy.model_validate_json(
    Path("readiness.json").read_bytes()
)
with acquire_snapshot(repository, reference, subdir=subdir) as snapshot:
    print(f"Git snapshot: commit {snapshot.commit}", file=sys.stderr)
    prepared = prepare_exposure(
        snapshot.root, policy=exposure, readiness_policy=readiness
    )

# The temporary snapshot is already deleted; the compiler retained its sources.
for interface in ("rest", "mcp"):
    bundle = render_bundle(prepared, interface=interface)
    write_bundle(Path("python-" + interface), bundle)

GitSnapshot identifies repository, requested_ref, resolved commit, selected subdir and ephemeral root. The context manager cleans up on success, refusal and interruption. It does not catch or reclassify errors raised by your analysis. AcquisitionLimits configures the bounds below, and an optional threading.Event passed as cancel cancels acquisition. ca_file=Path(...) can explicitly trust a CA in Python tests; certificate and hostname verification remain enabled.

Acquisition raises GitSourceError with fixed codes such as git_not_found, git_invalid_url, git_ref_not_found, git_ambiguous_ref, git_timeout, git_cancelled, git_output_limit, git_acquisition_limit and git_snapshot_limit. CLI acquisition errors return 2; Ctrl-C returns 130 after cleanup. Raw Git output and URLs are not echoed in errors. Compiler policy refusals retain their existing codes.

Boundaries and limits

Git runs without a shell, with a fresh environment, private HOME, empty template, disabled hooks, credentials, inherited configuration, URL rewriting, proxies, automatic maintenance and submodule recursion. Only the explicitly selected HTTPS or SSH transport is enabled. TLS verification is mandatory. Embedded credentials, query strings, fragments and redirects are refused; use the final canonical public repository URL. The executable found on PATH and its installed Git helpers must be trusted.

There is no checkout, source import, dependency installation or execution of project code. Raw blobs are exported with cat-file, without smudge/textconv filters or archive attributes (export-ignore/export-subst). All symlinks, submodule entries and LFS pointers are refused, including outside the selected subdirectory, so missing content cannot masquerade as a complete analysis. Paths cannot leave the snapshot; .git and acquisition files are never exported. Source filenames must be UTF-8 and portable relative paths. Case-colliding files are refused if the host filesystem cannot represent them separately.

Default acquisition bounds (independent of the existing scanner bounds):

Bound Default Enforcement
Total acquisition time 120 seconds One deadline across Git commands and export
Git stdout per command 80 MiB Bounded while reading; metadata commands additionally capped at 4 MiB
Git stderr per command 64 KiB Bounded while reading, never included in diagnostics
Acquisition disk volume 128 MiB Sampled during Git execution and checked after commands/export
Snapshot regular files 10,000 Checked before writing blobs
One snapshot file 8 MiB Checked from object metadata before reading blobs
Total snapshot file bytes 64 MiB Checked before reading/writing blobs

The disk limit is an observed-volume limit, not a hard network-byte quota. Git can receive/write more bytes between samples; compressed packs can expand substantially, and a shallow fetch does not guarantee a small transfer. Acquisition is terminated when a measured bound is exceeded; no result is returned. The adapter also bounds filesystem-entry inspection. A busy filesystem can delay observation. This is not a disk/memory sandbox or a defense against a malicious Git executable: keep Git patched. Normal helper process groups are killed and the direct child is reaped on errors/interruption; detached descendants and uninterruptible kernel work cannot be guaranteed terminated. Cleanup failure is explicit (git_cleanup_failed).

There is no HTTPS authentication, persistent cache, remote policy loading, submodule/LFS acquisition, or remote publication in this iteration. Rendering REST/MCP bundles does not start either server or execute their source code.

Reproducible installed-wheel proof

Maintainers can run uv run python scripts/smoke_git_source.py dist/outerspace_apizr-*.whl. It creates a disposable minimal wheel installation outside the checkout and a real smart-HTTPS Git server with an explicitly trusted localhost certificate. It compares all four commands to local sources and executes the Python example above after deleting the snapshot. No optional packages or plugins are loaded. The normal installation CI runs this proof for Python 3.11 and 3.14.

Git references: environment isolation, shallow fetch, and literal object export.