I Rebuilt Git Without Git — 3,390 Lines of Python, Zero Dependencies
For the Zero Dependency Hackathon, I decided not to build another application that simply happens to have an empty requirements.txt.
I wanted to answer a harder question:
How much of a real developer tool can you rebuild from scratch using only Python's standard library?
My answer is pygit — a content-addressed, Git-like version control system implemented in a single Python file.
3,390 lines.
1 Python file.
141 tests.
30 commands.
0 external dependencies.
And no, it isn't a wrapper around Git.
pygit_single.py never invokes the real git executable. The object model, staging area, refs, merge logic, CLI, and synchronization protocol are implemented from scratch.
The packages I would normally install
If I wanted to build something like this in Python, there are plenty of libraries that could handle much of the work.
I could reach for:
- GitPython / dulwich / pygit2 for Git functionality
- diff-match-patch for diffing
- zstandard / python-lz4 for compression
- PyYAML for structured state
- Click / Typer for the CLI
- requests / paramiko for networking
- pathspec for gitignore-style matching
- pytest for testing
Instead, I removed the dependencies entirely.
The challenge wasn't simply deleting requirements.txt.
The challenge was rebuilding what those packages normally give you.
What did I actually build?
1. Content-addressed object storage
The foundation of pygit is a content-addressed object model.
Files become blobs, directory structures become trees, commits contain tree and parent references, and tags point to commits.
Objects are hashed using hashlib.sha256, compressed with zlib, and stored as loose objects or packed during garbage collection.
No database.
No Git library.
Just Python's standard library and the filesystem.
2. A real staging area
add builds a JSON index mapping paths to blob hashes.
Branch switching doesn't just change a pointer — the index is rebuilt from the target tree while the working tree is updated, keeping status consistent after a checkout.
3. Three-way merging
One of the most important parts I wanted to implement rather than fake was merging.
pygit performs three-way merges using difflib.SequenceMatcher at the line level.
It handles:
- Fast-forward merges
- Clean three-way merges
- Conflicting merges
When changes actually collide, it produces genuine conflict markers:
<<<<<<<
=======
>>>>>>>
and returns a non-zero exit status rather than silently resolving the conflict.
4. A surprisingly large Git-style command surface
This isn't just init, add, and commit.
There are 30 commands, including:
branch, checkout, switch, merge, tag, reset, stash, cherry-pick, rebase, revert, reflog, blame, gc, clean, remote, serve, fetch, clone, push, and config.
I also tested behavior against real Git where there was a meaningful comparison to make, including failure cases.
The part that really had no package to hide behind: networking
For remote synchronization, I didn't use Git's protocol or install a networking abstraction.
pygit has its own client/server synchronization protocol.
A clone/fetch follows a flow like:
client → WANT <ref>
server → TIP <sha>
client → HAVE <known shas>
server → streams missing objects
Push performs a fast-forward check and refuses to update a remote when the histories have diverged.
The transport uses Python's standard-library socket, with binary length-framed object transfer.
So:
No requests.
No paramiko.
No Git wire protocol.
Just sockets and a protocol designed for pygit.
What replacing the dependencies looked like
This is where the "zero dependency" requirement became an actual engineering challenge.
The substitution table ended up looking like this:
| Normally install | I used instead |
|---|---|
| GitPython / dulwich / pygit2 | My own implementation |
| diff libraries | difflib |
| zstandard / lz4 | zlib |
| PyYAML | json |
| Click / Typer | argparse |
| requests / paramiko | socket |
| pathspec | fnmatch |
| pytest | unittest |
| Directory-walking helpers | os.walk |
These aren't hypothetical substitutions. STDLIB.md documents the replacements and states that each row is backed by an actual import/use in the codebase.
The result
The final implementation is a single Python source file.
There is no packaging step.
There is no pip install.
There is no vendored dependency tree.
There is no subprocess call to Git.
Running it is simply:
python3 pygit_single.py init
python3 pygit_single.py add .
python3 pygit_single.py commit -m "First commit"
The project has 141 tests covering object serialization, repository operations, diffs, status, ignore rules, all three merge paths, stash, tags, reset, reflog, cherry-pick, rebase, revert, packfiles, garbage collection, blame, cleanup safety, colored output, and the network protocol.
The project also has a reproducibility check based on the SHA-256 of the source file, so the exact source a judge evaluates can be independently verified.
What I learned
The interesting lesson wasn't simply:
"You can use Python's standard library."
We already know that.
The interesting lesson was that dependencies often represent engineering decisions that you have to make yourself when you remove them.
A library doesn't just save lines of code.
It saves you from thinking about:
- Object formats
- Serialization
- Merge semantics
- Filesystem state
- Protocol framing
- History traversal
- Failure modes
- CLI behavior
- Testing infrastructure
- Edge cases
Building pygit forced me to understand those pieces instead of treating them as black boxes.
That's what made the project worth building.
pygit is my submission to the Zero Dependency Hackathon — Track A: Developer Tools & CLI.
Project
GitHub Repository — Lakshya-Varshney/pygit
Demo
Watch the pygit demo on YouTube



Top comments (0)