petersalomonsen/wasm-git
petersalomonsen/wasm-git has 850 GitHub stars and 49 forks. GitHot measured +1 stars in 24 hours, +2 in 7 days, and +3 in 15 days. Last checked 2026-11-09 18:53 UTC. Last push 2026-10-18.
GIT for nodejs and the browser using https://libgit2.org compiled to WebAssembly with https://emscripten.org
Measured star growth
- in 24h
- +1
- in 7 days
- +2
- in 15 days
- +3
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
OriginalWasm-git
(Wasm should be pronounced like awesome starting with a W — as stated in the WebAssembly Music talk at WebAssembly Summit 2020).
Git for Node.js and the browser, using libgit2 compiled to WebAssembly with Emscripten.
Imagine that every app you use kept a complete journal of your data: every change recorded, a backup on your own computer that is truly yours, and synchronization between your devices whenever you want it. Software developers have had exactly this for decades — the tool is called Git. Wasm-git brings that same engine to web applications: store user data locally in the browser with full change history, and push/pull to a remote when — and only when — the user chooses. The application doesn't need to expose any Git terminology: a single "synchronize data" button can do all of it behind the scenes.
Scope
Wasm-git is a port of libgit2 to WebAssembly, not a new git library. You use it through lg2, libgit2's example command-line program, which this repo extends so it resembles the git command line: lg.callMain(['clone', url, 'repo']) in JavaScript, or lg2 clone url repo in a WASI runtime. Every build variant exposes the same commands, and only where files and network come from differs.
This is deliberate. Git's command language is already designed, documented and widely known, so wasm-git adds no API of its own to learn or maintain, and it follows libgit2 upstream closely. lg2 covers a subset of git's commands and options; missing ones are added by extending the examples in libgit2patchedfiles. If you need a programmatic API, build it on top of these commands or compile libgit2 to wasm with your own bindings.
Quick start
npm install wasm-git
The package ships prebuilt WebAssembly binaries in several variants (see Choosing a variant below). The easiest one to start with is the async variant, which runs on the browser main thread or in Node.js with plain async/await:
import initGit from 'wasm-git/lg2_async.js';
const lg = await initGit();
const FS = lg.FS;
// Git needs to know who you are before committing
FS.writeFile('/home/web_user/.gitconfig',
'[user]\nname = Your Name\nemail = [email protected]');
// Create a repository, add a file, and commit
FS.mkdir('/myrepo');
FS.chdir('/myrepo');
await lg.callMain(['init', '.']);
FS.writeFile('data.json', JSON.stringify({ hello: 'world' }));
await lg.callMain(['add', 'data.json']);
await lg.callMain(['commit', '-m', 'first commit']);
Cloning and pushing over HTTP works the same way:
await lg.callMain(['clone', 'https://your-git-server.example.com/repo.git', 'repo']);
FS.chdir('repo');
// ...edit files, add, commit...
await lg.callMain(['push']);
When cloning from the browser, the git server must allow cross-origin requests (CORS) — or be served from the same origin. githttpserver is a ready-made reference server, and its live instance at https://wasm-git.petersalomonsen.com/ is an open playground where you can try cloning, editing, committing and pushing directly in the browser (please don't abuse it — it's there for you to test).
The files can also be loaded from public CDNs such as unpkg or jsDelivr (e.g. https://unpkg.com/wasm-git/lg2_async.js).
Choosing a variant
| Variant | File | Where it runs | Persistence | Notes |
|---|---|---|---|---|
| Sync | lg2.js |
Browser: Web Worker only. Node.js: main thread or worker_threads | MEMFS / IDBFS / NODEFS | Smallest binary; synchronous callMain. Needs a worker in the browser because of synchronous HTTP and long-running operations. |
| Async | lg2_async.js |
Browser main thread or worker; Node.js | MEMFS / IDBFS / NODEFS | Asyncify build: await lg.callMain(...). Larger binary, simplest client code. |
| OPFS (pthreads) | lg2_opfs.js |
Web Worker, requires cross-origin isolation | OPFS | Fastest OPFS variant; needs COOP/COEP headers for SharedArrayBuffer. |
| OPFS (JSPI) | lg2_opfs_jspi.js |
Web Worker, no isolation needed | OPFS | SAB-free, smallest OPFS binary; needs a JSPI-capable browser. |
| OPFS (ASYNCIFY) | lg2_opfs_async.js |
Web Worker, no isolation needed | OPFS | SAB-free universal fallback; largest binary. |
| OPFS auto-loader | lg2_opfs_auto.js |
Web Worker | OPFS | Picks the best supported OPFS build at runtime and exposes a uniform API — see OPFS variants and the runtime loader. |
For browser persistence with the sync/async variants, mount IDBFS and call FS.syncfs — or use the OPFS variants, which persist through the filesystem itself with better performance and quota management.
Complete working examples for every variant are in the test folders:
- test for Node.js
- test-browser for the sync version in a web worker
- test-browser-async for the async version
- test-browser-opfs for the OPFS (pthreads/WASMFS) version
- test-browser-opfs-noniso for the SAB-free OPFS variants (ASYNCIFY + JSPI) and the runtime loader
Built with wasm-git
- Ariz-Portfolio — tracks crypto asset balances, profits and losses on the NEAR protocol blockchain. Uses wasm-git to store portfolio data with full change history on the user's own machine, and lets the user download a complete copy — history included — to their computer. Its optional cloud sync is end-to-end encrypted with encrypted-git-storage: repositories are encrypted before upload with keys only the user holds.
- Y42 — a data pipeline platform (DataOps) where every integration, model and dashboard is versioned in git. In the Data Council talk Using GIT as a NoSQL Database, Y42's founder Hung Dang explains that "under the hood we developed our own git client for the browser using wasm-git, which is a WebAssembly compiled version of libgit2" — and recommends checking out wasm-git at the end of the talk.
- WebAssembly Music — a music-making environment in the browser and wasm-git's original driving use case: compositions and instruments are stored and versioned locally, and synchronized with a remote when the user chooses. See it in action in this video playlist.
- AutoDev — an AI-native multi-agent development platform built on Kotlin Multiplatform: wasm-git is the git backend for its WebAssembly target, powering clone, log, diff and status in
GitOperations.wasmJs.kt. - githttpserver — a CORS-enabled git server you can host yourself, plus the browser playground at https://wasm-git.petersalomonsen.com/ showing basic operations: clone, edit, add, commit, push and pull.
- encrypted-git-storage — an addon that encrypts and decrypts repositories locally in the browser (AES-256-GCM); the server only ever sees ciphertext. Includes a remote helper for the native git client, so users can clone their own application data — decrypted, with full history — straight to their computer.
Wasm-git is also covered in the book Building and Deploying WebAssembly Apps (BPB Publications, 2025) by wasm-git author Peter Salomonsen.
OPFS usage example
The pthreads OPFS version must run in a Web Worker because it requires SharedArrayBuffer (for pthreads), which in turn requires Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp (or credentialless) HTTP headers.
worker.js (Web Worker):
// Import the OPFS-enabled wasm-git module
const lgMod = await import('./lg2_opfs.js');
const lg = await lgMod.default();
const FS = lg.FS;
// WASMFS doesn't pre-create /home/web_user like MEMFS
try { FS.mkdir('/home'); } catch(e) {}
try { FS.mkdir('/home/web_user'); } catch(e) {}
FS.writeFile('/home/web_user/.gitconfig',
'[user]\n name = Your Name\n email = [email protected]');
// Create an OPFS-backed directory using the WASMFS OPFS backend
const backend = lg._lg2_create_opfs_backend();
const workingDir = '/opfs';
// Use ccall to marshal the JS string to a C pointer
lg.ccall('lg2_create_directory', 'number', ['string', 'number', 'number'],
[workingDir, 0o777, backend]);
// Clone using absolute path to avoid CWD ambiguity with WASMFS
const repoDir = workingDir + '/myrepo';
lg.callMain(['clone', 'https://github.com/user/repo.git', repoDir]);
// Work around a WASMFS getcwd() bug: create a root symlink so the path
// returned by getcwd() resolves correctly for libgit2's repo discovery.
FS.symlink(repoDir, '/myrepo');
FS.chdir(repoDir);
// Re-set CWD before each callMain since WASMFS may reset it
FS.chdir(repoDir);
FS.writeFile('newfile.txt', 'Hello OPFS!');
FS.chdir(repoDir);
lg.callMain(['add', 'newfile.txt']);
FS.chdir(repoDir);
lg.callMain(['commit', '-m', 'Add new file']);
FS.chdir(repoDir);
lg.callMain(['push']);
postMessage({ done: true });
See test-browser-opfs/worker.js for a complete working example.
OPFS variants and the runtime loader
OPFS's open/dir/metadata operations (getFileHandle, getDirectoryHandle,
removeEntry, entries, createSyncAccessHandle) are async-only. To call
them from libgit2's synchronous file IO, wasm-git ships three OPFS builds that
bridge async→sync in different ways:
| Build | File | Bridge | SharedArrayBuffer / cross-origin isolation | wasm size | Relative speed |
|---|---|---|---|---|---|
| pthreads / WASMFS | lg2_opfs.js |
WASMFS OPFS backend blocks a worker thread via Atomics.wait |
Required (COOP/COEP) | ~920 KB | fastest |
| JSPI | lg2_opfs_jspi.js |
JSPI — native WebAssembly stack switching | Not required | ~805 KB | fast |
| ASYNCIFY | lg2_opfs_async.js |
Asyncify — wasm rewritten to unwind/rewind its stack | Not required | ~1.5 MB | slower (instrumentation overhead) |
(wasm sizes are for -O3 release builds and will drift; JSPI is the smallest
because it needs no stack-rewriting instrumentation, ASYNCIFY the largest for the
same reason.)
The pthreads build needs Cross-Origin-Opener-Policy / Cross-Origin-Embedder-Policy
headers (cross-origin isolation), which some hosts (e.g. NEAR web4) cannot set.
The JSPI and ASYNCIFY builds are SAB-free: they persist to OPFS by
suspending the wasm stack across the async OPFS calls, so they run with
self.crossOriginIsolated === false. All three must run inside a Web Worker
(OPFS sync access handles require one).
Runtime loader (lg2_opfs_auto.js)
lg2_opfs_auto.js picks the most optimal supported build at runtime and exposes
a uniform git API over all three. Selection order:
- pthreads when the page is cross-origin isolated
(
crossOriginIsolated === trueandSharedArrayBufferexists); - JSPI when available (
WebAssembly.Suspending/WebAssembly.promising); - ASYNCIFY otherwise (universal fallback).
If OPFS itself is unavailable (navigator.storage.getDirectory missing, insecure
context), selectOpfsVariant() returns null — fall back to the non-OPFS IDBFS
build (lg2.js).
// inside a Web Worker (type: 'module')
import { loadOpfsGit, selectOpfsVariant } from './lg2_opfs_auto.js';
console.log(selectOpfsVariant()); // 'pthreads' | 'jspi' | 'asyncify' | null
const git = await loadOpfsGit({ user: 'Your Name', email: '[email protected]' });
console.log(git.variant); // which build was loaded
await git.clone('https://example.com/repo.git', 'repo.git');
await git.writeFile('repo.git', 'a.txt', 'hello');
await git.addCommitPush('repo.git', 'a.txt', 'add a.txt');
console.log(git.readFile('repo.git', 'a.txt')); // 'hello'
// After a reload, restore an existing repo from OPFS before using it:
await git.syncRepo('repo.git');
The detection helpers (detectOpfsEnvironment, selectOpfsVariant) are pure
functions that accept an env object, so they can be unit-tested or forced
(see test/opfs-detect.spec.js).
See test-browser-opfs-noniso/worker.js for a complete worker built on the loader.
Filesystem backends
Wasm-git supports multiple filesystem backends for different use cases:
MEMFS (Memory File System)
- Use case: In-memory storage, not persisted
- Build target: Default (
./build.sh Release) - Browser support: All browsers
IDBFS (IndexedDB File System)
- Use case: Browser persistent storage using IndexedDB
- Build target: Default (
./build.sh Release) - Browser support: All browsers with IndexedDB
NODEFS (Node.js File System)
- Use case: Node.js native filesystem access
- Build target: Default (
./build.sh Release) - Platform: Node.js only
OPFS (Origin Private File System)
- Use case: Modern browser persistent storage with better performance and quota management
- Browser support: Chrome 86+, Edge 86+, Firefox 111+, Safari 15.2+ (JSPI variant: Chromium-based browsers with JSPI)
- Advantages: Better performance and quota compared to IDBFS
- Requirements: Must run in a Web Worker (OPFS sync access handles require one)
- Three variants — see OPFS variants and the runtime loader:
- pthreads / WASMFS (
./build.sh Release-opfs→lg2_opfs.js): synchronouscallMain; requires cross-origin isolation (Cross-Origin-Opener-Policy: same-origin+Cross-Origin-Embedder-Policy: require-corp/credentialless) for SharedArrayBuffer. - JSPI (
./build.sh Release-opfs-jspi→lg2_opfs_jspi.js): SAB-free; asynccallMain; smallest binary. - ASYNCIFY (
./build.sh Release-opfs-async→lg2_opfs_async.js): SAB-free; asynccallMain; universal fallback, largest binary. lg2_opfs_auto.jsselects the best supported variant at runtime.
- pthreads / WASMFS (
WASI component (experimental)
Besides the Emscripten builds for JavaScript hosts, lg2 can be built as a
WASI 0.2 component that needs no JavaScript at all: files go
through wasi:filesystem and HTTP(S) through wasi:http, so it runs in any
component runtime, such as Wasmtime, and the host decides
which directories and network access it gets.
./setup.sh
./wasibuild/build.sh # downloads the pinned wasi-sdk and wit-bindgen on first run
# mount ./work as the guest's root directory and allow outgoing HTTP
wasmtime run -S http --dir ./work::/ --env HOME=/ \
wasibuild/examples/lg2.wasm clone https://github.com/octocat/Hello-World.git hello
The component is not part of the npm package. It currently exposes the lg2
command line (wasi:cli/run), and HTTP authentication and redirects are not
supported yet. npm run test-wasi runs test-wasi against the
build in Wasmtime.
wasibuild/server is an example in the other direction: a
git smart HTTP server built on libgit2, exported as wasi:http/incoming-handler
and serving bare repositories from a mounted directory (protocol v0, no auth):
./wasibuild/build.sh server
wasmtime serve -S cli --dir ./repos::/repos wasibuild/server-build/gitserver.wasm
git clone http://localhost:8080/myrepo.git # serves ./repos/myrepo.git
Setting GIT_SERVER=wasm makes the test suites use it instead of git http-backend.
Building and developing
Compatibility
- libgit2: v1.9.4
- Emscripten: Pinned to 6.0.3
- Node.js: v18+
- Browsers: Modern browsers with WebAssembly support
Prerequisites
- Emscripten SDK (version 6.0.3)
- Node.js (v18 or higher)
- CMake
- Make
Quick Start
Clone the repository
git clone https://github.com/petersalomonsen/wasm-git.git cd wasm-gitInstall and activate Emscripten
git clone https://github.com/emscripten-core/emsdk.git cd emsdk ./emsdk install 6.0.3 ./emsdk activate 6.0.3 source ./emsdk_env.sh cd ..Set up libgit2
./setup.shThis script downloads libgit2 v1.9.4 and applies necessary patches for WebAssembly compilation.
Build the project
cd emscriptenbuild ./build.sh # Debug build (smaller, for development) ./build.sh Release # Release build (optimized)For async versions (with Asyncify support):
./build.sh Debug-async # Debug async build ./build.sh Release-async # Release async buildFor OPFS versions:
./build.sh Release-opfs # pthreads / WASMFS OPFS build (needs COOP/COEP) ./build.sh Release-opfs-jspi # SAB-free OPFS build using JSPI ./build.sh Release-opfs-async # SAB-free OPFS build using ASYNCIFY # (Debug-opfs, Debug-opfs-jspi and Debug-opfs-async also exist)Install npm dependencies
npm installRun tests
npm test # Run Node.js tests npm run test-browser # Run browser tests (sync version) npm run test-browser-async # Run browser tests (async version) npm run test-browser-opfs # OPFS pthreads/WASMFS tests (cross-origin isolated) npm run test-browser-opfs-noniso # SAB-free OPFS tests (ASYNCIFY + JSPI, non-isolated) npm run test-opfs-detect # Loader variant-detection unit tests npm run test-opfs-loader # Multi-browser loader selection tests (Chromium/Firefox/WebKit)
Development Options
GitHub Codespaces
The easiest way to get started is using GitHub Codespaces. The repository includes a .devcontainer configuration that automatically sets up the complete development environment with all dependencies.
Local Development
The Github actions test pipeline shows all the commands needed for CI/CD and can be used as a reference for local setup.
Build Outputs
After building, you'll find the following files in emscriptenbuild/libgit2/examples/:
lg2.jsandlg2.wasm- Synchronous versionlg2_async.jsandlg2_async.wasm- Asynchronous version with Asyncifylg2_opfs.jsandlg2_opfs.wasm- OPFS version with WASMFS + pthreadslg2_opfs_jspi.jsandlg2_opfs_jspi.wasm- SAB-free OPFS version using JSPIlg2_opfs_async.jsandlg2_opfs_async.wasm- SAB-free OPFS version using ASYNCIFY
The runtime loader lg2_opfs_auto.js (a hand-written module, not a build output)
selects the best of the three OPFS builds at runtime — see
OPFS variants and the runtime loader.
These files are also available from npm packages and CDNs for production use.
The WASI component build (wasibuild/build.sh) writes wasibuild/examples/lg2.wasm —
see WASI component (experimental).
Troubleshooting
Build errors: Ensure the Emscripten environment is properly activated:
source /path/to/emsdk/emsdk_env.shTest failures: Remove any stale test directories before running tests:
rm -rf nodefsclonetest npm test
History
Wasm-git started in 2020, and getting libgit2 to run in a JavaScript environment required several fixes to Emscripten itself, all merged upstream that year: #10095, #10526, #10782 and #10669 (NODEFS support).
Explore related projects
codecrafters-io/build-your-own-x
Master programming by recreating your favorite technologies from scratch.
561,398 starspublic-apis/public-apis
A collective list of free APIs
497,679 starsmorluto/rea
Reverse engineer anything with agents, from app behavior down to native binaries.
447,495 starsopenclaw/openclaw
The AI that really does things. Any OS. Any Platform. The lobster way. 🦞
394,261 stars