Reconstruct a full repository from a GitIngest-style digest file.
Project description
git-undigest
Reconstruct a full repository — folder structure and all — from a GitIngest-style digest file.
GitIngest turns a repository into a single flat text digest for feeding to
an LLM. git-undigest does the reverse: it parses that digest and rebuilds
the original directory tree and files on disk, safely and deterministically.
Features
- Streaming parser — parses multi-GB digests with constant memory (proportional to the largest single file, not total size).
- Pluggable formats — add support for Repomix, Repopack, or custom digest formats without modifying core code.
- Security-first — path traversal protection, absolute path rejection, Windows reserved name detection, atomic writes.
- Conflict handling —
--overwrite,--skip-existing,--backuppolicies. - Dry-run mode — preview what would happen without touching the filesystem.
- Compressed digests — transparent
.gz,.xzsupport built-in;.zstviapip install git-undigest[zstd]. - Plugin discovery — third-party format packages auto-discovered via entry points.
- No runtime dependencies — pure Python, zero required installs beyond the standard library.
Installation
pip install git-undigest
Requires Python 3.10+.
Optional compression support:
pip install "git-undigest[zstd]" # for .zst (zstandard) files
Quick Start
# Reconstruct a repository from a digest file
git-undigest digest.txt
# Reconstruct into a specific directory
git-undigest digest.txt output/
# Validate without writing
git-undigest validate digest.txt
CLI Examples
# Reconstruct with overwrite policy
git-undigest digest.txt --overwrite
# Dry-run preview
git-undigest digest.txt output/ --dry-run
# Skip files that already exist
git-undigest digest.txt output/ --skip-existing
# Back up existing files before overwriting
git-undigest digest.txt output/ --backup
# Verbose output (one line per file action)
git-undigest digest.txt --verbose
# Inspect repository metadata
git-undigest inspect digest.txt
# List all files in the digest
git-undigest list digest.txt
# Get statistics
git-undigest stats digest.txt
Conflict Flags
| Flag | Behavior |
|---|---|
--overwrite |
Overwrite existing files instead of erroring |
--skip-existing |
Leave existing files untouched |
--backup |
Rename existing files to name.bak before writing |
--dry-run |
Show what would happen without touching the filesystem |
--verbose |
Print a line for every file action taken |
--quiet |
Suppress summary output |
Python API
from git_undigest import reconstruct, validate, inspect, stats, list_files
# Reconstruct a repository
result = reconstruct("digest.txt", output="repo")
print(f"{len(result.created)} files created in {result.output_dir}")
# Validate without writing
summary = validate("digest.txt")
print(f"Repository: {summary.repo_name}, {summary.file_count} files")
# Inspect
info = inspect("digest.txt")
print("Languages:", info["languages"])
print("Directory tree:\n", info["tree"])
# Get statistics
s = stats("digest.txt")
print(f"Total: {s.total_bytes} bytes, ~{s.estimated_tokens} tokens")
# List all files
for path in list_files("digest.txt"):
print(path)
Streaming API
For large digests, use the streaming parser directly:
from git_undigest import parse_stream, reconstruct_files_stream
entries = parse_stream("large_digest.txt")
result = reconstruct_files_stream(entries, "output", overwrite=True)
This keeps memory constant regardless of digest size.
Supported Digest Formats
| Format | Status | Notes |
|---|---|---|
| GitIngest | Stable | Default format |
| Custom | Pluggable | Subclass DigestFormat |
To add support for a new format, create a subclass of DigestFormat,
implement sniff(), parse_stream(), and serialize(), then register it:
from git_undigest.formats import DigestFormat, register_format_class
from git_undigest.models import FileEntry
class MyFormat(DigestFormat):
name = "myformat"
@classmethod
def sniff(cls, prefix: str) -> bool:
return prefix.startswith("MAGIC")
def parse_stream(self, stream):
... # yield FileEntry instances
def serialize(self, repo) -> str:
...
register_format_class(MyFormat)
Third-party packages are auto-discovered via the git_undigest.formats
entry point group.
Security
Every path in the digest is validated before anything is written:
- No path traversal.
../../../etc/passwd,../secret.txt, and any path containing a..segment that would escape the output directory is rejected withPathTraversalError. - No absolute paths. POSIX absolute paths (
/etc/shadow), Windows drive-qualified paths (C:\Windows\System32), and UNC paths (\\server\share) are all rejected. - No Windows reserved device names.
CON,PRN,AUX,NUL,COM1–COM9,LPT1–LPT9are rejected as path components. - No null bytes are permitted in paths.
- Final containment check. Every resolved path is confirmed, via
Path.relative_to, to be a real descendant of the output directory after full filesystem resolution. - Atomic writes. Files are written to a temporary file in the same directory and then renamed into place, so a crash or interruption never leaves a partially-written file at the destination.
Architecture
src/git_undigest/
├── __init__.py # Public API: reconstruct, validate, inspect, stats
├── cli.py # argparse CLI entry point
├── parser.py # Streaming and bulk digest parsing
├── formats/
│ ├── __init__.py # DigestFormat ABC, registry, plugin discovery
│ └── gitingest.py # GitIngest format implementation
├── validator.py # Path safety + structural validation
├── writer.py # Streaming filesystem reconstruction
├── checksum.py # SHA-256 checksum utilities
├── models.py # Dataclasses (FileEntry, Repository, results)
├── exceptions.py # Exception hierarchy
└── utils.py # Shared helpers
benchmarks/
└── bench_streaming.py
tests/
├── test_api.py
├── test_cli.py
├── test_parser.py
├── test_writer.py
├── test_validator.py
├── test_fuzz.py
├── test_phase1.py
├── test_formats_and_placeholders.py
└── ...
Design Principles
- Parser only parses. It never touches the filesystem or makes security decisions.
- Writer only writes. It assumes the digest has been validated, but re-validates every path as defense-in-depth.
- Validator owns all validation. No duplicated logic.
- Formats are pluggable. Adding a new format means a new module in
formats/— no changes toparser.py,validator.py, orwriter.py. - Streaming by default. All public APIs use constant-memory streaming internally.
Performance
The streaming parser is 2–30x faster than bulk parsing for typical digest sizes because it avoids allocating a single large string for the entire digest:
| Files | Digest Size | Bulk (s) | Stream (s) | Speedup |
|---|---|---|---|---|
| 100 | 12 KB | 0.036 | 0.001 | 32x |
| 1,000 | 120 KB | 0.040 | 0.007 | 6x |
| 10,000 | 1.2 MB | 0.124 | 0.072 | 1.7x |
Memory usage is O(largest file) for streaming vs O(total digest) for bulk.
Roadmap
- SHA-256 checksum manifest verification
- Binary file reconstruction (base64-embedded digests)
- Parallel reconstruction for very large digests
- Resumable reconstruction
- Plugin distribution guide for third-party format packages
Contributing
See CONTRIBUTING.md for development setup, testing instructions, and pull request guidelines.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file git_undigest-0.2.0.tar.gz.
File metadata
- Download URL: git_undigest-0.2.0.tar.gz
- Upload date:
- Size: 35.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87a459913465dd6915ae94acb11709ad461d2103ba993ae82c60bf5c8ee942ca
|
|
| MD5 |
c4cc0c7be9634f173683957adc7d8da6
|
|
| BLAKE2b-256 |
c18532d178bd320cfd0b877778335b2c5ebe40f651819303c5d891493c17ae36
|
Provenance
The following attestation bundles were made for git_undigest-0.2.0.tar.gz:
Publisher:
release.yml on vnparmane/git-undigest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_undigest-0.2.0.tar.gz -
Subject digest:
87a459913465dd6915ae94acb11709ad461d2103ba993ae82c60bf5c8ee942ca - Sigstore transparency entry: 2022056833
- Sigstore integration time:
-
Permalink:
vnparmane/git-undigest@2e036cbcf7f0f3cb150fc43cdb361c1c7e6d8eca -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/vnparmane
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2e036cbcf7f0f3cb150fc43cdb361c1c7e6d8eca -
Trigger Event:
push
-
Statement type:
File details
Details for the file git_undigest-0.2.0-py3-none-any.whl.
File metadata
- Download URL: git_undigest-0.2.0-py3-none-any.whl
- Upload date:
- Size: 34.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dec3b06a18ba324ed14b1b078c4ee383e512975169378894e19188204ec2c4af
|
|
| MD5 |
215eeed769ca4b38b36302197898b0eb
|
|
| BLAKE2b-256 |
d29c9a5091cef09087aaa2547c2ae7eb8d1cf010f8f0c86282c842271665a153
|
Provenance
The following attestation bundles were made for git_undigest-0.2.0-py3-none-any.whl:
Publisher:
release.yml on vnparmane/git-undigest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_undigest-0.2.0-py3-none-any.whl -
Subject digest:
dec3b06a18ba324ed14b1b078c4ee383e512975169378894e19188204ec2c4af - Sigstore transparency entry: 2022057175
- Sigstore integration time:
-
Permalink:
vnparmane/git-undigest@2e036cbcf7f0f3cb150fc43cdb361c1c7e6d8eca -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/vnparmane
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2e036cbcf7f0f3cb150fc43cdb361c1c7e6d8eca -
Trigger Event:
push
-
Statement type: