┌────────────────────────────────────────────────────────┐
│ TINY GIT │
│ A small, educational, Git-inspired content- │
│ addressed version-control system. │
└────────────────────────────────────────────────────────┘
[Python 3.12+] [License: MIT] [Tests: 98 Passed (0 Failed)] [CLI Commands: 16 (Frozen)]
tiny-git (tiny-git) is a small, cleanly designed open-source version-control system built from scratch in modern Python 3.12+. It implements content-addressed objects, a staging index, immutable commits, references, branching, unified diffs, and three-way merges without invoking Git.
Version control systems like Git are often perceived as mysterious "black boxes." While developers use git daily, fewer understand the fundamental mechanics of SHA-1 content-addressed storage, staging indices, immutable commit snapshots, lowest common ancestor (LCA) graph traversal, and three-way merging.
tiny-git was built with one primary engineering goal:
To demonstrate the core internal concepts of Git with maximum correctness, simplicity, understandability, and clean architecture.
Our killer feature is simple:
We built a small Git-like VCS whose internals you can read and understand in an afternoon.
We explicitly prioritize:
- Correctness & Invariants: Strict content-addressed invariants, atomic filesystem operations, and safe checkout/merge semantics that protect uncommitted work.
- Repository Integrity: Comprehensive integrity checking (
tiny-git fsck), crash consistency, and format versioning. - Simplicity: Small, sharply separated modules with clear data structures and no magic.
- Testability: 98 tests covering unit behavior, property invariants, crash consistency, a 30+ scenario merge matrix, and full CLI workflows in isolated temporary directories.
- No Fake Completeness: Every feature claimed in this README works in code and is verified by tests.
Working Tree Index (Staging Area) Object Database
(Disk Files) (.tinygit/index) (.tinygit/objects/)
┌────────────┐ ┌────────────┐ ┌────────────┐
│ │ add │ │ commit │ │
│ main.py ├───────────►│ main.py ├─────────────►│ Blob │
│ README.md │ │ README.md │ (build │ Tree │
│ │ │ │ tree) │ Commit │
└─────▲──────┘ └─────▲──────┘ └─────▲──────┘
│ │ │
│ │ │
└─────────────────────────┴───────────────────────────┘
checkout / merge (restore snapshots)
- Content-Addressable Object Store (
.tinygit/objects/):- Implements SHA-1 content-addressed storage with deduplication for Blobs, Trees, and Commits, using standard
xx/yyyy...two-character prefix directory sharding. - Security & Algorithm Extension Point: SHA-1 is used explicitly to mirror Git's historical object model for educational clarity, not for modern cryptographic collision resistance. The store is decoupled via the
HashAlgorithmenum andget_hasher()abstraction, allowing future algorithms (like SHA-256) without rewriting storage logic.
- Implements SHA-1 content-addressed storage with deduplication for Blobs, Trees, and Commits, using standard
- Repository Integrity Checker (
tiny-git fsck):- Validates object hashes, tree entry references, parent commit existence, ref validity, HEAD target integrity, index blob existence, and commit graph acyclicity.
- Atomic File Writes (
atomic_write_file):- Metadata and object files (
HEAD,refs,index,config,objects/) are written using temporary files,os.fsync(), and atomic renames to prevent partial file corruption on process crash or power loss.
- Metadata and object files (
- Format Versioning (
core.repositoryformatversion):- Stores
repositoryformatversion = 1in.tinygit/config. Rejects newer versions safely with anUnsupportedRepositoryFormatError.
- Stores
- Symlink & Byte-Oriented Storage Policy:
- Symlinks are explicitly unsupported in V1 for security and determinism; filesystem traversals never follow or stage symlink targets.
- Blobs are strictly byte-oriented (
read_bytes()/write_bytes()), supporting binary files and arbitrary character encodings safely.
- Revision Resolver (
RevisionResolver):- Unified resolution engine for
HEAD, branches, short SHA prefixes, and ancestry syntax (HEAD~1,HEAD^,main~2^2).
- Unified resolution engine for
- Python 3.12 or higher
Clone the repository and install it in editable mode using pip:
git clone https://github.com/example/tiny-git.git
cd tiny-git
pip install -e .Verify the installation:
tiny-git --helpmkdir demo
cd demo
# Initialize empty repository
tiny-git init
# Configure author identity
tiny-git config user.name "Ron"
tiny-git config user.email "ron@example.com"
# Create and stage a file
echo "hello" > hello.txt
tiny-git add hello.txt
tiny-git commit -m "Initial commit"
# Make a modification and inspect diff
echo "world" >> hello.txt
tiny-git diff
# Stage and commit
tiny-git add hello.txt
tiny-git commit -m "Update greeting"
# Inspect commit log
tiny-git log --oneline
# Verify repository integrity
tiny-git fsckChecking repository...
✓ 3 objects checked
✓ 1 refs checked
✓ HEAD valid
✓ index valid
✓ commit graph valid
Repository is healthy.
Every command is implemented in tinygit.cli and executable via tiny-git <command> [options]:
┌───────────────────┐
│ TINY-GIT CLI │
└─────────┬─────────┘
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
[Repository] [Working Tree] [Inspection]
• init • add • status
• config • rm • log
• restore • diff
• commit • show
• branch • cat-file
• checkout • rev-parse
• merge • fsck
tiny-git init [-b <default-branch>] [<path>]: Initializes.tinygit/, default branch (main), empty index, and INI config withcore.repositoryformatversion = 1.tiny-git config <key> [<value>]: Gets or sets repository configuration in.tinygit/config.
tiny-git add <files>...: Stages files, directories, or all changes/deletions (.) in.tinygit/index.tiny-git rm <files>... [--cached]: Stages file deletions. Use--cachedto remove from.tinygit/indexwhile leaving the working tree file untouched.tiny-git restore <files>... [--staged]: Restores working tree files from index (default) or unstages index modifications fromHEAD(--staged).tiny-git commit -m "<message>" [--allow-empty]: Creates an immutable snapshot from the staging area.
tiny-git status: ComparesHEAD -> Index -> Working Treeto report staged, unstaged, unmerged, and untracked changes.tiny-git log [--oneline] [-n <max-count>]: Traverses parent chains to format commit logs matching Git.tiny-git diff [--cached]: Generates unified diffs (--- a/file/+++ b/file) for unstaged or staged changes.tiny-git show [<rev>]: Displays commit metadata and unified diff against its first parent.tiny-git cat-file <object> [-t | -s | -p]: Low-level object database inspection. Show type (-t), size (-s), or pretty-printed content (-p).tiny-git rev-parse <rev>: Resolves revision expressions (HEAD~1,main, short SHAs) to a full 40-character SHA-1.tiny-git fsck: Checks repository integrity, object references, index blobs, and commit graph acyclicity.
tiny-git branch [<name>]: Lists branches (indicating current branch with*) or creates a new branch pointing to HEAD.tiny-git checkout <target>: Safely switches branches or checks out commit SHAs (detached HEAD) without overwriting uncommitted work.tiny-git merge <branch> [--abort]: Executes fast-forward merges or true 3-way line merges, reporting clean convergence or conflict marker files. Use--abortto discard a conflicted merge and restore pre-merge state.
tiny-git follows a strictly layered architecture to separate user input parsing from version-control domain logic and filesystem serialization:
┌─────────────────────────────────────────┐
│ CLI Layer │ (cli.py, __main__.py)
│ • Argument parsing (argparse) │
│ • Human-readable error formatting │
└────────────────────┬────────────────────┘
│
┌────────────────────▼────────────────────┐
│ Repository Layer │ (repository.py, config.py)
│ • Central facade (Repository class) │
│ • Enforces format version & invariants │
└────────┬───────────┬───────────┬────────┘
│ │ │
┌────────────────────┼───────────┴───────────┼────────────────────┐
▼ ▼ ▼ ▼
┌───────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Status & │ │ Branching & │ │ Merge & Diff │ │ Working Tree │
│ Working │ │ Refs Logic │ │ Engine │ │ I/O & Safe │
│ Tree Diff │ │ (refs.py) │ │(merge/diff.py)│ │ Checkout │
└─────┬─────┘ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │ │
└────────────────────┼───────────────────────┴────────────────────┘
▼
┌─────────────────────────────────────────┐
│ Staging Area / Index │ (index.py)
│ • Structured JSON index (.tinygit/index)│
└────────────────────┬────────────────────┘
│
┌────────────────────▼────────────────────┐
│ Content-Addressable Storage │ (objects.py)
│ • SHA-1 digest ("<type> <size>\0...") │
│ • Blob, Tree, Commit serialization │
└────────────────────┬────────────────────┘
│
┌────────────────────▼────────────────────┐
│ Local Filesystem │ (.tinygit/objects/xx/yy...)
└─────────────────────────────────────────┘
For detailed architectural diagrams, see docs/architecture.md.
tiny-git stores all data inside .tinygit/ at the root of your project:
.tinygit/
├── objects/ # SHA-1 content-addressed database (sharded by xx/yyyy...)
├── refs/
│ └── heads/ # References storing 40-character commit SHA-1 hashes
├── HEAD # Symbolic reference pointing to current branch (ref: refs/heads/main)
├── index # Structured JSON staging area representing proposed next snapshot
└── config # Local repository configuration file (INI format)
Blob: Raw file contents (blob 12\0hello world\n). Filenames are never stored in Blobs.Tree: Directory structures (040000 <sha> src\n100644 <sha> README.md\n).Commit: Root Tree snapshot, 0..N parents, author identity, timestamp, and message.
See docs/object-model.md and docs/storage.md for full serialization specifications.
B (current branch / HEAD)
/ \
A C (incoming branch)
\ /
M (resulting merge commit)
- Branching: Branches are lightweight text files in
.tinygit/refs/heads/storing commit SHAs. - Safe Checkouts: Before any checkout or merge,
tiny-gitchecks if any file changing between the current commit and target commit has uncommitted local edits or untracked collisions. If so, it aborts immediately withWorkingTreeDirtyError. - Three-Way Merge:
tiny-gitlocates the Lowest Common Ancestor (A) of current HEAD (B) and target branch (C) using BFS. For divergent histories, it comparesA -> BandA -> C:- Non-conflicting edits are combined automatically.
- Overlapping edits inject Git-compatible conflict markers:
<<<<<<< HEAD Current branch changes ======= Incoming branch changes >>>>>>> feature - Clean merges create a multi-parent commit object referencing both
BandC. - Use
tiny-git merge --abortat any time during a conflicted merge to restore the repository to its exact pre-merge state.
See docs/branching.md and docs/merging.md for deeper walkthroughs.
tiny-git is built around an explicit engineering specification defined in docs/invariants.md:
| # | Invariant | Enforcement Mechanism | Verifying Test Suite |
|---|---|---|---|
| 1 | Object Reachability | Every referenced object ID must exist in .tinygit/objects/. |
test_fsck.py, test_state_machine.py |
| 2 | Object Immutability | Written objects are never overwritten; hash checksums are verified on read. | test_invariants.py, test_fsck_adversarial.py |
| 3 | Branch Reference Validity | Branch files must store 40-character hex strings pointing to valid commits. | test_branch.py, test_invariants.py |
| 4 | HEAD Consistency | .tinygit/HEAD stores either a valid symbolic ref or a detached SHA-1. |
test_checkout.py, test_fsck.py |
| 5 | Commit Tree Existence | Every Commit's root tree header must reference an existing Tree object. |
test_invariants.py, test_fsck.py |
| 6 | Tree Hierarchy Integrity | Every Tree entry must reference an existing valid Blob or Tree object. | test_checkout_topology.py, test_fsck.py |
| 7 | Worktree Data Protection | Checkouts and merges never overwrite uncommitted edits or untracked files. | test_checkout.py, test_merge.py, test_merge_abort_brutal.py |
| 8 | Atomic Filesystem Durability | Metadata writes use temp files, os.fsync(), and atomic rename (.replace()). |
test_atomic_crash_safety.py, test_state_machine.py |
tiny-git has an extensive test suite built with pytest where every test uses an isolated temporary directory (tmp_path):
pytest -v- Unit Tests: Object hashing, blob deduplication, JSON index serialization, reference management, diff generation, revision resolution, fsck diagnostics,
cat-file/showinspection,rm/restorebehaviors. - Property & Invariant Tests (
test_invariants.py):- Same content -> same SHA-1 ID; different content -> different SHA-1 ID.
- Commit hash uniqueness under message/author/tree changes.
- Branch isolation invariance.
- Exact snapshot reproduction on checkout.
- Clean merge union invariance.
- Crash Consistency & Atomic Writes (
test_atomic_crash_safety.py):- Verifies zero temporary file leakage on write, format version rejection, and
fsckcorruption detection.
- Verifies zero temporary file leakage on write, format version rejection, and
- Comprehensive Merge Matrix (
test_merge_matrix.py,test_merge_adversarial_v2.py,test_merge_abort_brutal.py):- Over 25 dedicated test functions testing 30+ scenarios: adjacent line edits, identical edits, overlapping conflict hunk injection, modify/delete conflicts, delete/modify conflicts, binary file collisions, empty files, criss-cross DAG LCA ancestor resolution, and exact pre-merge state restoration on abort.
- State-Machine Fuzzer (
test_state_machine.py):- Runs 10 distinct random seeds x 100 operations each (1,000 total operations) across files, staging, commits, branches, checkouts, and merges, asserting all 8 invariants after every step.
- End-to-End Workflow & CLI Contract Tests (
test_end_to_end.py,test_cli_contract.py):- Complete lifecycle verification across Python
RepositoryAPI and live CLI subprocess execution, auditing error messages, non-zero exit codes, and zero tracebacks.
- Complete lifecycle verification across Python
- Static Analysis:
ruff check src tests: 0 errors (All checks passed!).mypy src: 0 errors (Success: no issues found in 16 source files).
tiny-git includes an automated benchmark suite verifying linear
python3 benchmarks/run_benchmarks.py| Operation | 100 Files | 1,000 Files | 2,500 Files | Scaling Behavior |
|---|---|---|---|---|
init |
1.16 ms | 1.23 ms | 0.96 ms | |
add . |
35.46 ms | 345.97 ms | 806.34 ms | |
commit |
1.33 ms | 6.08 ms | 13.71 ms | |
status |
16.80 ms | 150.05 ms | 374.86 ms | |
diff |
21.24 ms | 191.54 ms | 478.00 ms | |
checkout |
18.63 ms | 177.00 ms | 435.75 ms | |
fsck |
18.16 ms | 156.09 ms | 369.77 ms |
- v1.0.0 (Current Frozen Release):
- 16 Core CLI commands:
init,config,add,commit,status,log,branch,checkout,diff,merge,fsck,cat-file,show,rev-parse,rm,restore. - Correctness hardening: atomic
fsyncwrites,core.repositoryformatversion = 1,tiny-git fsck, safe checkouts,merge --abort, 98 automated tests.
- 16 Core CLI commands:
- v1.1 — Reference Extensions & Maintenance (Planned):
tiny-git tag(lightweight and annotated tags in.tinygit/refs/tags/).tiny-git count-objectsand reachability analysis.tiny-git gc --dry-runandtiny-git prune --dry-run.
- v2.0 — Remote Protocols (Future Exploration): Simple HTTP/SSH loose-object transfer (
clone,fetch,push,pull).
This project is open-source software licensed under the MIT License. See LICENSE for details.