Desktop Material

Cheap LFS-aware Git CLI wrapper

Historical prototype record: This page preserves the July 27, 2026 TUI experiment. It is not current supported-product guidance or a Windows-release blocker.

github exposes an argv-preserving Git wrapper alongside the interactive TUI:

github git <any native Git arguments>
github push <any native git push arguments>
github pull <any native git pull arguments>

github git passes its native argv directly to Git from the caller-selected working directory, including commands that do not require an existing repository. A native push or pull within that form receives the same Cheap LFS protection as the short aliases. The aliases do not require --yes; they execute immediately, just like Git. Put Desktop Material options before the wrapper command, for example github -C /work/repo --json push --dry-run origin main. Native Git options may appear in their ordinary positions, including github push --force-with-lease origin main, github pull --rebase, and github git -c protocol.version=2 status --short.

For protected push/pull operations, repository-, configuration-, or ref-namespace-changing Git globals inside the native argv are refused because they could make preflight inspect different state from the final command. Use github -C /other/repository push ..., not github git -C /other/repository push .... Ordinary passthrough commands may still use native git -C, -c, --git-dir, and related globals.

Push phases and safety boundary

Before a push, the wrapper first runs Git's parseable porcelain dry-run, then reports a Cheap LFS preflight phase. It inspects tracked and unignored-untracked safe, single-link regular working-tree files and blocks candidates strictly larger than 100 MiB unless their index entry is a canonical Cheap LFS pointer. Ignored files, symbolic links, hardlinks, and non-regular filesystem objects are excluded from this working-candidate scan rather than followed.

When an indexed pointer is materialized in the working tree, the wrapper hashes it and checks both its size and SHA-256 against the index pointer instead of treating it as a bypass. It also refuses published history containing blobs strictly larger than 100 MiB. The native dry-run names each actual source and destination update. When its old destination object can be proven in the local object database, the scan excludes that remote base and examines the publication delta. A new ref, missing/ambiguous old object, or other unprovable base safely falls back to all history reachable from the pushed source ref. The scan is bounded at 1,000,000 objects.

--dry-run never publishes refs, stages files, rewrites history, uploads assets, or restores/pulls payloads. Preflight never downloads missing Cheap LFS assets. It validates index pointers and hashes materialized payloads only.

The wrapper deliberately never installs a Git hook/filter or shadow git executable, rewrites commits, stages files, creates commits, or uploads Cheap LFS assets. Convert a rejected file through the Cheap LFS manager and make any necessary history rewrite deliberately.

Recursive submodule modes that can publish submodule histories are refused: push each submodule through the wrapper first, then use --recurse-submodules=check or no. This prevents a root-repository scan from silently blessing an uninspected submodule publication.

Pull phases and repeated pulls

After a successful native git pull, the wrapper restores every canonical index pointer through the normal Cheap LFS cache/provider path and reports the native-pull and restore phases separately. A cached payload restores without a download when its hashes match.

Materialized payloads are verified and left intact. If native Git rejects a subsequent pull because one could be overwritten, the wrapper does not replace it behind Git's back: it reports the affected verified paths and tells the user to restore their canonical pointers before retrying. This is intentionally a clear, non-destructive failure rather than an automatic temporary checkout.

Output and failure handling

Human output labels each phase as succeeded, failed, skipped, or planned and preserves Git's sanitized stdout/stderr and exit code. --json emits the same structured report, including blocked working files, blocked blobs, restored paths, and already-materialized paths. Exit code 3 is a Cheap LFS push preflight refusal; exit code 4 means native pull succeeded but restoration failed without overwriting the path. Native Git failures retain Git's exit code.

Configuration

Failure modes

Condition Result
native push dry-run fails return Git's exit code; skip preflight and real push
unbacked safe file is strictly above 100 MiB exit 3; list the working path and do not push
source history contains an oversized blob exit 3; list blob/path evidence and require a deliberate history repair
materialized bytes differ from their indexed pointer exit 3; do not push
source-history/path scan exceeds its 1,000,000-item bound fail preflight instead of silently skipping objects
push-capable recursive-submodule mode is enabled exit 3 before preview; push each submodule through the wrapper
protected push/pull carries native -c, -C, --git-dir, or similar state-changing global refuse it; move repository selection to github -C PATH
native pull fails retain Git's exit code; do not start pointer restoration
native pull succeeds but one restore fails exit 4; preserve the successful pull and do not overwrite the conflicting path
cache miss or provider/authentication failure restoration fails closed; the pointer remains available for a deliberate retry

Security

Every Git and gh invocation is an argument array with shell=False, noninteractive credential prompting, bounded execution, and credential-shaped output redaction. Push preflight is read-only and never downloads payloads. Pull restoration reuses Cheap LFS's regular-file, no-symlink, unchanged-pointer, size, whole-file SHA-256, part SHA-256, cache, quarantine, and no-overwrite checks.

The wrapper does not infer consent for history rewriting or provider writes. The typed push/pull command authorizes only native Git plus the documented read-only preflight or verified materialization phase.

Verification

Focused tests cover raw argv and -c passthrough; non-repository/subdirectory working directories; both short aliases; native help/option parsing; forced porcelain output; delimiter and missing-value handling; push versus pull dry-run semantics; recursive-submodule refusal; oversized working files; remote-base and full-history source scans; canonical working pointers; hash-verified materialized payloads; repository-changing global refusal; cache-backed pull restoration; native pull failure; and phase/exit reporting. The full package suite, strict typing, Ruff, wheel smoke, and a real disposable Linux bare-remote push/pull run remain part of the dated path-browser/wrapper manifest.