git-closure
Deterministic, self-describing, verifiable source-tree snapshots.
git-closure builds .gcl files (S-expressions) that can be checked into git,
emailed, archived, diffed, and materialized back into a filesystem tree.
Current CLI Surface (v0.1)
git-closure currently ships these subcommands:
build(b) - build a snapshot from a local or remote sourcematerialize(m) - restore a snapshot into a directoryverify(v) - verify structural and per-file integritylist(l) - list recorded entriesdiff(d) - compare snapshots or snapshot-vs-directory (text,--json,--stat)fmt(f) - canonicalize snapshot formattingrender(r) - render Markdown, HTML, or JSON reportssummary(s) - print compact snapshot metadata (textor--json)completion(c) - generate shell completions (bash/zsh)
Quick Start
# Build
# Run
# Quality gates
Core Examples
These examples are machine-validated by trycmd fixtures under
tests/cli/README/.
# Snapshot a local directory
# Verify integrity
# Count-only diff summary (exit 1 when differences exist)
# Snapshot vs working tree diff
# Snapshot summary
Sources and Providers
build accepts local paths and remote source syntaxes. In auto mode, source
classification is grammar-driven:
- Local existing path ->
local - Nix flake references (
nix:,github:,gitlab:,path:,tarball+,file+,git+,sourcehut:) ->nix - GitHub shorthand/HTTPS repo (
gh:owner/repo[@ref],https://github.com/owner/repo[@ref]) ->github-api - GitLab shorthand and other remotes ->
git-clone
Provider behavior:
local: snapshots a local directory directlygit-clone: shallow clone (--depth 1 --no-tags) then snapshot checkoutnix:nix flake metadata <ref> --json, then snapshot returned store pathgithub-api: download GitHub tarball, strip top-level archive directory, snapshot extracted tree
GitHub authentication/rate limits:
- Set
GCL_GITHUB_TOKENfor private repositories or higher API limits.
Important syntax distinction:
gh:owner/repo= GitHub shorthand (auto ->github-api)github:owner/repo= Nix flake ref (auto ->nix)
You can force a provider explicitly:
Output Naming and Build Notice
When --output is omitted, build derives a filename:
gh:owner/repo@main->repo@main.gclgl:group/project@v1.2->project@v1.2.gcl.-><current-directory-name>.gcl- fallback ->
snapshot.gcl
When auto-deriving output, build emits exactly one stderr note:
note: writing snapshot to <path>
No note is emitted when --output is explicitly provided.
File Selection Semantics
In a git repository, build follows git-tracked semantics by default:
- includes tracked files
- excludes untracked files unless
--include-untracked - still excludes ignored files when
--include-untrackedis set - with
--require-clean, fails if selected source scope has uncommitted changes
Format and Integrity
.gcl snapshots contain:
;; snapshot-hash(structural hash);; file-count- optional
;; git-rev,;; git-branch - optional provenance headers
;; source-uri,;; source-providerfor remote builds - S-expression entries for files/symlinks
snapshot-hash uses SHA-256 over length-prefixed tuples with u64 big-endian
length prefixes.
Canonical git revision field name is git-rev (not git-commit). The value is
captured from git rev-parse HEAD and is treated as informational metadata.
materialize Safety Model
materialize has explicit safety constraints:
- output directory must be empty (or newly created)
- paths must be safe relative paths (no absolute paths, no
..traversal) - symlink targets are containment-checked lexically and cannot escape output root
These constraints prevent pre-planted symlink and path-traversal attacks during reconstruction.
Library consumers can opt into alternate materialization profiles via
MaterializeOptions:
Strict(default): requires empty output directoryTrustedNonempty: allows overlay into non-empty output directoriesNoSymlink: rejects snapshots containing symlink entries
diff Output Modes
- default text: path-level changes with identity detail
--json: structured entries (added,removed,modified,renamed,mode_changed)--stat: deterministic counts only
The right-hand diff input is auto-detected:
- existing directory path -> compare snapshot against live source tree
- otherwise -> compare snapshot file vs snapshot file
diff exit behavior:
- exit
0when identical - exit
1when differences exist
fmt Behavior
git-closure fmt <file>canonicalizes formatting- by default,
fmtrejects parseable files whose storedsnapshot-hashdoes not match recomputed structure --repair-hashexplicitly opts into hash repair/re-canonicalization
fmt --check exit behavior is split by condition (see Exit Codes).
render Formats
- formats:
markdown,html,json - default output: stdout
- optional file output:
-o/--output
summary Output
Summary includes snapshot hash, entry counts, total bytes, git metadata, and top-5 largest regular files.
Symlink rendering policy:
- Markdown/HTML: entry type shown as symlink, digest column shows
-> <target> - JSON:
type=symlink,mode="120000",size=0,sha256="", andsymlink_targetpopulated
Exit Codes
0- success / no semantic differences1- semantic negative result (diffdifferences,fmt --checknoncanonical-valid)2- integrity mismatch (fmt --checkhash mismatch)3- parse failure (fmt --checkparse error)4- operational/runtime failure (I/O, provider/subprocess, usage/runtime errors)
CI Contract
CI validates both library and CLI contract behavior:
cargo test --lockedonubuntu-latestandmacos-latest- both MSRV (
1.85) and stable toolchains cargo clippy --locked -- -D warningscargo fmt --checkcargo build --locked --release- tag-triggered
Releaseworkflow forv*
Golden Fixtures
Byte-level format stability is locked by committed fixtures:
tests/fixtures/simple.gcl(canonical snapshot bytes)tests/fixtures/simple.render.json(render JSON surface)
Intentional format changes must update fixtures in the same commit with an explicit rationale.
Shell Completions
Generate completion scripts:
clap_complete is intentionally included unconditionally in v0.1 so completion
generation is always available in distributed binaries.
Fuzzing
Fuzz targets are in fuzz/:
Roadmap
- v0.1 (current): build/materialize/verify/list/diff/fmt/render/summary/completion
- post-v0.1 [planned]: richer remote providers, additional render/export targets, and further performance/security hardening
Commands like query and watch are not part of the current shipped CLI.
License
MIT