A git implementation built from scratch in TypeScript for understanding it.
mygit implements the core of git: init, add, commit, and log. Every design decision mirrors how real git works — content-addressable object storage, hash pointers for commit history, a persistent staging area. Built to demystify, not to ship.
There is a companion blog series documenting the entire thought process, architecture decisions, and lessons learned. Start there if you want to understand the why behind the code.
mygit init # initialize a repository in the current directory
mygit add <file> [file...] # stage one or more files
mygit commit -m "your message" # commit everything in the staging area
mygit log # view commit historymygit init
Creates a .mygit/ folder and writes { "HEAD": null } to mygit.json. That is the entire repository state for a fresh repo.
mygit add
Reads each file, hashes the contents with SHA-1, compresses with gzip, and stores the result in .mygit/objects/<hash>. Records the file name and hash in .mygit/stage.json.
mygit commit
Loads the staging area, builds a file tree (carrying forward unchanged files from the previous commit), creates a commit object, hashes it, saves it to .mygit/objects/<commitHash>, updates mygit.json to point HEAD at the new hash, and clears the staging area.
mygit log
Reads HEAD, follows the parentHash chain through commit files, and prints each commit with its hash, date, and message.
.mygit after a few commits:
.mygit/
├── objects/
│ ├── 51d292... ← compressed file blob
│ ├── 9d4e2f... ← first commit object
│ └── 7c8b1a... ← second commit object
└── mygit.json ← { "HEAD": "7c8b1a..." }
History is reconstructed by following parentHash pointers — no linked list serialization, no circular reference issues. This is exactly how real git works.
- TypeScript with ESM (
type: module,NodeNextresolution) tsxfor developmentyocto-spinnerfor CLI output- Node built-ins:
fs,crypto,zlib,path
git clone <repo>
cd BuildYourOwnGit
npm install
npm run build
npm linkThen navigate to any folder and try it:
mygit init
mygit add index.ts
mygit commit -m "first commit"
mygit logsrc/
├── constants/
│ └── commands.ts # command name constants
├── repository/
│ ├── MyGit.ts # core types and pure functions
│ ├── init.ts # mygit init
│ ├── add.ts # mygit add
│ ├── commit.ts # mygit commit
│ └── log.ts # mygit log
├── utils/
│ └── getMygitPath.ts # resolves .mygit path from cwd
├── validations/
│ ├── checkRepositoryExist.ts
│ └── checkFilePaths.ts
└── index.ts # CLI entry point
Contributions are welcome. If you want to extend mygit, fix a bug, or improve the code — open a PR.
A few good places to start:
mygit checkout— decompress blobs fromobjects/and restore files to their paths based on a commit's file treemygit diff— compare two commit trees node by node, files with different hashes changed, missing files were added or deletedmygit status— compare the working directory against the latest commit tree to show unstaged changes- Branches — a branch is just a named file containing a commit hash, almost exactly like HEAD
- gitignore support — skip matched patterns during
mygit add
If you are implementing something new, keep it consistent with the existing patterns — plain types over classes, pure functions where possible, load from disk → do the work → save back to disk.
- Branches
- Checkout
- Diff
- gitignore
- Remotes
All of these build directly on what is here. The foundation is solid.