Best use case
isomorphic-git is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Teams using isomorphic-git should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/isomorphic-git/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How isomorphic-git Compares
| Feature / Agent | isomorphic-git | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
This skill provides specific capabilities for your AI agent. See the About section for full details.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Skill: isomorphic-git
## What this skill knows
This skill covers isomorphic-git v1: a pure JavaScript Git implementation that runs in both Node.js and the browser. It covers the most commonly used APIs, the filesystem abstraction, LightningFS, branch/HEAD manipulation, and common gotchas.
## Installation
```bash
pnpm add isomorphic-git @isomorphic-git/lightning-fs
```
## Core concept: filesystem abstraction
isomorphic-git never touches the real filesystem. Every call takes an `fs` parameter - an object conforming to the `fs.promises` interface from Node.js. In the browser, LightningFS provides this:
```ts
import LightningFS from '@isomorphic-git/lightning-fs';
const fs = new LightningFS('my-app');
// Now fs.promises has: readFile, writeFile, mkdir, rmdir, stat, readdir, unlink, ...
```
In Node.js you pass the real `fs` module:
```ts
import fs from 'node:fs';
import git from 'isomorphic-git';
await git.init({ fs, dir: '/path/to/repo' });
```
## LightningFS
LightningFS stores files in IndexedDB. The constructor name (first argument) is the IndexedDB database name.
```ts
const fs = new LightningFS('git-sandbox');
// Check if repo exists
try {
await fs.promises.stat('/repo/.git');
// repo exists
} catch {
// needs init
}
// Wipe everything
await fs.promises.rm('/repo', { recursive: true }).catch(() => {});
await fs.promises.mkdir('/repo');
```
LightningFS is synchronous-by-default in older versions. Use `fs.promises.*` (async) API to avoid blocking the main thread.
## Core operations
### init
```ts
await git.init({ fs, dir: '/repo' });
// Creates .git directory structure
```
### Writing files (not a git function)
Use the filesystem directly:
```ts
await fs.promises.writeFile('/repo/README.md', '# Hello\n', { encoding: 'utf8' });
```
### add
```ts
// Stage a specific file
await git.add({ fs, dir: '/repo', filepath: 'README.md' });
// Stage all changed files (walk the status matrix)
const matrix = await git.statusMatrix({ fs, dir: '/repo' });
await Promise.all(
matrix
.filter(([, head, workdir, stage]) => workdir !== stage)
.map(([filepath]) => git.add({ fs, dir: '/repo', filepath }))
);
```
### commit
```ts
const oid = await git.commit({
fs,
dir: '/repo',
message: 'Initial commit',
author: {
name: 'Learner',
email: 'learner@sandbox.local',
// timestamp and timezoneOffset are optional (defaults to now)
},
});
// Returns the new commit OID (SHA-1 string)
```
### log
```ts
const commits = await git.log({ fs, dir: '/repo' });
// Returns ReadCommitResult[]
// commits[0] is the newest commit (HEAD)
for (const { commit, oid } of commits) {
console.log(oid, commit.message, commit.parent);
}
```
The `commit` object has: `message`, `tree` (tree OID), `parent` (array of parent OIDs), `author`, `committer`.
### statusMatrix
```ts
const matrix = await git.statusMatrix({ fs, dir: '/repo' });
// Returns [filepath: string, HEAD: 0|1, WORKDIR: 0|1|2, STAGE: 0|1|2|3][]
// Decode status:
// HEAD 0 = file is new (not in last commit)
// HEAD 1 = file was committed
// WORKDIR 0 = file absent on disk
// WORKDIR 1 = file unchanged
// WORKDIR 2 = file added or modified
// STAGE 0 = not staged
// STAGE 1 = same as HEAD (no staged changes)
// STAGE 2 = staged, differs from HEAD
// STAGE 3 = staged with conflict
```
Practical helper:
```ts
function decodeStatus(row: [string, 0|1, 0|1|2, 0|1|2|3]) {
const [file, head, workdir, stage] = row;
if (head === 0 && workdir === 2 && stage === 0) return { file, status: 'untracked' };
if (head === 0 && workdir === 2 && stage === 2) return { file, status: 'new-staged' };
if (head === 1 && workdir === 2 && stage === 1) return { file, status: 'modified-unstaged' };
if (head === 1 && workdir === 2 && stage === 2) return { file, status: 'modified-staged' };
if (head === 1 && workdir === 1 && stage === 0) return { file, status: 'deleted-unstaged' };
if (head === 1 && workdir === 1 && stage === 1) return { file, status: 'unchanged' };
return { file, status: 'unknown' };
}
```
### branch and checkout
```ts
// List branches
const branches = await git.listBranches({ fs, dir: '/repo' });
// Get current branch
const current = await git.currentBranch({ fs, dir: '/repo' });
// Create a branch at current HEAD
await git.branch({ fs, dir: '/repo', ref: 'feature' });
// Switch to a branch (moves HEAD, updates working tree)
await git.checkout({ fs, dir: '/repo', ref: 'feature' });
// Create and switch (equivalent to git checkout -b)
await git.branch({ fs, dir: '/repo', ref: 'feature' });
await git.checkout({ fs, dir: '/repo', ref: 'feature' });
// Delete a branch
await git.deleteBranch({ fs, dir: '/repo', ref: 'feature' });
```
### merge
```ts
const result = await git.merge({
fs,
dir: '/repo',
theirs: 'feature', // branch or commit OID to merge in
author: { name: 'Learner', email: 'learner@sandbox.local' },
});
// result.oid is the new merge commit OID
// result.alreadyMerged is true if fast-forward was used
// result.fastForward is true if no merge commit was needed
```
isomorphic-git does not handle merge conflicts automatically. If there is a conflict, `merge` throws a `MergeConflictError`. The sandbox avoids conflicts by constructing lessons to prevent them.
### diff (reading diff)
```ts
// Get diff of a file between two commits
const result = await git.walk({
fs,
dir: '/repo',
trees: [git.TREE({ ref: 'HEAD' }), git.WORKDIR()],
map: async (filepath, [headEntry, workdirEntry]) => {
if (!headEntry || !workdirEntry) return null;
const [headContent, workdirContent] = await Promise.all([
headEntry.content().then(b => new TextDecoder().decode(b)),
workdirEntry.content().then(b => new TextDecoder().decode(b)),
]);
if (headContent === workdirContent) return null;
return { filepath, headContent, workdirContent };
},
});
```
For a simpler line-by-line diff, pass the contents to a diffing library like `diff` (npm package `diff`).
### resolveRef and readCommit
```ts
// Get the OID of HEAD
const headOid = await git.resolveRef({ fs, dir: '/repo', ref: 'HEAD' });
// Get the OID of a branch tip
const mainOid = await git.resolveRef({ fs, dir: '/repo', ref: 'main' });
// Read a specific commit
const { commit } = await git.readCommit({ fs, dir: '/repo', oid: headOid });
```
### writeRef (for implementing git reset)
isomorphic-git has no `git reset` equivalent. Implement it:
```ts
async function gitReset(fs: any, dir: string, ref: string, mode: 'soft' | 'mixed' | 'hard') {
// Resolve target OID
const targetOid = await git.resolveRef({ fs, dir, ref });
// Move branch pointer
const branch = await git.currentBranch({ fs, dir });
if (branch) {
await git.writeRef({ fs, dir, ref: `refs/heads/${branch}`, value: targetOid, force: true });
}
if (mode === 'soft') return; // Done: staging preserved
// Clear staging area (mixed)
const { commit } = await git.readCommit({ fs, dir, oid: targetOid });
await git.checkout({ fs, dir, ref: targetOid, noCheckout: mode === 'mixed' });
if (mode === 'hard') {
// Update working tree to match target commit
await git.checkout({ fs, dir, ref: targetOid });
}
}
```
## Error types
```ts
try {
await git.merge({ ... });
} catch (err) {
if (err.name === 'MergeConflictError') {
// conflicting files: err.data.filepaths
} else if (err.name === 'NotFoundError') {
// branch or ref does not exist
} else if (err.name === 'CheckoutConflictError') {
// uncommitted changes would be overwritten
}
}
```
## HEAD states
- `currentBranch` returns the branch name (string) when HEAD points to a branch.
- `currentBranch` returns `undefined` when HEAD is detached (points directly to a commit OID).
- `resolveRef({ ref: 'HEAD' })` always returns the commit OID regardless of detached state.
## Performance in the browser
- Each LightningFS operation is an IndexedDB transaction. Batching writes reduces latency.
- `git.log` fetches all commits - on repos with many commits, pass `depth` to limit: `git.log({ depth: 50, ... })`.
- `git.statusMatrix` walks the entire working tree. On repos with many files this is slow. Call it only when you need to update the UI.
- For the sandbox (max ~20 files), performance is not a concern.
## Testing isomorphic-git code
Use a real LightningFS in tests (jsdom environment includes IndexedDB via fake-indexeddb or idb-keyval):
```ts
import LightningFS from '@isomorphic-git/lightning-fs';
import git from 'isomorphic-git';
const fs = new LightningFS('test');
const dir = '/test-repo';
beforeEach(async () => {
await fs.promises.rm(dir, { recursive: true }).catch(() => {});
await fs.promises.mkdir(dir);
await git.init({ fs, dir });
});
test('commit stores message', async () => {
await fs.promises.writeFile(`${dir}/a.txt`, 'hello');
await git.add({ fs, dir, filepath: 'a.txt' });
await git.commit({ fs, dir, message: 'add a', author: { name: 'T', email: 't@t.com' } });
const [{ commit }] = await git.log({ fs, dir });
expect(commit.message).toBe('add a\n');
});
```
Note: isomorphic-git appends a newline to commit messages. Strip it when displaying.
## Packages required
```json
{
"dependencies": {
"isomorphic-git": "^1.27.0",
"@isomorphic-git/lightning-fs": "^4.6.0"
}
}
```
No additional polyfills are needed in modern browsers or Node.js 18+.Related Skills
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview
Skill: Syntax Highlighting
## Purpose
Skill: Pastebin Core
## Purpose