I built a version control system CLI in JavaScript using only Node.js and the fs API. No third-party dependencies. The goal: understand how Git actually works by implementing the parts people use every day — init, add, commit, log, and branch.
How Git stores files
The thing that surprised me most is that Git is fundamentally a content-addressable filesystem. Every file you commit is stored as a blob whose name is the SHA-1 hash of its contents. Two files with identical content are stored exactly once, regardless of filename or path — elegant and efficient in a way that isn't obvious until you build it.
In my implementation I used Node's built-in crypto module to generate SHA-256 hashes and the fs API to write blob objects into a .vcs/objects/ directory — structured by the first two hex characters of the hash, exactly like Git's .git/objects/ layout.
Commits are just tree snapshots
A commit in Git is not a diff. It is a pointer to a tree object representing the entire state of the working directory at that moment. Each tree entry is either a blob (a file) or another tree (a subdirectory). The tree object is also content-addressed, so if nothing in a directory changes between commits, Git reuses the exact same tree object.
Implementing this from scratch made me realise that Git is not magic — it is just a very well-designed use of hashing and linked lists.
My commit objects store: a reference to the root tree hash, a parent commit hash (or null for the first commit), a timestamp, and a message. That linked list of commit objects is your branch history.
What branching actually is
A branch is just a text file containing a commit hash. When you create a new branch, you create a file in .vcs/refs/heads/ pointing to the current commit. HEAD is another text file storing which branch you're on. Understanding this made merge conflicts, rebasing, and detached HEAD states completely unscary.
Would I recommend building this?
Absolutely. It's a weekend-sized project with career-sized returns. Pick any VCS feature you find confusing, implement it yourself, and you'll never be confused by it again. The code is on my GitHub.