isomorphic-git

GitHub

A pure JavaScript implementation of git for node and browsers!

RAW Doc

Alphabetic

---
title: All Commands
sidebar_label: Alphabetical Index
---


- abortMerge
- add
- addNote
- addRemote
- annotatedTag
- branch
- checkout
- cherryPick
- clone
- commit
- currentBranch
- deleteBranch
- deleteRef
- deleteRemote
- deleteTag
- expandOid
- expandRef
- fastForward
- fetch
- findMergeBase
- findRoot
- getConfig
- getConfigAll
- getRemoteInfo
- getRemoteInfo2
- hashBlob
- indexPack
- init
- isDescendent
- isIgnored
- listBranches
- listFiles
- listNotes
- listRefs
- listRemotes
- listServerRefs
- listTags
- log
- merge
- packObjects
- pull
- push
- readBlob
- readCommit
- readNote
- readObject
- readTag
- readTree
- remove
- removeNote
- renameBranch
- resetIndex
- resolveRef
- setConfig
- stash
- status
- statusMatrix
- tag
- updateIndex
- version
- walk
- writeBlob
- writeCommit
- writeObject
- writeRef
- writeTag
- writeTree

---

Authentication

---
title: Authentication
sidebar_label: Authentication
---

Authentication is normally required for pushing to a git repository.
It may also be required to clone or fetch from a private repository.
Git does all its authentication using HTTPS Basic Authentication.
Usually this is straightforward: just specify username and password in the URL. In a browser, you can do this:

js
// this will just build a url like https://user:[email protected]/isomorphic-git/isomorphic-git
// it escapes all the non-URL characters for you
const repoUrl = "https://github.com/isomorphic-git/isomorphic-git"
const u = new URL(repoUrl)

// your github username
u.username = login

// can come from github oauth flow, or your real password, if you don't have 2fa enabled
u.password = token

await git.push({
fs,
http,
dir: '/yours',
corsProxy: 'https://cors.isomorphic-git.org',
url: u.toString(),
author: {
name: "you",
email: "you@wherever"
}
})

However, there are some things to watch out for.

If you have two-factor authentication (2FA) enabled on your account, you
probably cannot push or pull using your regular username and password.
Instead, you may have to create a Personal Access Token (or an App Password in Bitbucket lingo) and use that to authenticate.
( Instructions for GitHub
| Instructions for Bitbucket
| Instructions for GitLab
)

There is also an option to use onAuth, which is fired on a failed request, if the status is 401 ("Authentication Required".) From there you can return username/password or other headers, however you like, so it can be great for async authentication (prompt user, load files, etc.)

If you are writing a third-party app that interacts with GitHub/GitLab/Bitbucket, you may be obtaining
OAuth2 tokens from the service via a feature like "Login with GitHub".
Depending on the OAuth2 token's grants, you can use those tokens for pushing and pulling from git repos as well.

Github/Gitlab/Bitbucket uses this oauth/personal-access-token token as the password, in above URL scheme.

---

Cache

---
id: cache
title: The cache parameter
sidebar_label: cache
---

TL;DR see Example.

Background

Some git commands can greatly benefit from a cache.
Reading and parsing git packfiles (the files sent over the wire during clone, fetch, pull and push) can take a "long" time for large git repositories.
(Here "long" is usually measured in milliseconds.)

For example, here is one of the absolute worst performing things you can do:

js
// PLEASE DON'T DO THIS!! This is for demonstration purposes only.
const test = async () => {
console.time('time elapsed')
for (const filepath of await git.listFiles({ fs, dir })) {
console.log(${filepath}: ${await git.status({ fs, dir, filepath })})
}
console.timeEnd('time elapsed')
}

test().catch(err => console.log(err))

Running this code on the isomorphic-git repo on my 2018 Macbook Pro takes over 2 minutes!

It is slow because every time you call git.status it has to re-read and re-parse one or more packfiles in .git/objects/pack.
Each individual status may take relatively little time (10ms to 100ms) but if you have thousands of files that quickly adds up.

Naively doing it in parallel will not help!

js
// PLEASE DON'T DO THIS!! This is for demonstration purposes only.
const test = async () => {
console.time(time elapsed)
const filepaths = await git.listFiles({ fs, dir })
await Promise.all(
filepaths.map(async filepath => {
console.log(${filepath}: ${await git.status({ fs, dir, filepath })})
})
)
console.timeEnd(time elapsed)
}

test().catch(err => console.log(err))

This performs even worse than the first code snippet because now instead of reading and parsing the packfiles thousands of times in a row, you are doing the same workload in parallel!
It quickly consumed all 32 GB of memory on my Macbook and I had to kill it after 4 minutes.

You can write an extremely performant version of the above though using walk.
That's what statusMatrix is.

js
const test = async () => {
console.time(time elapsed)
const matrix = await git.statusMatrix({ fs, dir })
for (const [filepath, head, workdir, stage] of matrix) {
console.log(${filepath}: ${head} ${workdir} ${stage})
}
console.timeEnd(time elapsed)
}

test().catch(err => console.log(err))

This runs in 843ms on my machine.

The cache parameter

As you can see, you can easily write yourself into a performance trap using isomorphic-git commands in isolation.

Unlike canonical git commands however, there is a way for isomorphic-git commands to cache intermediate results
and reuse them between commands.
It used to do this by default, but that results in a memory leak if you never clear the cache.

There is no single best caching strategy:
- For long-running processes, you may want to monitor memory usage and discard the cache when memory usage is above some threshold.
- For memory constrained devices, you may want to not use a cache at all.

Instead of compromising, I've placed a powerful tool in your hands:
1. You pass in an ordinary cache object.
2. isomorphic-git stores data on it by setting Symbol properties.
3. Manipulating the cache directly will void your warranty ⚠️.
4. To clear the cache, remove any references to it so it is garbage collected.

Example

Here's what the first example looks like re-written to use a shared cache parameter:

js
// PLEASE DON'T DO THIS!! This is for demonstration purposes only.
const test = async () => {
console.time('time elapsed')
let cache = {}
for (const filepath of await git.listFiles({ fs, dir, cache })) {
console.log(${filepath}: ${await git.status({ fs, dir, filepath, cache })})
}
console.timeEnd('time elapsed')
}

test().catch(err => console.log(err))

This code runs in under 8 seconds on my machine.
(Compare with over 2 minutes without the cache argument.)
Still nowhere as good as statusMatrix, but not everything you might want to do with isomorphic-git can be described by a walk.

The catch of course, is you have to decide when (if ever) to get rid of that cache.
It is just a JavaScript object, so all you need to do is eliminate any references to it and it will be garbage collected.

js
// 1. Create a cache
let cache = {}
// 2. Do some stuff
// 3. Replace cache with new object so old cache is garbage collected
cache = {}

---

Dir Vs Gitdir

---
title: dir vs gitdir
sidebar_label: dir vs gitdir
---

I looked hard and wide for a good explanation of the "working tree" and the "git directory" and the best I found was this one:

If you have a non-bare git repository, there are two parts to it: the git directory and the working tree:

> - The working tree has your checked out source code, with any changes you might have made.

- The git directory is normally named .git, and is in the top level of your working tree - this contains all the history of your project, configuration settings, pointers to branches, the index (staging area) and so on.

> While this is the default layout of a git repository, you can actually set any directories in the filesystem to be your git directory and working tree. You can change these directories from their defaults either with the --work-tree and --git-dir options to git or by using the GIT_DIR and GIT_WORK_TREE environment variables. Usually, however, you shouldn't need to set these.

> — Mark Longair from Stack Overflow

The isomorphic-git equivalent of --work-tree is the dir argument.

The isomorphic-git equivalent of --git-dir is the gitdir argument.

This is really only important when working with bare repositories. Most of the time setting dir is sufficient, because gitdir defaults to path.join(dir, '.git').

---

Faq

---
title: Frequently Asked Questions
sidebar_label: FAQ
---

FAQ philosophy

Most frequently asked questions will get turned into code.
For instance, "How to get the current branch?" and "How to list all the files in a commit?" used to be two frequently asked questions.
So this FAQ is kind of small

- FAQ philosophy
- Is this based on js-git?
- How does this compare with nodegit?
- How does this compare with...
- Why is there no default export in the ES module?
- How to add all untracked files with git.add?
- How to make a shallow repository unshallow?
- Does it support wire protocol version 2?
- How do I use it with an HTTP proxy?

Is this based on js-git?

_Answer by Will Hilton (@wmhilton):_

No, it is a rewrite from scratch. I basically wrote this library because I though js-git was a great idea but poorly designed for actual use.
This quote from the Q-Git documentation illustrates it best:

JS-Git requires a certain amount of ceremony oweing to its many layers of configurability and code reuse.

``js

var repo = {};

repo.rootPath = fs.join(__dirname, "..", ".git");

require("git-node-fs/mixins/fs-db")(repo, repo.rootPath);

require('js-git/mixins/create-tree')(repo);

require('js-git/mixins/pack-ops')(repo);

require('js-git/mixins/walkers')(repo);

require('js-git/mixins/read-combiner')(repo);

require('js-git/mixins/formats')(repo);

`

That is six different modules being required just to open a repo.
Implementation details, like
read-combiner and pack-ops are exposed.
It is so hyper-modular that the ability to open a repo in a file system is not part of the core of
js-git but requires you to install a second package called git-node-fs.

While js-git is extremely clever, it suffers from being too ahead of its time.
It was written in 2013, before async/await and even before Node streams were very good (I believe streams2 came out right about the time js-git started) and so the codebase uses something called 'continuables' and its own stream implementation called 'min-streams'.
So right off the bat, in order to begin using the code, you have to learn two new alien/outdated concepts.
And obviously this was written in ES5 because ES6 (aka ES2015) had not come out yet.
In the years since, js-git has not changed to keep up with the JavaScript language to take advantage of things like async/await, Promises, and streams.
There was enough interest in it though that multiple projects were spawned to create successors to js-git.
Isomorphic-git just happens to be the most mature successor.

How does this compare with nodegit?

How is isomorphic git different from nodegit?

I understand that nodegit is just a nodeJS binding for libgit, but apart from the implementation, how are these two different, and how do I choose between which to use?

Excluding the isomorphism

_Answer by Dan Allen (@mojavelinux) in the Gitter channel, reposted with permission:_

As a current user of nodegit who is planning to migrate to isogit, I can offer some insight into this question.
First and foremost, isogit is pure JavaScript. this is no small thing. the single biggest obstacle to using the tool I built on nodegit (named Antora) is getting nodegit installed.
Nodegit is highly system dependent and really only works without modification on Windows (since binaries are made available) and Ubuntu after installing some packages.
All other versions of Linux require that you recompile libgit2 from scratch, which takes forever.
So do not ignore how much of a pain it will be for users or even developers if you choose nodegit.
Aside from that, nodegit can only operate on a full clone. isogit already offers many more efficient paths to getting information out of the repository because it only takes what it needs.
Authentication in nodegit is also quite a disaster, imho.
Basically, it's very system dependent and authentication errors can result in segfaults due to overzealous assertions in the libgit2 C code.
Don't get me wrong, nodegit is very powerful. it offers a pretty complete git experience thanks to the fact that it uses libgit2 under the covers.
But it has a lot of warts, from installation to incomplete mapping...and I think the most viable strategy for a Node project is to be using something that is pure JavaScript...hence my personal interest and recommendation for isogit.
I'm counting the days until Antora can offer isogit as the primary git client. (i just haven't gotten around to integrating it yet).
What I like most about nodegit is probably the tree walker...that's a nice way to extract content out of a git branch.
But there's really nothing nodegit can do that isogit can't or won't be able to do...and I think isogit is in a position to be a lot more flexible since it's not coupled to another library (as nodegit is to libgit2).
I also find the isogit project to be much more friendly. @wmhilton is a great development lead.

How does this compare with...

Here's a collection of all the other JavaScript git libraries I can find.
I haven't had time to review them all.

- https://github.com/mariusGundersen/es-git
- https://github.com/SamyPesse/gitkit-js
- https://github.com/MatrixAI/js-virtualgit
- http://gitlet.maryrosecook.com/docs/gitlet.html <-- one of my favorites!

Why is there no default export in the ES module?

I've noticed that ES6 import of the the module requires import * as git from 'isomorphic-git'.

It seems that there's no default export which should just contain all the functions

I'm suggesting to have a default export that gathers all the functions together.

In 0.x.x I withheld adding a default export for the reasons explained below. However in 1.x.x there _is_ a default export - with a caveat!
The CommonJS format does _not_ have a default export. This actually makes the most sense because it means this Just Works (TM):

js
const git = require('isomorphic-git')

If you have a default export _and_ a named export, Rollup spits out a file that has to be consumed like this...

js
const git = require('isomorphic-git').default

which nobody wants.

To benefit from tree-shaking, you still should use named exports. But for convenience there is a default export now! So either of these work:

js
import git from 'isomorphic-git'
// or
import * as git from 'isomorphic-git'

which strays from my usual Pythonic "there should only be one way to do it, and that way should be the best way" attitude... but having a default export also makes using the library _simpler_ because you don't have to think about whether to use a namespace import or a default import. And it looks nicer.

Old Answer preserved for posterity:

_Answer by Will Hilton (@wmhilton):_

Default exports are actually really bad for tree-shaking. If you do import * as git from 'isomorphic-git' and only use git.log, rollup and webpack are smart enough to only bundle git.log.
But if you do
import git from 'isomorphic-git' then they can't do any tree-shaking, because you're importing an Object that could have interdependent functions and side effects.
Plus, if you export a default then the commonjs usage gets weird, because then you have to do
const git = require('git').default
So I've concluded that
default exports are simply a bad pattern, and I don't think anyone should ever use them.

I'll reconsider the matter once Node.js figures out how it is dealing with mixed ES6 imports and CJS requires.
But for now I think having a
default export causes more harm than good - since the only good it does is save typing "* as " as far as I can tell.
But that is a VERY good question and one I spent a long time trying to figure out when I was researching how to design the module, and I remember being very disappointed at first when I discovered that
default exports destroy tree-shaking.

How to add all files based on a glob pattern with git.add?

I want to add multiple files based on a pattern. How can I do this?

_Answer by Will Hilton (@wmhilton):_

TLDR:

js
const globby = require('globby');
// Add all .js files using the pattern "/*.js" - adjust the
// pattern to suit your needs!
const paths = await globby(['/*.js'], { gitignore: true });
for (const filepath of paths) {
await git.add({ fs, dir, filepath });
}

Long answer including a browser solution by @jcubic: #187

How to add all untracked files with git.add?

I want to add all the files in a repository. How can I do this?

_Answer by @mtlewis:_

If you want to add all the files in a repo, you can use the code below. The dir parameter should be set to the repository directory. Patterns in .gitignore will be respected, so ignored files should not be added by this command.

js
await git.add({ fs, dir, filepath: '.' });

How to make a shallow repository unshallow?

Is there an equivalent to git fetch --unshallow?

The fast and dirty solution is just use really big depth, like {depth: 1000000000}.

What I would actually recommend would be the following:
- Start with
{ singleBranch: true, depth: 1 }
- Then fetch with
{ depth: 100, relative: true } which will grab the previous 100 commits
- Then repeat fetching with
{ depth: 100, relative: true } as needed until you have the full history.

This gives you a well-behaved, paginated method for lengthening the git history as needed!

You can tell you have the full history indirectly by a couple of means... probably the easiest would just be git.log and when the array returned stops growing in length.
A slightly more efficient way of telling if you have the full history, would be to grab the oid from the last commit returned by
git.log and use that as the starting point for the next call to git.log with { ref: oid } and keep repeating until git.log only returns one commit. Or you could use the 'progress' event emitter in fetch and if the fetch completed successfully with 0 progress events, I think that would indicate there's no more to fetch.

Does it support wire protocol version 2?

Not yet, but you can go upvote the issue
As soon as GitHub supports the fetch filter feature I'll have a reason to work on it, because that would be extremely useful in browser environments!
But until then, there's no advantage to using the new protocol.

How do I use it with an HTTP proxy?

I want to route HTTP requests through a proxy. How can I do this?

_Answer by Dan Allen (@mojavelinux):_

isomorphic-git only supports a CORS proxy out of the box. However, all HTTP requests are handled by the http plugin. Therefore, you can swap out the http plugin with a wrapper to inject an HTTP or HTTPS agent that routes requests through the proxy.
This technique only works when using isomorphic-git on Node.js.

First, add the following dependencies to your project (or any HTTP agent that supports proxies that you prefer):

* hpagent

Next, create a file named http-plugin.js and populate it with the following code:

js
'use strict'

const { request: delegate } = require('isomorphic-git/http/node')
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent')

async function request ({ url, method, headers, body }) {
const proxy = url.startsWith('https:')
? { Agent: HttpsProxyAgent, url: process.env.https_proxy }
: { Agent: HttpProxyAgent, url: process.env.http_proxy }
const agent = proxy.url ? new proxy.Agent({ proxy: proxy.url }) : undefined
return delegate({ url, method, agent, headers, body })
}

module.exports = { request }

Next, assign this plugin to the http variable instead of isomorphic-git/http/node:

js
const http = require('./http-plugin.js')

Finally, pass this http variable to any command that requires the http keyword, such as clone:

text
await git.clone({ ...repo, url, http })

With this code in place, isomorphic-git will honor the http_proxy and https_proxy environment variables.
Those environment variables specify a URL through which to route HTTP and HTTPS connections, respectively.
The URL may contain a username and password if the proxy requires authentication.

---

Fs

---
title: fs
sidebar_label: fs
---

You need to pass a file system into isomorphic-git functions that do anything that involves files (which is most things in git).

In Node, you can pass the builtin fs module.
In the browser it's more involved because there's no standard 'fs' module.
But you can use any module that implements enough of the
fs API.


Node's fs

If you're only using isomorphic-git in Node, you can just use the native fs module:

js
const git = require('isomorphic-git');
const fs = require('fs');
const files = await git.listFiles({ fs, dir: __dirname });
console.log(files)

LightningFS

If you are writing code for the browser, you will need something that emulates the fs API.
While ZenFS (see next section) has more features, LightningFS might very well fit your needs.
It was designed from scratch for
isomorphic-git (by the same author) to eek out more performance
for fewer bytes. As an added bonus it's dead simple to configure.

html
<script src="https://unpkg.com/@isomorphic-git/lightning-fs"></script>
<script src="https://unpkg.com/isomorphic-git"></script>
<script>
const fs = new LightningFS('my-app')
const files = git.listFiles({ fs, dir: '/' });
console.log(files);
</script>

You can configure LightningFS to load files from an HTTP server as well, which makes it easy to prepopulate a browser file system
with a directory on your server. See the LightningFS documentation for an example of how to do this.

ZenFS

At the time of writing, the most complete option is ZenFS.
It has a few more steps involved to set up than in Node, as seen below:

html
<script type="importmap">
{
"imports": {
"isomorphic-git": "https://esm.sh/isomorphic-git",
"@zenfs/core": "https://esm.sh/@zenfs/core",
"@zenfs/dom": "https://esm.sh/@zenfs/dom"
}
}
</script>
<script type="module">
import { fs, configureSingle } from "@zenfs/core";
import { IndexedDB } from "@zenfs/dom";
import git from "isomorphic-git";

await configureSingle({ backend: IndexedDB });

const files = git.listFiles({ fs, dir: '/' });
console.log(files);

</script>

Besides IndexedDB, ZenFS supports many different backends with different performance characteristics (all backends support sync operations), as well as different features such as proxying a static file server as a read-only file system, mounting ZIP files as file systems, or overlaying a writeable in-memory filesystem on top of a read-only filesystem.
You don't need to know all these features, but familiarizing yourself with the different options may be necessary if you hit a storage limit or performance bottleneck in the IndexedDB backend I suggested above.

An advanced example usage is in the old unit tests for isomorphic-git.
It uses the
Fetch backend to mount (read-only) the test fixtures directory which is stored on the server, then adds a read-write InMemory layer using the Overlay backend so that the tests can modify files locally.
In between tests it empties the
InMemory, restoring the file system to a pristine state.
The current unit tests use LightningFS instead, which was built with this HTTP-backed overlay behavior by default, because I find it so useful.


Environments without IndexedDB (e.g. Cloudflare Workers, Deno Deploy)

Cloudflare Workers, Deno Deploy, and other edge runtimes do not expose IndexedDB.
This means that
LightningFS throws a ReferenceError: indexedDB is not defined at
startup, and ZenFS's
IndexedDB backend won't work either.

The good news is that isomorphic-git itself has no dependency on IndexedDB — it
just needs any object implementing the
fs.promises interface.
The fix lives entirely in the filesystem layer.

LightningFS is designed to be storage-agnostic. Its DefaultBackend uses IndexedDB
under the hood, but you can swap it out via the
backend option with any object that
implements five low-level methods:
saveSuperblock, loadSuperblock, readFile(inode),
writeFile(inode, data), and unlink(inode).

A MemoryBackend backed by a plain Map requires no platform APIs and works everywhere:

js
// MemoryBackend.js
class MemoryBackend {
constructor() {
this._map = new Map()
}
saveSuperblock(superblock) {
this._map.set('!root', superblock)
}
loadSuperblock() {
return this._map.get('!root') || null
}
readFile(inode) {
return this._map.get(inode) || null
}
writeFile(inode, data) {
this._map.set(inode, data)
}
unlink(inode) {
this._map.delete(inode)
}
async wipe() {
this._map.clear()
}
}

Pass it to LightningFS via the backend option:

js
import LightningFS from '@isomorphic-git/lightning-fs'
import git from 'isomorphic-git'
import http from 'isomorphic-git/http/web'

import { MemoryBackend } from '@isomorphic-git/lightning-fs' // once the PR is merged
// or paste the class above locally in the meantime

const fs = new LightningFS('mem', { backend: new MemoryBackend() })

await git.clone({
fs,
http,
dir: '/',
url: 'https://github.com/example/repo',
singleBranch: true,
depth: 1,
})

Note: Data is ephemeral — it lives in the JS heap and is lost when the runtime

terminates. For persistence across Cloudflare Worker requests, see Option 3 in the Cloudflare Workers guide to replace MemoryBackend

with a backend backed by Durable Object storage

using the same five-method interface.

Option 2 — ZenFS with the InMemory backend

ZenFS provides an InMemory backend that also avoids
IndexedDB and implements the full
fs.promises API:

js
import { fs, configureSingle } from '@zenfs/core'
import { InMemory } from '@zenfs/core'
import git from 'isomorphic-git'
import http from 'isomorphic-git/http/web'

await configureSingle({ backend: InMemory })

await git.clone({
fs,
http,
dir: '/',
url: 'https://github.com/example/repo',
singleBranch: true,
depth: 1,
})

Compatibility notes

| Runtime | LightningFS (default) | LightningFS + MemoryBackend | ZenFS InMemory |
|---|---|---|---|
| Node.js | ❌ (no IndexedDB; use native
fs) | ✅ | ✅ |
| Browser | ✅ | ✅ | ✅ |
| Cloudflare Workers | ❌ (no IndexedDB) | ✅ | ✅ |
| Deno Deploy | ❌ (no IndexedDB) | ✅ | ✅ |
| Bun | ✅ | ✅ | ✅ |

---

Implementing your own fs

There are actually TWO possible interfaces for an fs object: the classic "callback" API and the newer "promise" API. If your fs object provides an enumerable promises property, isomorphic-git will use the "promise" API _exclusively_.

Using the "callback" API

A "callback" fs object must implement the following subset of node's fs module:

- [fs.readFile(path[, options], callback)](https://nodejs.org/api/fs.html#fs_fs_readfile_path_options_callback)
- [fs.writeFile(file, data[, options], callback)](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback)
- fs.unlink(path, callback)
- [fs.readdir(path[, options], callback)](https://nodejs.org/api/fs.html#fs_fs_readdir_path_options_callback)
- [fs.mkdir(path[, mode], callback)](https://nodejs.org/api/fs.html#fs_fs_mkdir_path_mode_callback)
- fs.rmdir(path, callback)
- [fs.stat(path[, options], callback)](https://nodejs.org/api/fs.html#fs_fs_stat_path_options_callback)
- [fs.lstat(path[, options], callback)](https://nodejs.org/api/fs.html#fs_fs_lstat_path_options_callback)
- [fs.readlink(path[, options], callback)](https://nodejs.org/api/fs.html#fs_fs_readlink_path_options_callback) (optional ¹)
- [fs.symlink(target, path[, type], callback)](https://nodejs.org/api/fs.html#fs_fs_symlink_target_path_type_callback) (optional ¹)
- fs.chmod(path, mode, callback) (optional ²)
- [fs.rm(path[, options], callback)](https://nodejs.org/api/fs.html#fs_fs_rm_path_options_callback) (optional ³)

Internally, isomorphic-git wraps the provided "callback" API functions using pify.

As of node v12 the fs.promises API has been stabilized. (lightning-fs also provides a fs.promises API!) Nowadays, wrapping the callback functions
with
pify is redundant and potentially less performant than using the native promisified versions. Plus, if you're writing your own fs implementation,
the
fs.promises API lets you write straightforward implementations using async / await without the messy optional argument handling the callback API needs.
Therefore a second API is now supported...

Using the "promise" API (preferred)

A "promise" fs object must implement the same set functions as a "callback" implementation, but it implements the promisified versions, and they should all be on a property called promises:

- [fs.promises.readFile(path[, options])](https://nodejs.org/api/fs.html#fs_fspromises_readfile_path_options)
- [fs.promises.writeFile(file, data[, options])](https://nodejs.org/api/fs.html#fs_fspromises_writefile_file_data_options)
- fs.promises.unlink(path)
- [fs.promises.readdir(path[, options])](https://nodejs.org/api/fs.html#fs_fspromises_readdir_path_options)
- [fs.promises.mkdir(path[, mode])](https://nodejs.org/api/fs.html#fs_fspromises_mkdir_path_options)
- fs.promises.rmdir(path)
- [fs.promises.stat(path[, options])](https://nodejs.org/api/fs.html#fs_fspromises_stat_path_options)
- [fs.promises.lstat(path[, options])](https://nodejs.org/api/fs.html#fs_fspromises_lstat_path_options)
- [fs.promises.readlink(path[, options])](https://nodejs.org/api/fs.html#fs_fspromises_readlink_path_options) (optional ¹)
- [fs.promises.symlink(target, path[, type])](https://nodejs.org/api/fs.html#fs_fspromises_symlink_target_path_type) (optional ¹)
- fs.promises.chmod(path, mode) (optional ²)
- [fs.promises.rm(path[, options])](https://nodejs.org/api/fs.html#fs_fspromises_rm_path_options) (optional ³)

---

<a id="footnote-1">¹</a> readlink and symlink are only needed to work with git repos that contain symlinks.

<a id="footnote-2">²</a> Right now, isomorphic-git rewrites the file if it needs to change its mode. In the future, if chmod is available it will use that.

<a id="footnote-3">³</a> Only called with recursive: true option. A fallback implementation is provided if not implemented.

---

Guide Cli

---
id: cli
title: isogit CLI
sidebar_label: isogit CLI
---

Isomorphic-git comes with a simple CLI tool, named "isogit" because "isomorphic-git" is a lot to type.
It is really just a thin shell that translates command line arguments into the equivalent JS API commands,
so you should be able to run any current or future isomorphic-git commands using the CLI.

It always assumes two of the arguments:
-
fs is node's native fs module
-
dir is the current working directory

The first argument is the name of the command and then command line option flags to generate the argument object.

Example:

sh
isogit clone --url=https://github.com/isomorphic-git/isomorphic-git --depth=1 --singleBranch

will run

js
git.clone({
fs: require('fs'),
dir: process.cwd(),
url: 'https://github.com/isomorphic-git/isomorphic-git',
depth: 1,
singleBranch: true
})

For commands like git.log which return JSON, it pretty-prints the output.

---

Guide Cloudflare Workers

---
id: guide-cloudflare-workers
title: Using isomorphic-git in Cloudflare Workers
sidebar_label: Cloudflare Workers Guide
---

Cloudflare Workers is a popular edge computing platform that runs JavaScript in a V8
isolate environment. It does not support IndexedDB — which is the storage backend
used by
@isomorphic-git/lightning-fs by default.

This guide explains how to use isomorphic-git inside a Cloudflare Worker using
LightningFS's pluggable backend system.

---

Why does LightningFS need a custom backend?

LightningFS separates the filesystem logic (directory trees, stat objects, path
resolution) from the storage layer. The storage layer is a plain object that
implements five methods:

| Method | Purpose |
|---|---|
|
saveSuperblock(superblock) | Persist the serialised directory tree |
|
loadSuperblock() | Load the directory tree on startup |
|
readFile(inode) | Read raw file bytes by inode key |
|
writeFile(inode, data) | Write raw file bytes by inode key |
|
unlink(inode) | Delete a file by inode key |

The default storage layer (IdbBackend) uses IndexedDB. Swap it out and you can
run LightningFS — and therefore
isomorphic-git — anywhere.

---

Option 1 — Ephemeral in-memory (MemoryBackend)

For workflows that only need to run Git operations within a single request (e.g.
clone a repo, read a file, return a response), an in-memory backend is ideal:

js
// MemoryBackend.js
export class MemoryBackend {
constructor() {
this._map = new Map()
}
saveSuperblock(superblock) { this._map.set('!root', superblock) }
loadSuperblock() { return this._map.get('!root') || null }
readFile(inode) { return this._map.get(inode) || null }
writeFile(inode, data) { this._map.set(inode, data) }
unlink(inode) { this._map.delete(inode) }
async wipe() { this._map.clear() }
}

Note: MemoryBackend is tracked for inclusion in @isomorphic-git/lightning-fs

as a first-class export. Until then, paste the class above into your project.

Use it with LightningFS and isomorphic-git:

js
// worker.js
import LightningFS from '@isomorphic-git/lightning-fs'
import git from 'isomorphic-git'
import http from 'isomorphic-git/http/web'
import { MemoryBackend } from './MemoryBackend.js'

export default {
async fetch(request, env) {
// Each request gets a fresh in-memory filesystem
const fs = new LightningFS('mem', { backend: new MemoryBackend() })

await git.clone({
fs,
http,
dir: '/',
url: 'https://github.com/isomorphic-git/isomorphic-git',
singleBranch: true,
depth: 1,
})

const commits = await git.log({ fs, dir: '/', depth: 5 })
return new Response(JSON.stringify(commits, null, 2), {
headers: { 'content-type': 'application/json' },
})
},
}

This is perfect for:
- Cloning a repo to read a config file at the edge
- Rendering markdown from a git tree
- Running
git log / git diff in response to a webhook
- Building a static site at the edge on every deploy

---

Option 2 — ZenFS InMemory backend

ZenFS is a separate library that also provides an
InMemory backend and implements the full fs.promises API without going through
LightningFS:

toml

wrangler.toml


compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]

js
import { fs, configureSingle } from '@zenfs/core'
import { InMemory } from '@zenfs/core'
import git from 'isomorphic-git'
import http from 'isomorphic-git/http/web'

export default {
async fetch(request) {
await configureSingle({ backend: InMemory })

await git.clone({ fs, http, dir: '/', url: 'https://github.com/example/repo', singleBranch: true, depth: 1 })

return new Response('ok')
},
}

Warning: configureSingle sets a global filesystem. If requests run concurrently

in the same isolate, they share state. Prefer MemoryBackend with LightningFS if you

need per-request isolation.

---

<a id="option-3"></a>

Option 3 — Persistent storage (Durable Objects backend)

If the repository must survive across requests, replace MemoryBackend with a backend
that persists to Durable Object storage.
The five-method interface is the same — only the storage medium changes:

js
// DurableBackend.js
export class DurableBackend {
constructor(storage) {
//
storage is this.ctx.storage from inside a Durable Object
this._storage = storage
}
async saveSuperblock(superblock) { await this._storage.put('!root', superblock) }
async loadSuperblock() { return (await this._storage.get('!root')) || null }
async readFile(inode) { return (await this._storage.get(String(inode))) || null }
async writeFile(inode, data) { await this._storage.put(String(inode), data) }
async unlink(inode) { await this._storage.delete(String(inode)) }
async wipe() { await this._storage.deleteAll() }
}

Use it inside a Durable Object class:

js
// GitRepository.js — the Durable Object
import LightningFS from '@isomorphic-git/lightning-fs'
import git from 'isomorphic-git'
import http from 'isomorphic-git/http/web'
import { DurableBackend } from './DurableBackend.js'

export class GitRepository {
constructor(state, env) {
this.ctx = state
}

async fetch(request) {
const fs = new LightningFS('repo', { backend: new DurableBackend(this.ctx.storage) })
const url = new URL(request.url)

if (url.pathname === '/init') {
await git.init({ fs, dir: '/' })
return new Response('initialized')
}
if (url.pathname === '/log') {
const log = await git.log({ fs, dir: '/', depth: 10 })
return new Response(JSON.stringify(log), { headers: { 'content-type': 'application/json' } })
}

return new Response('not found', { status: 404 })
}
}

// worker.js
export default {
async fetch(request, env) {
const id = env.GIT_REPO.idFromName('my-repo')
return env.GIT_REPO.get(id).fetch(request)
},
}

toml

wrangler.toml


name = "git-worker"
main = "worker.js"
compatibility_date = "2024-01-01"

[[durable_objects.bindings]]
name = "GIT_REPO"
class_name = "GitRepository"

[[migrations]]
tag = "v1"
new_sqlite_classes = ["GitRepository"]

Storage limits: For legacy KV-backed Durable Objects, values have a 128 KiB limit per key.

SQLite-backed Durable Objects (recommended for all new namespaces) allow up to 2 MB per combined key/value pair

and 10 GB total per Durable Object. For repositories with large binary blobs exceeding these limits, store file data

in R2 and keep only the superblock + inode metadata in Durable Object storage.

---

Troubleshooting

ReferenceError: indexedDB is not defined


You are using the default
LightningFS without a custom backend. Pass
{ backend: new MemoryBackend() } as shown above.

ReferenceError: Buffer is not defined


Add
nodejs_compat to wrangler.toml:
toml
compatibility_flags = ["nodejs_compat"]

TypeError: Failed to fetch on clone


Remove
corsProxy — Cloudflare Workers can make cross-origin fetch requests natively.

Cloning a private repo


Pass credentials via
onAuth:
js
await git.clone({
fs, http, dir: '/',
url: 'https://github.com/org/private-repo',
onAuth: () => ({ username: 'token', password: env.GITHUB_TOKEN }),
})

---

Summary

| Goal | Recommended approach |
|---|---|
| Read-only, single request | LightningFS +
MemoryBackend |
| Read-write, single request | LightningFS +
MemoryBackend |
| Persistent across requests | LightningFS +
DurableBackend |

Further reading:
- fs documentation — the
fs.promises interface and custom backend docs
- Cloudflare Durable Objects
- LightningFS custom backends

---

Guide Quickstart

---
id: quickstart
title: Quick Start
sidebar_label: Quick Start
---

Here's a whirlwind tour of the main features of isomorphic-git.

First, let's set up LightningFS and isomorphic-git. Note: I've already done this for you, which is why there is no RUN button for this code block.

html
<script src="https://unpkg.com/@isomorphic-git/lightning-fs"></script>
<script src="https://unpkg.com/isomorphic-git"></script>
<script type="module">
import http from 'https://unpkg.com/isomorphic-git/http/web/index.js'
// Initialize isomorphic-git with a file system
window.fs = new LightningFS('fs')
// I prefer using the Promisified version honestly
window.pfs = window.fs.promises
</script>

Picking a directory


Now let's pick a directory to work in.

`js live
window.dir = '/tutorial'
console.log(dir);
await pfs.mkdir(dir);
// Behold - it is empty!
await pfs.readdir(dir);

text
Now that we've got an empty directory, let's clone a git repository.
I'm cloning
isomorphic-git itself (how meta!).
I'm only cloning a single branch and only to a depth of 10 commits to save time, bandwidth, and browser storage space.
Since GitHub hasn't added CORS headers to the git clone endpoint yet, we have to use a proxy server.
(They never suspected that a browser would want to run "git clone"!)
js live
await git.clone({
fs,
http,
dir,
corsProxy: 'https://cors.isomorphic-git.org',
url: 'https://github.com/isomorphic-git/isomorphic-git',
ref: 'main',
singleBranch: true,
depth: 10
});

// Now it should not be empty...
await pfs.readdir(dir);

text
Great! We've got files. We've also got commits.
Let's see what the recent history of this branch looks like.
Hint: be sure to expand the objects so you can see all the properties.
js live
await git.log({fs, dir})
text
Git is used to track files. Let's see what kind of file things we can do!

git.status is a major one. That let's us compare the working directory file to the current branch.

js live
await git.status({fs, dir, filepath: 'README.md'})
text
OK so the status is "unmodified" because we haven't modified it.
What if we change the file by writing over it?
js live
await pfs.writeFile(
${dir}/README.md, 'Very short README', 'utf8')
await git.status({fs, dir, filepath: 'README.md'})
text
The status is "\*modified" with a star.
Text editors sometimes use a "\*" in the title bar to indicate a file has unsaved changes.
That's what is going on here - we've made changes to the file but we haven't added those changes to the git "staging area".
js live
await git.add({fs, dir, filepath: 'README.md'})
await git.status({fs, dir, filepath: 'README.md'})
text
Now that we've done "git add" that little star has gone away and the status is just "modified".

What if we write a new file?

js live
await pfs.writeFile(
${dir}/newfile.txt, 'Hello World', 'utf8')
await git.status({fs, dir, filepath: 'newfile.txt'})
text
"\*added" means the file has been added, but not staged. Simple to fix:
js live
await git.add({fs, dir, filepath: 'newfile.txt'})
await git.status({fs, dir, filepath: 'newfile.txt'})
text
The third and final trick: deleting a file:
js live
await pfs.unlink(
${dir}/package.json)
await git.status({fs, dir, filepath: 'package.json'})
text
This last bit has always been unintuitive to me... but you need to tell git you deleted the file.
js live
await git.remove({fs, dir, filepath: 'package.json'})
await git.status({fs, dir, filepath: 'package.json'})
text
What happens if you tell git you deleted a file but you really didn't?
js live
await git.remove({fs, dir, filepath: 'package-lock.json'})
await git.status({fs, dir, filepath: 'package-lock.json'})
text
Does that make sense? No? Sorry, naming things is hard. (Git doesn't do a great job of it either.
It reports the file as "untracked" and "deleted" at the same time.) OK, enough messing around.
js live
await git.add({fs, dir, filepath: 'package-lock.json'})
await git.status({fs, dir, filepath: 'package-lock.json'})
text
Cool. So we've deleted package.json and replaced the README with the text "Very short README".
A solid day's work - let's commit those changes.
js live
let sha = await git.commit({
fs,
dir,
message: 'Delete package.json and overwrite README.',
author: {
name: 'Mr. Test',
email: '[email protected]'
}
})

console.log(sha)

text
git.commit returns the shasum of our new commit. Let's examine our handiwork:
js live
let commits = await git.log({fs, dir, depth: 1})
console.log(commits[0])
text
Congrats! This just scratches the surface of what you can do with isomorphic-git.
There are a lot more functions. You can see them all in the Alphabetical Index.

---

<details>
<summary><i>Tip: If you need a clean slate, expand and run this snippet to clean up the file system.</i></summary>

js live
window.fs = new LightningFS('fs', { wipe: true })
window.pfs = window.fs.promises
console.log('done')
text
</details>

---

Guide Quickstart With Bundlers

---
id: quickstart-with-bundlers
title: Quick Start (With bundlers)
sidebar_label: Quick Start (With bundlers)
---

Run the following command to add Isomorphic-git to your project:

bash
npm install @isomorphic-git/lightning-fs isomorphic-git buffer
text
Here's a whirlwind tour of the main features of isomorphic-git.

First, let's set up LightningFS and isomorphic-git. Note: I've already done this for you, which is why there is no RUN button for this code block.

js live
import LightningFS from '@isomorphic-git/lightning-fs';
import http from 'isomorphic-git/http/web';
import git from 'isomorphic-git';
import { Buffer } from 'buffer'

// Bundlers require Buffer to be defined on window
window.Buffer = Buffer;
// Initialize isomorphic-git with a file system
window.fs = new LightningFS('fs')
// I prefer using the Promisified version honestly
window.pfs = window.fs.promises

text
Now you can continue now by picking a directory.

---

Guide Webworker

---
id: webworker
title: WebWorker Example
sidebar_label: WebWorker Example
---

While isomorphic-git tries not to block the main thread, it still does on occasion.
This can cause your webapp to stutter or even freeze up briefly!
To achieve buttery smooth performance, you'll eventually want to move all your
isomorphic-git usage off of the main thread.
Actually, you should move all your logic that's not directly responsible for updating the DOM off the main thread.
That's still a real challenge in 2020, but more and more libraries are appearing to help solve this.

Introduction

WebWorkers live in a separate operating system thread from the main JS thread and communicate using the worker.postMessage() method.
Code in the worker thread does not have access to heap objects in the main thread.
So all objects sent via postMessage need to be serialized before being sent.
Technically, this is done using the structured clone algorithm.

What kinds of Objects can be sent using postMessage? Functions cannot. Therefore objects with methods cannot.
JSON objects can.
Date objects and RegExp objects can. Also Uint8Array objects and Map objects and Set objects.
So basically the types of objects you can send to a worker are a superset of JSON but a subset of full JavaScript objects.

If you don't already have a WebWorker RPC solution, then I recommend using MagicPortal (because I wrote it) or Comlink which is a similar library.
The example below will use MagicPortal.

Example

Here is a complete example that runs git in a WebWorker.
The worker wraps some git functions and exposes them to the main thread, while the main thread exposes some functions to the worker for use in callbacks like
onProgress, onMessage, and onAuth.

<iframe
src="https://codesandbox.io/embed/magic-portal-with-isomorphic-git-ejdoo?fontsize=14&hidenavigation=1&module=%2Fworker.js&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="[email protected] in a Worker example"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

---

Headers

---
title: headers
sidebar_label: headers
---

Authorization header

Plain old HTTP Basic auth can be handled elegantly using the onAuth handler.
But if you want to use Bearer auth or something, any value you manually set for the
Authorization header will override the derived value.

User-Agent header

Regretably, some git hosting services have User-Agent specific behavior.
For instance, GitHub will correctly interpret git HTTP requests made to a repository URL that is missing the
.git suffix but _ONLY_ if the User-Agent starts with git/.
And in fact, does not interpret git HTTP requests for _gists_ correctly _at all_ unless the User-Agent start with
git/ (bug #259).

Since 2015 the specs state that setting a custom User-Agent header in fetch should override the default. This works in Firefox (bug #247), but Chrome has a bug so setting a custom User-Agent doesn't work at all (chrome bug #571722).

The @isomorphic-git/cors-proxy solves some of this problem by checking if the User-Agent starts with git/ and if it doesn't, it sets the User-Agent to git/@isomorphic-git/cors-proxy. So cloning gists using a proxy works.

CORS also has a strange relationship with the User-Agent header. Setting a custom User-Agent header requires that 'User-Agent' be explicitly whitelisted in the CORS pre-flight request (bug #555).

As you can see, User-Agent is basically a mine field. Which is why as of version 1.0 this library doesn't touch it. There is no solution that works for everything (GitHub handling URLs without .git, cloning gists, setting it in Chrome, setting it in a proxy, CORS). This is your problem now, not mine. Go bug GitHub, Inc to stop using user-agent filtering.

X- headers

There is nothing stopping you from setting custom headers if you really want. But if you're doing it in a browser you'll either need to run the CORS proxy on the same domain or
run a custom CORS proxy to whitelist those headers if they aren't already whitelisted.

---

Http

---
title: http
sidebar_label: http
---

You need to pass an HTTP client into isomorphic-git functions that make HTTP requests.
Both a node client (
isomorphic-git/http/node) and a browser client (isomorphic-git/http/web) are included in the npm package, but you have to pick which one to use.
Or you can provide your own!

(In the past, we tried to be clever and automatically select the client for you. But that can be really hard to determine in edge cases like Electron.)

Node Client

The Node client uses the simple-get package under the hood.

js
const git = require("isomorphic-git");
const http = require("isomorphic-git/http/node");
git.getRemoteInfo({ http, url: 'https://github.com/isomorphic-git/isomorphic-git' })
.then(console.log)
text
If you need features that aren't supported currently, like detecting and handling HTTP_PROXY environment variables, you can
wrap this client or implement your own HTTP client. (See section below.)

Browser Client:

The Browser client uses the Fetch API under the hood.

js
import git from "isomorphic-git";
import http from "isomorphic-git/http/web";
git.getRemoteInfo({ http, url: 'https://github.com/isomorphic-git/isomorphic-git' })
.then(console.log)
text
If you are using ES modules directly, you can import it like this:
js
import http from 'https://unpkg.com/isomorphic-git/http/web/index.js'
text
If you need to use a script tag (such as in a WebWorker), then use the UMD build. But note that the global var is called GitHttp not http because I was worried that would be too generic:
html
<script src="https://unpkg.com/isomorphic-git/http/web/index.umd.js">
<script>
git.getRemoteInfo({ http: GitHttp, url: 'https://github.com/isomorphic-git/isomorphic-git' })
.then(console.log)
text

Implementing your own http client

An http client is an object with a single request method that implements the following API:

#### GitHttpPlugin

js
const http = {
async request ({
url,
method,
agent,
headers,
body,
onProgress
}) {
...
// Do stuff
...
return {
url,
method,
headers,
body,
statusCode,
statusMessage
}
}
}
text
##### Parameters

| param | type [= default] | description |
| ----------- | ----------------------------------- | ----------------------------------------------------------------------- |
| url | string | The URL to request |
| method | string = 'GET' | The HTTP method to use |
| agent | object (optional) | An HTTP/HTTPS agent that manages HTTP client connections (Node.js only) |
| headers | object = {} | Headers to include in the HTTP request |
| body | AsyncIterableIterator\<Uint8Array\> | An async iterator of Uint8Arrays that make up the body of POST requests |
| onProgress | function (optional) | Reserved for future use (emitting
GitProgressEvents) |
| signal | AbortSignal (optional) | Reserved for future use (canceling a request) |

##### Return values

| param | type [= default] | description |
| ----------------- | ----------------------------------- | ---------------------------------------------------------------------- |
| url | string | The final URL that was fetched after any redirects |
| method | string | The HTTP method that was used |
| headers | object | HTTP response headers |
| body | AsyncIterableIterator\<Uint8Array\> | An async iterator of Uint8Arrays that make up the body of the response |
| statusCode | number | The HTTP status code |
| statusMessage | string | The HTTP status message |

Both requests and responses are "streaming" in the sense that they are async iterables.
You don't _have_ to support streaming (and in some cases, like uploads in the browser, it may not be possible yet) but it is nice to have.
If you are not streaming responses, you can simply fake it by returning an array with a single
Uint8Array inside it.
This works because the async iteration protocol (
for await ... of) will fallback to the sync iteration protocol, which is supported by plain Arrays.

To get started, you might want to look at src/http/node/index.js
and
src/http/web/index.js.

---

In The News

---
title: In The News
---

2020-02-27 | heise online (German)

2020-02-26 | Hacker News

2018-06-01 | Open Source Awards winner of "The most exciting use of technology" category!

2018-05-26 | MoonGift (Japanese)

2018-05-25 | blog (Chinese)

2018-05-20 | blog (Russian)

2018-05-17 | Open News (Russian)

2018-05-17 | Hacker News

2018-05-15 | ES Next News

---

MergeDriver

---
title: mergeDriver
sidebar_label: mergeDriver
---
The merge driver is a callback which is called for each conflicting file during a merge. It takes the file contents on each branch as an array and returns the merged result.

By default the merge command uses the diff3 algorithm to try to solve merge conflicts, and throws an error if the conflict cannot be resolved. This is not always ideal, so isomorphic-git implements merge drivers so that users may implement their own merging algorithm.

A merge driver implements the following API:

#### async ({ branches, contents, path }) => { cleanMerge, mergedText }
| param | type [= default] | description |
| ------------- | ------------------------------------------------- | --------------------------------------------------------- |
| branches | Array\<string\> | an array of human readable branch names |
| contents | Array\<string\> | an array of the file's contents on each respective branch |
| path | string | the file's path relative to the git repository |
| return | Promise\<{cleanMerge: bool, mergedText: string}\> | Whether merge was successful, and the merged text |


If
cleanMerge is true, then the mergedText string will be written to the file. If cleanMerge is false, a MergeConflictError will be thrown and no merge commit will be created.

If merge was called with abortOnConflict: false, the mergedText string will be written to the file even if there is a merge conflict. Otherwise, in the event of a merge conflict, no changes will be written to the worktree or index.

MergeDriverParams#path


The
path parameter refers to the path of the conflicted file, relative to the root of the git repository.

MergeDriverParams#branches


The
branches array contains the human-readable names of the branches we are merging. The first index refers to the merge base, the second refers to the branch being merged into, and any subsequent indexes refer to the branches we are merging. For example, say we have a git history that looks like this:

A topic
/
D---E main
text
If we were to merge topic into main, the branches array would look like: ['base', 'main', 'topic']. In this case, the name base refers to commit D which is the common ancestor of our two branches. base will always be the name at the first index.

MergeDriverParams#contents


The
contents array contains the file contents respective of each branch. Like the branches array, the first index always refers to the merge base. The second index always refers to the branch we are merging into, i.e. 'ours'. Subsequent indexes refer to the branches we are merging, i.e. 'theirs'.

For example, say we have a file text.txt which contains:


original
text
file
text
On the main branch, we modify the text file to read:

text
file
was
modified
text
However, on the topic branch, we modify the text file to read:

modified
text
file
text
In this case, when our merge driver is called on text.txt, the contents array will look like this:
js
[
'original\ntext\nfile',
'text\n\file\nwas\nmodified',
'modified\ntext\nfile',
]
text

Examples


Below is an example of a very simple merge driver which always chooses the other branch's version of the file whenever it was modified by both branches.

const mergeDriver = ({ contents }) => {
const mergedText = contents[2]
return { cleanMerge: true, mergedText }
}
text
If we applied this algorithm to the conflict in the previous example, the resolved file would simply read:

modified
text
file
text
and if instead we wanted to chose our branch's version of the file, whenever it was modified by both branches,we simply change the line:

const mergedText = contents[2]
text
to read:

const mergedText = contents[1]
text
which results in the resolved file reading:

text
file
was
modified
text
As a more complex example, we use the default diff3 algorithm, but choose the other branch's changes whenever specific lines of the file conflict.

const diff3Merge = require('diff3')
const mergeDriver = ({ contents }) => {
const baseContent = contents[0]
const ourContent = contents[1]
const theirContent = contents[2]

const LINEBREAKS = /^.*(\r?\n|$)/gm
const ours = ourContent.match(LINEBREAKS)
const base = baseContent.match(LINEBREAKS)
const theirs = theirContent.match(LINEBREAKS)
const result = diff3Merge(ours, base, theirs)
let mergedText = ''
for (const item of result) {
if (item.ok) {
mergedText += item.ok.join('')
}
if (item.conflict) {
mergedText += item.conflict.b.join('')
}
}
return { cleanMerge: true, mergedText }
}

text
If we apply this algorithm to the conflict in the previous example, the resolved file reads:

modified
text
file
was
modified
text
and if we wanted to choose our branch's changes whenever specific lines of the file conflict, we simply change the above line:

mergedText += item.conflict.b.join('')
text
to read:

mergedText += item.conflict.a.join('')
text
which results in a resolved file that reads:

text
file
was
modified
text
Finally, what if we wanted to make a slight modification to the behavior of the default merge driver, like changing the size of conflict markers? The code for the default merge driver is located in src/utils/mergeFile.js. We can copy the code into our merge driver like so:

const diff3Merge = require('diff3')
const mergeDriver = ({ contents, branches }) => {
const ourName = branches[1]
const theirName = branches[2]

const baseContent = contents[0]
const ourContent = contents[1]
const theirContent = contents[2]

const ours = ourContent.match(LINEBREAKS)
const base = baseContent.match(LINEBREAKS)
const theirs = theirContent.match(LINEBREAKS)

const result = diff3Merge(ours, base, theirs)

const markerSize = 7

let mergedText = ''
let cleanMerge = true

for (const item of result) {
if (item.ok) {
mergedText += item.ok.join('')
}
if (item.conflict) {
cleanMerge = false
mergedText +=
${'<'.repeat(markerSize)} ${ourName}\n
mergedText += item.conflict.a.join('')

mergedText += ${'='.repeat(markerSize)}\n
mergedText += item.conflict.b.join('')
mergedText +=
${'>'.repeat(markerSize)} ${theirName}\n
}
}
return { cleanMerge, mergedText }
}

text
If we want larger conflict markers, we can simply change the line

const markerSize = 7
text
to

const markerSize = 14
text
Which will give us conflict markers that are 14 characters wide instead of the default 7.

Now if we use this merge driver when merging the branch 'topic' into 'main', and if we have abortOnConflict set to false, the worktree will be updated with a text.txt file that looks like this:


<<<<<<<<<<<<<< main
modified
==============
>>>>>>>>>>>>>> topic
text
file
was
modified
text
---

OnAuth

---
title: onAuth
sidebar_label: onAuth
---

The onAuth callback allows isomorphic-git to request credentials.
It is only called if a server returns an HTTP error (such as 404 or 401) when attempting to access the resource without credentials.

Authentication is normally required for pushing to a git repository.
It may also be required to clone or fetch from a private repository.
Git does all its authentication using HTTPS Basic Authentication.

An onAuth function is called with a url and an auth object and should return a GitAuth object:

ts
/
* @callback AuthCallback
* @param {string} url
* @param {GitAuth} auth - Might have some values if the URL itself originally contained a username or password.
* @returns {GitAuth | void | Promise<GitAuth | void>}
*/

/
* @typedef {Object} GitAuth
* @property {string} [username]
* @property {string} [password]
* @property {Object<string, string>} [headers]
* @property {boolean} cancel - Tells git to throw a
UserCanceledError (instead of an HTTPError).
*/

text

Example

js
await git.clone({
...,
onAuth: url => {
let auth = lookupSavedPassword(url)
if (auth) return auth

if (confirm('This repo is password protected. Ready to enter a username & password?')) {
auth = {
username: prompt('Enter username'),
password: prompt('Enter password'),
}
return auth
} else {
return { cancel: true }
}
}
})

text

Option 1: Username & Password

Return an object with { username, password }.

However, there are some things to watch out for.

If you have two-factor authentication (2FA) enabled on your account, you
probably cannot push or pull using your regular username and password.
Instead, you may have to use a Personal Access Token. (Bitbucket calls them "App Passwords".)

Personal Access Tokens

- Instructions for GitHub
- Instructions for Bitbucket
- Instructions for GitLab

In this situation, you want to return an object with { username, password } where password is the Personal Access Token.
Note that GitHub actually lets you specify the token as the
username and leave the password blank, which is convenient but none of the other hosting providers do this that I'm aware of.

OAuth2 Tokens

If you are writing a third-party app that interacts with GitHub/GitLab/Bitbucket, you may be obtaining
OAuth2 tokens from the service via a feature like "Login with GitHub".
Depending on the OAuth2 token's grants, you can use those tokens for pushing and pulling from git repos as well.

In this situation, you want to return an object with { username, password } where username and password depend on where the repo is hosted.

Unfortunately, all the major git hosting companies have chosen different conventions for converting OAuth2 tokens into Basic Authentication headers!

| | username | password |
| ---------- | ---------------- | --------------- |
| GitHub |
token | 'x-oauth-basic' |
| GitHub App | 'x-access-token' |
token |
| BitBucket | 'x-token-auth' |
token |
| GitLab | 'oauth2' |
token |

I will gladly accept pull requests to document more companies' conventions.

Since it is a rarely used feature, I'm not including the conversion table directly in isomorphic-git anymore.
But if there's interest in maintaining this table as some kind of function, I'm considering starting an
@isomorphic-git/quirksmode package to handle these kinds of hosting-provider specific oddities.

Option 2: Headers

This is the super flexible option. Just return the HTTP headers you want to add as an object with { headers }.
If you can provide
{ username, password, headers } if you want. (Although if headers includes an Authentication property that overwrites what you would normally get from username/password.)

To re-implement the default Basic Auth behavior, do something like this:

js
let auth = {
headers: {
Authorization:
Basic ${Buffer.from(${username}:${password}).toString('base64')}
}
}
text
If you are using a custom proxy server that has its own authentication in addition to the destination authentication, you could inject it like so:
js
let auth = {
username,
password,
headers: {
'X-Authentication':
Bearer ${token}
}
}
text
---

OnAuthFailure

---
title: onAuthFailure
sidebar_label: onAuthFailure
---

The onAuthFailure callback is called when credentials fail.
This is helpful to know if you were using a saved password in the
onAuth callback, then you may want to offer the user the option to delete the currently saved password.
It also gives you an opportunity to retry the request with new credentials.

As long as your onAuthFailure function returns credentials, it will keep trying.
This is the main reason we don't reuse the
onAuth callback for this purpose. If we did, then a naive onAuth callback that simply returned saved credentials might loop indefinitely.

An onAuthFailure function is called with a url and an auth object and can return a GitAuth object:

js
/
* @callback AuthFailureCallback
* @param {string} url
* @param {GitAuth} auth The credentials that failed
* @returns {GitAuth | void | Promise<GitAuth | void>}
*/

/
* @typedef {Object} GitAuth
* @property {string} [username]
* @property {string} [password]
* @property {Object<string, string>} [headers]
* @property {boolean} cancel - Tells git to throw a
UserCanceledError (instead of an HTTPError).
*/

text

Example

js
await git.clone({
...,
onAuthFailure: (url, auth) => {
forgetSavedPassword(url)
if (confirm('Access was denied. Try again?')) {
auth = {
username: prompt('Enter username'),
password: prompt('Enter password'),
}
return auth
} else {
return { cancel: true }
}
}
})
text
---

OnAuthSuccess

---
title: onAuthSuccess
sidebar_label: onAuthSuccess
---

The onAuthSuccess callback is called when credentials work. This is helpful to know if you want to offer to save the credentials, but only if they are valid.

An onAuthSuccess function is called with a url and an auth object.

js
/
* @callback AuthSuccessCallback
* @param {string} url
* @param {GitAuth} auth
* @returns {void | Promise<void>}
*/

/
* @typedef {Object} GitAuth
* @property {string} [username]
* @property {string} [password]
* @property {Object<string, string>} [headers]
* @property {boolean} cancel - Tells git to throw a
UserCanceledError (instead of an HTTPError).
*/

text

Example

js
await git.clone({
...,
onAuthSuccess: (url, auth) => {
if (confirm('Remember password?')) {
savedPassword(url, auth)
}
}
})
text
---

OnMessage

---
title: onMessage
sidebar_label: onMessage
---

The clone, fetch, push, and pull commands all accept an onMessage callback.

Message events are for messages generated by the remote server and sent during fetch and push requests.
They are particularly useful if the remote server has custom git-hooks that print to the console.

Usage Example:

You are writing a console application, and you want to simply print any server messages to standard out.

js
const git = require('isomorphic-git')
git.clone({
...,
onMessage: console.log
})
text
---

OnPostCheckout

---
title: onPostCheckout
sidebar_label: onPostCheckout
---

The onPostCheckout callback is called after a worktree is updated.

This callback is implemented as an equivalent to the canonical git post-checkout hook. The onPostCheckout function is passed an object containing the object IDs of the previous and new HEAD and information about whether a branch or a set of files was checked.

js
/
* @callback PostCheckoutCallback
* @param {PostCheckoutParams} args
* @returns {void | Promise<void>}
*/

/
* @typedef {Object} PostCheckoutParams
* @property {string} previousHead The SHA-1 object id of HEAD before checkout
* @property {string} newHead The SHA-1 object id of HEAD after checkout
* @property {'branch' | 'file'} type flag determining whether a branch or a set of files was checked
*/

text
For more information, see post-checkout git documentation.

Example

js
await git.checkout({
...,
onPostCheckout: args => {
console.log(args)
}
})
text
---

OnPrePush

---
title: onPrePush
sidebar_label: onPrePush
---

The onPrePush callback is called before sending an object pack to remote and can be used to abort the push action.

This callback is implemented as an equivalent to the canonical git pre-push hook. An onPrePush function is passed an object containing information about the target remote and url, local and remote ref name and the commit oids. This function must return false if the push action is to be aborted and true if not.

js
/
* @callback PrePushCallback
* @param {PrePushParams} args
* @returns {boolean | Promise<boolean>} Returns false if the push must be cancelled
*/

/
* @typedef {Object} PrePushParams
* @property {string} remote The expanded name of the target remote
* @property {string} url The URL address of the target remote
* @property {ClientRef} localRef The ref which the client wants to push to the remote
* @property {ClientRef} remoteRef The ref which is known by the remote
*/

/
* @typedef {Object} ClientRef
* @property {string} ref The name of the ref
* @property {string} oid The SHA-1 object id the ref points to
*/

text
For more information, see pre-push git documentation.

Example

js
await git.push({
...,
onPrePush: args => {
console.log(args)
return false
}
})
text
---

OnProgress

---
title: onProgress
sidebar_label: onProgress
---

Long-running commands can accept an onProgress callback that is called with GitProgressEvents.

js
/
* @typedef {Object} GitProgressEvent
* @property {string} phase
* @property {number} loaded
* @property {number} total
*/
text
Progress events are not guaranteed to be in order or always incrementing.
Many git commands (like
clone) actually consist of multiple sub-commands (fetch + indexPack + checkout) which
makes computing a single progress percentage tricky.
Instead, progress events are marked with a
phase that provides a description of what step of the process it is in.
You could choose to show the phase as a label next to the progress bar, or show one progress bar per phase.

Usage Example:

You are writing a browser application, and want to display progress in your UI somehow.

js
import { clone } from 'isomorphic-git'
clone({
...,
onProgress: event => {
updateLabel(event.phase)
if (event.total) {
updateProgressBar(event.loaded / event.total)
} else {
updateIndeterminateProgressBar(event.loaded)
}
}
})
text
---

OnSign

---
title: onSign
sidebar_label: onSign
---

In order to use the PGP signing feature of commit, you have to provide a PGP signing callback like so:

js
import { pgp } from '@isomorphic-git/pgp-plugin'
git.commit({ ..., onSign: pgp.sign })
text
You can choose between an OpenPGP.js implementation and an isomorphic-pgp implementation!

OpenPGP (recommended for node apps)
- much wider support for different keys
- LGPL (which probably means you can't bundle it into your application)
- ~164kb gzipped

isomorphic-pgp (recommended for browser apps)
- limited types of keys supported
- MIT
- ~21k gzipped

Implementing your own onSign callback

The PGP signing function must implement the following API:

#### async ({ payload, secretKey }) => { signature }

| param | type [= default] | description |
| ------------- | ------------------------------ | ----------------------------------------------------------------------------------- |
| payload | string | a plaintext message |
| secretKey | string | an 'ASCII armor' encoded PGP key (technically can actually contain _multiple_ keys) |
| return | Promise\<{signature: string}\> | an 'ASCII armor' encoded "detached" signature |

Verifying Signatures

To verify signed commits and signed annotated tag objects, you use the signature (.gpgsig) and the signing payload (payload) as returned from log, readCommit, or readTag.

js
// Verify a whole bunch of commits
import { pgp } from '@isomorphic-git/pgp-plugin'

let commits = await git.log({ fs, dir, ref: 'main' })
for (const { commit, payload } of commits) {
let { valid, invalid } = await pgp.verify({ payload, publicKey, signature: commit.gpgsig })
// valid is a string[] of the valid key ids
// invalid is a string[] of the invalid key ids. Ideally this is empty.
}

text
js
// Verify a commit object
import { pgp } from '@isomorphic-git/pgp-plugin'

let oid = await git.resolveRef({ fs, dir, ref: 'main' })
let { commit, payload } = await git.readCommit({ fs, dir, oid })
let { valid, invalid } = await pgp.verify({ payload, publicKey, signature: commit.gpgsig })
// valid is a string[] of the valid key ids
// invalid is a string[] of the invalid key ids

text
js
// Verify an annotated tag object
import { pgp } from '@isomorphic-git/pgp-plugin'
import { resolveRef, readCommit } from 'isomorphic-git'

let oid = await resolveRef({ fs, dir, ref: 'v1.0.0' })
let { tag, payload } = await readTag({ fs, dir, oid })
let { valid, invalid } = await pgp.verify({ payload, publicKey, signature: tag.signature })
// valid is a string[] of the valid key ids
// invalid is a string[] of the invalid key ids

text

A valid signature isn't enough!

Note that simply verifying the signatures are valid is not sufficient to establish _trust_.
You must also have reason to believe that the
publicKey really does belong to the person who wrote the commit.
You must also have a way to find the
publicKey in the first place!

So how _do_ you get the publicKey? Here are two ways, each with serious drawbacks. (Spoiler: there's no standard solution yet.)

One thing you could do would be to use commit.author.email and commit.committer.email, match those to GitHub usernames (not a trivial task because their emails might be private), and then lookup the PGP key on GitHub. See ghkeys for an implementation of username -> PGP key lookup. The downside to this is, it only works for commits, signed by users, who have public emails on GitHub, who have uploaded their PGP keys on GitHub. But on the positive side, you can be pretty sure the PGP key really does belong to that user, because GitHub is acting as the authority. To be extra careful, I think GitHub's API lets you check whether the email address is a verified email address or not.

Another thing you could try is parse the PGP signature, extract the public key ID, and use the key ID to lookup the
public key on a PGP keyserver like mit.pgp.edu. (Some code to do just that follows this paragraph.) One downside to this is it only works if people bother to upload their key to a PGP keyserver. Another downside to this is there is absolutely no security. Anybody can upload a key claiming to be
[email protected] or whatever. If you look up the PGP key from the signature, you also need to make sure that the email address associated with the publicKey is the same one used in commit.author.email (or commit.committer.email). If you rely on a public keyserver where anyone can claim to be [email protected], then you'll need to exploit the Web-of-Trust (where keys are signed by other keys, which are signed by other keys, and so on until you reach a "trusted key") or use some other way to decide keys are trustworthy.

js
const extractKey = (gpgsig) => {
const m = Message.parse(gpgsig);
for (const p of m.packets) {
if (p.tag === 2 / Signature Packet /) {
for (const s of p.packet.unhashed.subpackets) {
if (s.type === 16 / Issuer /) {
return s.subpacket.issuer_s
}
}
}
}
}

const lookupKey = async (keyid) => {
let text = await (await fetch(
http://pgp.mit.edu/pks/lookup?op=get&search=0x${keyid})).text()
let matches = text.match(/-----BEGIN PGP PUBLIC KEY BLOCK-----(.|\n)*-----END PGP PUBLIC KEY BLOCK-----/)
if (matches) return matches[0]
}

text
You could do a "trust on first use" strategy where the first time you see a signed commit by [email protected] you lookup the public key and save it, and then in the future if a signed commit by [email protected] uses a different key, show a warning to the user that the key has changed. (This is very similar to the strategy used by SSH - maybe you've seen the famous message The authenticity of host <IP ADDRESS> can't be established. RSA key fingerprint is <FINGERPRINT>. Are you sure you want to continue connecting (yes/no)?)

If you're making a more enterprisey, application, you could send the user an email and verify the email that way. You could store the PGP keys that you've verified in a centralized database.

Sadly, these two questions:

- Where do I find the public key?
- Can I trust that the public key really belongs to this email address?

have no simple answers. However, if you're building a decentralized system where you auto-generate PGP keys for users, I'd recommend saving the public PGP keys in the git repo itself. That seems like an obvious place.

---

Snippets

---
title: Useful Code Snippets
sidebar_label: Useful Snippets
---

Looking for useful code snippets? Look right here! Have a useful code snippet? Add it to the collection! (Click the Edit button in the top right of the page.)

All snippets are published under the MIT License.

- git add --no-all .
- git add -A .
- Use native git credential manager
- GitHub Pages deploy script
- git log -- path/to/file
- git diff --name-status \<commitHash1\> \<commitHash2\>
- Remove untracked files

git add --no-all .

js
const globby = require('globby');
const paths = await globby(['./', './/.*'], { gitignore: true });
for (const filepath of paths) {
await git.add({ fs, dir, filepath });
}
text

git add -A .

js
await git.statusMatrix(repo).then((status) =>
Promise.all(
status.map(([filepath, , worktreeStatus]) =>
worktreeStatus ? git.add({ ...repo, filepath }) : git.remove({ ...repo, filepath })
)
)
)
text

Use native git credential manager

Adapted from the Antora docs:

js
const { spawn } = require('child_process')
const { URL } = require('url')

async function onAuth (url) {
const { protocol, host } = new URL(url)
return new Promise((resolve, reject) => {
const output = []
const process = spawn('git', ['credential', 'fill'])
process.on('close', (code) => {
if (code) return reject(code)
const { username, password } = output.join('\n').split('\n').reduce((acc, line) => {
if (line.startsWith('username') || line.startsWith('password')) {
const [ key, val ] = line.split('=')
acc[key] = val
}
return acc
}, {})
resolve({ username, password })
})
process.stdout.on('data', (data) => output.push(data.toString().trim()))
process.stdin.write(
protocol=${protocol.slice(0, -1)}\nhost=${host}\n\n)
})
}

await git.clone({ ...repo, onAuth })

text

GitHub Pages deploy script

js
// website/scripts/deploy-gh-pages.js
const path = require('path')
const fs = require('fs')
const git = require('isomorphic-git')
const http = require('isomorphic-git/http/node')

// PARAMETERS - CHANGE THESE FOR YOUR CODE
const url = 'https://github.com/isomorphic-git/isomorphic-git.github.io'
const sourceDir = path.join(__dirname, '../..')
const buildDir = path.join(sourceDir, 'website/build/isomorphic-git.github.io')

;(async () => {
let dir = sourceDir
const commits = await git.log({ fs, dir, depth: 1 })
const commit = commits[0].commit

dir = buildDir
await git.init({ fs, dir })
await git.addRemote({ fs, dir, url, remote: 'origin' })
await git.fetch({ http, fs, dir, ref: 'main', depth: 1 })
await git.checkout({ fs, dir, ref: 'main', noCheckout: true })
await git.add({ fs, dir, filepath: '.' })
await git.commit({ fs, dir, author: commit.author, message: commit.message })
await git.push({
http,
fs,
dir,
onAuth: () => ({
oauth2format: 'github',
token: process.env.GITHUB_TOKEN,
}),
})
})()

text

git log -- path/to/file

js
const fs = require('fs')
const git = require('.')

// PARAMETERS - CHANGE THESE FOR YOUR CODE
const dir = '.'
const filepath = 'path/to/file'

;(async () => {
const commits = await git.log({ fs, dir })
let lastSHA = null
let lastCommit = null
const commitsThatMatter = []
for (const commit of commits) {
try {
const o = await git.readObject({ fs, dir, oid: commit.oid, filepath })
if (o.oid !== lastSHA) {
if (lastSHA !== null) commitsThatMatter.push(lastCommit)
lastSHA = o.oid
}
} catch (err) {
// file no longer there
commitsThatMatter.push(lastCommit)
break
}
lastCommit = commit
}
console.log(commitsThatMatter)
})()

text

git diff --name-status \<commitHash1\> \<commitHash2\>


Adapted from GitViz by @kpj
js
async function getFileStateChanges(commitHash1, commitHash2, dir) {
return git.walk({
fs,
dir,
trees: [git.TREE({ ref: commitHash1 }), git.TREE({ ref: commitHash2 })],
map: async function(filepath, [A, B]) {
// ignore directories
if (filepath === '.') {
return
}
if ((await A.type()) === 'tree' || (await B.type()) === 'tree') {
return
}

// generate ids
const Aoid = await A.oid()
const Boid = await B.oid()

// determine modification type
let type = 'equal'
if (Aoid !== Boid) {
type = 'modify'
}
if (Aoid === undefined) {
type = 'add'
}
if (Boid === undefined) {
type = 'remove'
}
if (Aoid === undefined && Boid === undefined) {
console.log('Something weird happened:')
console.log(A)
console.log(B)
}

return {
path:
/${filepath},
type: type,
}
},
})
}

text

Remove untracked files

This snippet walks the index (tree: STAGE) and worktree (tree: WORKDIR), identifies untracked files and directories in the worktree, and removes them.

js
const { promises: fsp } = require('fs')

function removeUntrackedFiles (repo) {
const trees = [git.STAGE({}), git.WORKDIR()]
const map = (relpath, [sEntry]) => {
if (relpath === '.') return
if (relpath === '.git') return null
if (sEntry == null) return fsp.rm(ospath.join(repo.dir, relpath), { recursive: true }).then(() => null)
return sEntry.mode().then((mode) => (mode === 0o120000 ? null : undefined))
}
return git.walk({ ...repo, trees, map })
}

text
---

CONTRIBUTING

⊱⋅──────⋅⊱ Contributing to isomorphic-git ⊰⋅──────⋅⊰

Oh wow! Thanks for opening up the contributing file! :grin: :tada:

You are very welcome here and any contribution is appreciated. :+1:

Tips

The code is written in "plain" JavaScript and as a rule of thumb shouldn't require transpilation. (The glaring exception being browser's lack of support for bare imports.)

New feature checklists :sparkles:️

I'm honestly documenting these steps just so I don't forget them myself.

To add a parameter to an existing command X:

- [ ] add parameter to the function in src/api/X.js (and src/commands/X.js if necessary)
- [ ] document the parameter in the JSDoc comment above the function
- [ ] add a test case in
__tests__/test-X.js if possible
- [ ] if this is your first time contributing, run
npm run add-contributor and follow the prompts to add yourself to the README
- [ ] squash merge the PR with commit message "feat(X): Added 'bar' parameter"
- [ ] see Appendix A below, about submodules

To create a new command:

- [ ] add as a new file in src/api (and src/commands if necessary)
- [ ] add command to
src/index.js (named and/or default export)
- [ ] update
__tests__/__snapshots__/test-exports.js.snap
- [ ] create a test in
src/__tests__
- [ ] document the command with a JSDoc comment
- [ ] add page to the Docs Sidebar
website/sidebars.json
- [ ] if this is your first time contributing, run
npm run add-contributor and follow the prompts to add yourself to the README
- [ ] squash merge the PR with commit message "feat: Added 'X' command"
- [ ] see Appendix A below, about submodules

Overview

I have written this library as a series of layers that build upon one another and should tree-shake very well:

commands

Each command is available as its own file, so you are able to import individual commands
if you only need a few in order to optimize your bundle size.

managers

Managers are a level above models. They take care of implementation performance details like

- batching reads to and from the file system
- in-process concurrency locks
- lockfiles
- caching files and invalidating cached results
- reusing objects
- object memory pools

everything else

These are the lowest level building blocks. They tend to be small, pure functions.

models

Models generally have very few or no dependencies except for 'buffer'.
This makes them portable to many different environments so they can be a useful lowest common denominator.

utils

Utils are basically miscellaneous functions.

storage

This folder contains code for reading and writing to the git "object store".
I'm hoping I can abstract it into a plugin interface at some point so that the plugin system can provide
alternative object stores that integrate seamlessly.

wire

This folder contains the parsers and serializers for the Git wire protocol.
For a given thing, like an upload-pack command, there can be up to 4 different functions.

Client:
write[*]Request: (input: Object) -> stream
parse[*]Response: (input: stream) -> Object

Server:
parse[*]Request: (input: stream) -> Object
write[*]Response: (input: Object) -> stream

How git works

If you want to contribute it may be useful if you understand how git works under the hood.
This is great article that shows the details:<br/>
A Hacker's Guide to Git.<br/>
But as first the introduction you can watch this video:<br/>
[](http://www.youtube.com/watch?v=bSA91XTzeuA)

And here are some other advanced videos:<br/>

- Advanced Git: Graphs, Hashes, and Compression, Oh My!<br/>
[](https://www.youtube.com/watch?v=ig5E8CcdM9g)

- Git Internals by John Britton of GitHub - CS50 Tech Talk<br/>
[](https://www.youtube.com/watch?v=lG90LZotrpo)

- Anatomy of ".git/" folder - How Git Really Works Under the Hood by Piotr Kowalski<br/>
[](https://www.youtube.com/watch?v=rzwyDeRlLdE)
- Stop Memorizing Git Commands. Learn The Data Model<br/>
[](https://www.youtube.com/watch?v=Csd4lMKPC5g)

Another resource is GitHub blog:

- Git’s database internals I: packed object store
- Git’s database internals II: commit history queries

And this description of .git directory:

- What is in that .git directory?

There is also chapter in git Pro book

- Git Internals

You can also search git in the blog of Julia Evans.

Appendix A, submodules

As of 2026, isomorphic-git supports commands run within submodules and so new contributions should take this into account.

The quick TLDR summary is this: look in __tests__ and you'll see there are two test files for every command, a regular one and an -in-submodule.js version. Make sure to include both.

The following discussion covers more details.

1. Modifying or adding __tests__ to existing apis

Let's say the main file is test-branch.js and the corresponding submodule file is test-branch-in-submodule.js. After modifying or adding tests in test-branch.js, copy and paste identical code to test-branch-in-submodule.js since the new submodule tests will be mostly the same. Replace any instances of makeFixture with makeFixtureAsSubmodule in submodule tests. Prefer the plain variable gitdir in test files whenever possible, only swapping it to gitdirsmfullpath as a last resort if tests are failing and there is no other choice. gitdirsmfullpath is almost like "cheating" because it reveals to the testing code where the gitdir really is, but often that answer should be computed automatically, and not passed in.

2. Creating new __tests__ for brand new apis

Let's imagine "brancher" is a new API command. Place two new files in __tests__ which are test-brancher.js and test-brancher-in-submodule.js. They should be mostly identical. For submodule tests, import and use makeFixtureAsSubmodule instead of makeFixture. See the notes above about the gitdir variable.

3. Creating new src/api/ commands

In terms of submodule-related features, only modify src/api/ files and not src/commands/. This is an architectural decision to keep logic at one layer of the stack, while other layers may remain unaffected. Review other files, the basic idea is to apply the discoverGitdir function, and never assume gitdir is right. Send the gitdir value through the discoverGitdir filter before passing it anywhere else. In a common situation, when submodules aren't used, the discoverGitdir filter will just send back the original value. If it turns out a submodule is used it will return the required information.

4. Modifying existing src/api/ commands

Depending on the situation, perhaps nothing must change. See the notes above regarding the discoverGitdir function.

---

README

<p align="center">
<a href="https://isomorphic-git.org/" target="_blank" rel="noopener">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/isomorphic-git-dark.svg?raw=true" />
<source media="(prefers-color-scheme: light)" srcset="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/isomorphic-git-light.svg?raw=true" />
<img alt="Isomorphic Git logo" src="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/isomorphic-git-light.svg?raw=true" height="150" />
</picture>
</a>
</p>

isomorphic-git

isomorphic-git is a pure JavaScript reimplementation of git that works in both Node.js and browser JavaScript environments. It can read and write to git repositories, fetch from and push to git remotes (such as GitHub), all without any native C++ module dependencies.

Goals

Isomorphic-git aims for 100% interoperability with the canonical git implementation. This means it does all its operations by modifying files in a ".git" directory just like the git you are used to. The included isogit CLI can operate on git repositories on your desktop or server.

This library aims to be a complete solution with no assembly required.
The API has been designed with modern tools like Rollup and Webpack in mind.
By providing functionality as individual functions, code bundlers can produce smaller bundles by including only the functions your application uses.

The project includes type definitions so you can enjoy static type-checking and intelligent code completion in editors like VS Code and CodeSandbox.

Project status


The original author of the project (Billie Hilton) left the project, but the project is still maintained by two volunteers:

* @jcubic (most active)
* @mojavelinux

But they don't write much code, mainly do code review and try to answer to issues and on Gitter, they just don't want the project to die. So you can say that this project is community driven (as jcubic always reply to issues). Which means that if you want a feature to be implemented you need to do this yourself or find someone that is willing to write the code for you. The project have some money on OpenCollective and we can spend it on some development, if you find someone that is willing to code in exchange to some bucks (it may be you), but we don't have a lot so don't expect to have full sallary.

If you want to help this project you're more than welcome to do so.

Supported Environments

The following environments are tested in CI and will continue to be supported until the next breaking version:

<table width="100%">
<tr>
<td align="center"><img src="https://raw.githubusercontent.com/isomorphic-git/isomorphic-git/main/website/static/img/browsers/node.webp" alt="" width="64" height="64"><br> Node 10</td>
<td align="center"><img src="https://raw.githubusercontent.com/alrra/browser-logos/bc47e4601d2c1fd46a7912f9aed5cdda4afdb301/src/chrome/chrome.svg?sanitize=true" alt="" width="64" height="64"><br> Chrome 79</td>
<td align="center"><img src="https://raw.githubusercontent.com/alrra/browser-logos/bc47e4601d2c1fd46a7912f9aed5cdda4afdb301/src/edge/edge.svg?sanitize=true" alt="" width="64" height="64"><br> Edge 79</td>
<td align="center"><img src="https://raw.githubusercontent.com/alrra/browser-logos/bc47e4601d2c1fd46a7912f9aed5cdda4afdb301/src/firefox/firefox.svg?sanitize=true" alt="" width="64" height="64"><br> Firefox 72</td>
<td align="center"><img src="https://raw.githubusercontent.com/alrra/browser-logos/bc47e4601d2c1fd46a7912f9aed5cdda4afdb301/src/safari/safari_64x64.png" alt="" width="64" height="64"><br> Safari 13</td>
<td align="center"><img src="https://upload.wikimedia.org/wikipedia/commons/6/64/Android_logo_2019_%28stacked%29.svg" alt="" width="64" height="64"><br> Android 10</td>
<td align="center"><img src="https://upload.wikimedia.org/wikipedia/commons/d/d6/IOS_13_logo.svg" alt="" width="64" height="64"><br> iOS 13</td>
</tr>
</table>

Upgrading from version 0.x to version 1.x?

See the full Release Notes on GitHub and the release Blog Post.

Install

You can install it from npm:


npm install --save isomorphic-git
text

Getting Started

The "isomorphic" in isomorphic-git means that the same code runs in either the server or the browser.
That's tricky to do since git uses the file system and makes HTTP requests. Browsers don't have an
fs module.
And node and browsers have different APIs for making HTTP requests!

So rather than relying on the fs and http modules, isomorphic-git lets you bring your own file system
and HTTP client.

If you're using isomorphic-git in node, you use the native fs module and the provided node HTTP client.

js
// node.js example
const path = require('path')
const git = require('isomorphic-git')
const http = require('isomorphic-git/http/node')
const fs = require('fs')

const dir = path.join(process.cwd(), 'test-clone')
git.clone({ fs, http, dir, url: 'https://github.com/isomorphic-git/lightning-fs' }).then(console.log)

text
If you're using isomorphic-git in the browser, you'll need something that emulates the fs API.
The easiest to setup and most performant library is LightningFS which is written and maintained by the same author and is part of the
isomorphic-git suite.

⚠️ LightningFS may apply file operations out of order, which can lead to repository corruption if the process crashes. You can mitigate this by calling fs.flush() after Git operations.

If LightningFS doesn't meet your requirements, isomorphic-git should also work with ZenFS and Filer.
Instead of
isomorphic-git/http/node this time import isomorphic-git/http/web:

html
<script src="https://unpkg.com/@isomorphic-git/lightning-fs"></script>
<script src="https://unpkg.com/isomorphic-git"></script>
<script type="module">
import http from 'https://unpkg.com/isomorphic-git@beta/http/web/index.js'
const fs = new LightningFS('fs')

const dir = '/test-clone'
git.clone({ fs, http, dir, url: 'https://github.com/isomorphic-git/lightning-fs', corsProxy: 'https://cors.isomorphic-git.org' }).then(console.log)
</script>

text
If you're using ES module syntax, you can use either the default import for convenience, or named imports to benefit from tree-shaking if you are using a bundler:
js
import git from 'isomorphic-git'
// or
import * as git from 'isomorphic-git'
// or
import {plugins, clone, commit, push} from 'isomorphic-git'
text
View the full Getting Started guide on the docs website.

Then check out the Useful Snippets page, which includes even more sample code written by the community!

CORS support

Unfortunately, due to the same-origin policy by default isomorphic-git can only clone from the same origin as the webpage it is running on. This is terribly inconvenient, as it means for all practical purposes cloning and pushing repos must be done through a proxy.

For this purpose, @isomorphic-git/cors-proxy exists; which you can clone it or npm install it. Alternatively, use CloudFlare workers, which can be setup without leaving the browser (instructions).

For testing or small projects, you can also use https://cors.isomorphic-git.org - a free proxy sponsored by Clever Cloud.

We hope to get CORS headers added to all the major Git hosting platforms eventually, and will list the progress made here:

| Service | Supports CORS requests |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Gogs (self-hosted) | |
| Gitea (self-hosted) | |
| Azure DevOps | (Usage Note: requires authentication) |
| Gitlab | ❌ Our PR was rejected, but the issue is still open! |
| Bitbucket | ❌ |
| Github | ❌ |

It is literally just two lines of code to add the CORS headers!! Easy stuff. Surely it will happen.

isogit CLI

Isomorphic-git comes with a simple CLI tool, named isogit because isomorphic-git is a lot to type. It is really just a thin shell that translates command line arguments into the equivalent JS API commands. So you should be able to run any current or future isomorphic-git commands using the CLI.

It always starts with an the assumption that the current working directory is a git root.
E.g.
{ dir: '.' }.

It uses minimisted to parse command line options and will print out the equivalent JS command and pretty-print the output JSON.

The CLI is more of a lark for quickly testing isomorphic-git and isn't really meant as a git CLI replacement.

Supported Git commands

This project follows semantic versioning, so we may continue to make changes to the API but they will always be backwards compatible
unless there is a major version bump.

commands

- abortMerge
- add
- addNote
- addRemote
- annotatedTag
- branch
- checkout
- cherryPick
- clone
- commit
- currentBranch
- deleteBranch
- deleteRef
- deleteRemote
- deleteTag
- expandOid
- expandRef
- fastForward
- fetch
- findMergeBase
- findRoot
- getConfig
- getConfigAll
- getRemoteInfo
- getRemoteInfo2
- hashBlob
- indexPack
- init
- isDescendent
- isIgnored
- listBranches
- listFiles
- listNotes
- listRefs
- listRemotes
- listServerRefs
- listTags
- log
- merge
- packObjects
- pull
- push
- readBlob
- readCommit
- readNote
- readObject
- readTag
- readTree
- remove
- removeNote
- renameBranch
- resetIndex
- resolveRef
- setConfig
- stash
- status
- statusMatrix
- tag
- updateIndex
- version
- walk
- writeBlob
- writeCommit
- writeObject
- writeRef
- writeTag
- writeTree

Community

Share your questions and ideas with us! We love that.
You can find us in our Gitter chatroom or just create an issue here on Github!
We are also @IsomorphicGit on Twitter.

Contributing to isomorphic-git

The development setup is similar to that of a large web application.
The main difference is the ridiculous amount of hacks involved in the tests.
We use Facebook's Jest for testing, which make doing TDD fast and fun,
but we also used custom hacks so that the same
tests will also run in the browser using Jasmine via Karma.
We even have our own mock server for serving
git repository test fixtures!

You'll need node.js installed, but everything else is a devDependency.

sh
git clone https://github.com/isomorphic-git/isomorphic-git
cd isomorphic-git
npm install
npm test
`

The new release happens automatically after every PR merge. We use semantic release.

Check out the CONTRIBUTING` document for more instructions.

Who is using isomorphic-git?

- nde - a futuristic next-generation web IDE
- git-app-manager - install "unhosted" websites locally by git cloning them
- GIT Web Terminal
- Next Editor
- Clever Cloud
- Stoplight Studio - a modern editor for API design and technical writing

Similar projects

- js-git
- es-git

Acknowledgments

Isomorphic-git would not have been possible without the pioneering work by
@creationix and @chrisdickinson. Git is a tricky binary mess, and without
their examples (and their modules!) we would not have been able to come even
close to finishing this. They are geniuses ahead of their time.

Cross-browser device testing is provided by:

[](http://browserstack.com/)

<a href="https://saucelabs.com" target="_blank" rel="noopener">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://saucelabs.com/images/logo-white.svg" />
<source media="(prefers-color-scheme: light)" srcset="https://saucelabs.com/images/logo.svg" />
<img alt="Sauce Labs logo" src="https://saucelabs.com/images/logo.svg" height="40" />
</picture>
</a>

Code Review AI provided by:

<a href="https://coderabbit.ai" target="_blank" rel="noopener">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/coderabbit-dark.svg?raw=true" />
<source media="(prefers-color-scheme: light)" srcset="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/coderabbit-light.svg?raw=true" />
<img alt="CodeRabbit logo" src="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/coderabbit-light.svg?raw=true" height="40" />
</picture>
</a>

DNS provided by:

<a href="https://www.cloudflare.com/" target="_blank" rel="noopener">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/cloudflare-dark.svg?raw=true" />
<source media="(prefers-color-scheme: light)" srcset="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/cloudflare-light.svg?raw=true" />
<img alt="CloudFlare logo" src="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/cloudflare-light.svg?raw=true" height="40" />
</picture>
</a>

Hosting (for CORS) provided by:

<a href="https://www.clever.cloud/" target="_blank" rel="noopener">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/clever-cloud-dark.svg?raw=true" />
<source media="(prefers-color-scheme: light)" srcset="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/clever-cloud-light.svg?raw=true" />
<img alt="Clever Cloud logo" src="https://github.com/isomorphic-git/isomorphic-git/blob/main/.github/images/clever-cloud-light.svg?raw=true" height="40" />
</picture>
</a>

Contributors

Thanks goes to these wonderful people (emoji key):


<table>
<tr>
<td align="center"><a href="https://onename.com/wmhilton"><img src="https://avatars2.githubusercontent.com/u/587740?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>William Hilton</b></sub></a><br /><a href="#blog-wmhilton" title="Blogposts">📝</a> <a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Awmhilton" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=wmhilton" title="Code">💻</a> <a href="#design-wmhilton" title="Design">🎨</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=wmhilton" title="Documentation">📖</a> <a href="#example-wmhilton" title="Examples">💡</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=wmhilton" title="Tests">⚠️</a> <a href="#tutorial-wmhilton" title="Tutorials">✅</a></td>
<td align="center"><a href="https://github.com/wDhTIG"><img src="https://avatars2.githubusercontent.com/u/33748231?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>wDhTIG</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3AwDhTIG" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/marbemac"><img src="https://avatars3.githubusercontent.com/u/847542?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Marc MacLeod</b></sub></a><br /><a href="#ideas-marbemac" title="Ideas, Planning, & Feedback">🤔</a> <a href="#fundingFinding-marbemac" title="Funding Finding">🔍</a></td>
<td align="center"><a href="http://brett-zamir.me"><img src="https://avatars3.githubusercontent.com/u/20234?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Brett Zamir</b></sub></a><br /><a href="#ideas-brettz9" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://mojavelinux.com"><img src="https://avatars2.githubusercontent.com/u/79351?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Dan Allen</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Amojavelinux" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=mojavelinux" title="Code">💻</a> <a href="#ideas-mojavelinux" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://TomasHubelbauer.net"><img src="https://avatars1.githubusercontent.com/u/6831144?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Tomáš Hübelbauer</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3ATomasHubelbauer" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=TomasHubelbauer" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/juancampa"><img src="https://avatars2.githubusercontent.com/u/1410520?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Juan Campa</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Ajuancampa" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=juancampa" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="http://iramiller.com"><img src="https://avatars2.githubusercontent.com/u/1041868?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Ira Miller</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Aisysd" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://rhys.arkins.net"><img src="https://avatars1.githubusercontent.com/u/6311784?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Rhys Arkins</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=rarkins" title="Code">💻</a></td>
<td align="center"><a href="http://twitter.com/TheLarkInn"><img src="https://avatars1.githubusercontent.com/u/3408176?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Sean Larkin</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=TheLarkInn" title="Code">💻</a></td>
<td align="center"><a href="https://daniel-ruf.de"><img src="https://avatars1.githubusercontent.com/u/827205?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Daniel Ruf</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=DanielRuf" title="Code">💻</a></td>
<td align="center"><a href="http://blog.bokuweb.me/"><img src="https://avatars0.githubusercontent.com/u/10220449?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>bokuweb</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=bokuweb" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=bokuweb" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=bokuweb" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/hirokiosame"><img src="https://avatars0.githubusercontent.com/u/1075694?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Hiroki Osame</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hirokiosame" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hirokiosame" title="Documentation">📖</a></td>
<td align="center"><a href="http://jcubic.pl/me"><img src="https://avatars1.githubusercontent.com/u/280241?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Jakub Jankiewicz</b></sub></a><br /><a href="#question-jcubic" title="Answering Questions">💬</a> <a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Ajcubic" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=jcubic" title="Code">💻</a> <a href="#example-jcubic" title="Examples">💡</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=jcubic" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/howardgod"><img src="https://avatars1.githubusercontent.com/u/10459637?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>howardgod</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Ahowardgod" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=howardgod" title="Code">💻</a></td>
<td align="center"><a href="https://twitter.com/btyga"><img src="https://avatars3.githubusercontent.com/u/263378?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>burningTyger</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3AburningTyger" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://melvincarvalho.com/#me"><img src="https://avatars2.githubusercontent.com/u/65864?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Melvin Carvalho</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=melvincarvalho" title="Documentation">📖</a></td>
<td align="center"><img src="https://avatars2.githubusercontent.com/u/3035266?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>akaJes</b></sub><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=akaJes" title="Code">💻</a></td>
<td align="center"><a href="http://twitter.com/dimasabanin"><img src="https://avatars2.githubusercontent.com/u/8316?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Dima Sabanin</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Adsabanin" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=dsabanin" title="Code">💻</a></td>
<td align="center"><a href="http://twitter.com/mizchi"><img src="https://avatars2.githubusercontent.com/u/73962?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Koutaro Chikuba</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Amizchi" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=mizchi" title="Code">💻</a></td>
<td align="center"><a href="https://www.hsablonniere.com/"><img src="https://avatars2.githubusercontent.com/u/236342?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Hubert SABLONNIÈRE</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hsablonniere" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hsablonniere" title="Tests">⚠️</a> <a href="#ideas-hsablonniere" title="Ideas, Planning, & Feedback">🤔</a> <a href="#fundingFinding-hsablonniere" title="Funding Finding">🔍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/DeltaEvo"><img src="https://avatars1.githubusercontent.com/u/8864716?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>David Duarte</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=DeltaEvo" title="Code">💻</a></td>
<td align="center"><a href="http://stoplight.io/"><img src="https://avatars2.githubusercontent.com/u/2294309?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Thomas Pytleski</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Apytlesk4" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=pytlesk4" title="Code">💻</a></td>
<td align="center"><a href="http://linkedin.com/in/vmarkovtsev"><img src="https://avatars3.githubusercontent.com/u/2793551?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Vadim Markovtsev</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Avmarkovtsev" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://yuhr.org"><img src="https://avatars0.githubusercontent.com/u/18474125?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Yu Shimura</b></sub></a><br /><a href="#ideas-yuhr" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=yuhr" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=yuhr" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/pyramation"><img src="https://avatars1.githubusercontent.com/u/545047?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Dan Lynch</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=pyramation" title="Code">💻</a></td>
<td align="center"><a href="https://www.jeffreywescott.com/"><img src="https://avatars3.githubusercontent.com/u/130597?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Jeffrey Wescott</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Ajeffreywescott" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=jeffreywescott" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/zebzhao"><img src="https://avatars2.githubusercontent.com/u/5515758?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>zebzhao</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=zebzhao" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/tilersmyth"><img src="https://avatars2.githubusercontent.com/u/8736328?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Tyler Smith</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Atilersmyth" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/beeman"><img src="https://avatars3.githubusercontent.com/u/36491?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Bram Borggreve</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Abeeman" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/stefan-guggisberg"><img src="https://avatars1.githubusercontent.com/u/1543625?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Stefan Guggisberg</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Astefan-guggisberg" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=stefan-guggisberg" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=stefan-guggisberg" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/katakonst"><img src="https://avatars2.githubusercontent.com/u/6519792?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Catalin Pirvu</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=katakonst" title="Code">💻</a></td>
<td align="center"><a href="http://web.engr.oregonstate.edu/~nelsonni/"><img src="https://avatars1.githubusercontent.com/u/6432572?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Nicholas Nelson</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=nelsonni" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=nelsonni" title="Tests">⚠️</a></td>
<td align="center"><a href="https://twitter.com/addaleax"><img src="https://avatars2.githubusercontent.com/u/899444?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Anna Henningsen</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=addaleax" title="Code">💻</a></td>
<td align="center"><a href="https://hen.ne.ke"><img src="https://avatars0.githubusercontent.com/u/4312191?v=4&s=60?s=60" width="60px;" alt=""/><br /><sub><b>Fabian Henneke</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3AFabianHenneke" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=FabianHenneke" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/djencks"><img src="https://avatars2.githubusercontent.com/u/569822?v=4?s=60" width="60px;" alt=""/><br /><sub><b>djencks</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Adjencks" title="Bug reports">🐛</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=djencks" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=djencks" title="Tests">⚠️</a></td>
<td align="center"><a href="https://justamouse.com"><img src="https://avatars0.githubusercontent.com/u/1086421?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Clemens Wolff</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=c-w" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=c-w" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=c-w" title="Tests">⚠️</a></td>
<td align="center"><a href="https://sojin.io"><img src="https://avatars1.githubusercontent.com/u/3102175?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sojin Park</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=raon0211" title="Code">💻</a></td>
<td align="center"><a href="http://eaf4.com"><img src="https://avatars0.githubusercontent.com/u/319282?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Edward Faulkner</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=ef4" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/KSXGitHub"><img src="https://avatars2.githubusercontent.com/u/11488886?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Khải</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3AKSXGitHub" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://crutchcorn.dev/"><img src="https://avatars0.githubusercontent.com/u/9100169?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Corbin Crutchley</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=crutchcorn" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=crutchcorn" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=crutchcorn" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/snowyu"><img src="https://avatars1.githubusercontent.com/u/327887?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Riceball LEE</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=snowyu" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=snowyu" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=snowyu" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center"><a href="https://onetwo.ren/"><img src="https://avatars1.githubusercontent.com/u/3746270?v=4?s=60" width="60px;" alt=""/><br /><sub><b>lin onetwo</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=linonetwo" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/linfaxin"><img src="https://avatars2.githubusercontent.com/u/3705017?v=4?s=60" width="60px;" alt=""/><br /><sub><b>林法鑫</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Alinfaxin" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/willstott101"><img src="https://avatars2.githubusercontent.com/u/335152?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Will Stott</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=willstott101" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=willstott101" title="Tests">⚠️</a></td>
<td align="center"><a href="http://mtnspring.org/"><img src="https://avatars2.githubusercontent.com/u/223277?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Seth Nickell</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Asnickell" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://www.alextitarenko.me/"><img src="https://avatars0.githubusercontent.com/u/3290313?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Alex Titarenko</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=alex-titarenko" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mmkal"><img src="https://avatars2.githubusercontent.com/u/15040698?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Misha Kaletsky</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=mmkal" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/rczulch"><img src="https://avatars1.githubusercontent.com/u/54646976?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Richard C. Zulch</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=rczulch" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=rczulch" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://scrapbox.io/mkizka/README"><img src="https://avatars.githubusercontent.com/u/30231179?v=4?s=60" width="60px;" alt=""/><br /><sub><b>mkizka</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=mkizka" title="Code">💻</a></td>
<td align="center"><a href="https://ryotak.me/"><img src="https://avatars.githubusercontent.com/u/49341894?v=4?s=60" width="60px;" alt=""/><br /><sub><b>RyotaK</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3ARy0taK" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/strangedev"><img src="https://avatars.githubusercontent.com/u/3045979?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Noah Hummel</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=strangedev" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=strangedev" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/mtlewis"><img src="https://avatars.githubusercontent.com/u/542836?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mike Lewis</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=mtlewis" title="Documentation">📖</a></td>
<td align="center"><a href="https://twitter.com/SamVerschueren"><img src="https://avatars.githubusercontent.com/u/1913805?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sam Verschueren</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=SamVerschueren" title="Code">💻</a></td>
<td align="center"><a href="http://vitorluizc.github.io/"><img src="https://avatars.githubusercontent.com/u/9027363?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Vitor Luiz Cavalcanti</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=VitorLuizC" title="Documentation">📖</a></td>
<td align="center"><a href="https://www.platformdemos.com/"><img src="https://avatars.githubusercontent.com/u/4261788?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Shane McLaughlin</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=mshanemc" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=mshanemc" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=mshanemc" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/seanpoulter"><img src="https://avatars.githubusercontent.com/u/2585460?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sean Poulter</b></sub></a><br /><a href="#maintenance-seanpoulter" title="Maintenance">🚧</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=seanpoulter" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=seanpoulter" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=seanpoulter" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/araknast"><img src="https://avatars.githubusercontent.com/u/84164531?v=4?s=60" width="60px;" alt=""/><br /><sub><b>araknast</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=araknast" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=araknast" title="Tests">⚠️</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=araknast" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/rraab-dev"><img src="https://avatars.githubusercontent.com/u/53948988?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Rafael Raab</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=rraab-dev" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=rraab-dev" title="Documentation">📖</a></td>
<td align="center"><a href="https://gitlab.com/CoalZombik/"><img src="https://avatars.githubusercontent.com/u/49895741?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Lukáš Cezner</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=CoalZombik" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=CoalZombik" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=CoalZombik" title="Tests">⚠️</a> <a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3ACoalZombik" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/dead-end"><img src="https://avatars.githubusercontent.com/u/30635084?v=4?s=60" width="60px;" alt=""/><br /><sub><b>dead-end</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=dead-end" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=dead-end" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=dead-end" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/barry963"><img src="https://avatars.githubusercontent.com/u/5289896?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Barry</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=barry963" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=barry963" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=barry963" title="Tests">⚠️</a></td>
<td align="center"><a href="https://stackoverflow.com/users/1493081/alireza-mirian"><img src="https://avatars.githubusercontent.com/u/3150694?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Alireza Mirian</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=alirezamirian" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=alirezamirian" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=alirezamirian" title="Tests">⚠️</a> <a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Aalirezamirian" title="Bug reports">🐛</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/DanilKazanov"><img src="https://avatars.githubusercontent.com/u/139755256?v=4?s=60" width="60px;" alt=""/><br /><sub><b>DanilKazanov</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=DanilKazanov" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=DanilKazanov" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=DanilKazanov" title="Tests">⚠️</a></td>
<td align="center"><a href="https://api.github.com/users/hisco"><img src="https://avatars.githubusercontent.com/u/39222286?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Eyal Hisco</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Ahisco" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/scolladon"><img src="https://avatars.githubusercontent.com/u/522422?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sebastien</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=scolladon" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yarikoptic"><img src="https://avatars.githubusercontent.com/u/39889?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Yaroslav Halchenko</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=yarikoptic" title="Documentation">📖</a></td>
<td align="center"><a href="https://alex-v.blog/"><img src="https://avatars.githubusercontent.com/u/716334?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Alex Villarreal</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=alexvy86" title="Code">💻</a></td>
<td align="center"><a href="http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=62372"><img src="https://avatars.githubusercontent.com/u/865809?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Modesty Zhang</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=modesty" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=modesty" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=modesty" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/amrc-benmorrow"><img src="https://avatars.githubusercontent.com/u/120477944?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Ben Morrow</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=amrc-benmorrow" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/jayree"><img src="https://avatars.githubusercontent.com/u/14836154?v=4?s=60" width="60px;" alt=""/><br /><sub><b>jayree</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=jayree" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=jayree" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/lsegurado"><img src="https://avatars.githubusercontent.com/u/27731047?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Lucas Martin Segurado</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=lsegurado" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Alsegurado" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/limond"><img src="https://avatars.githubusercontent.com/u/1025682?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Leon Kaucher</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=limond" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=limond" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/gilisho"><img src="https://avatars.githubusercontent.com/u/40733156?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Gili Shohat</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=gilisho" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=gilisho" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=gilisho" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/hhourani27"><img src="https://avatars.githubusercontent.com/u/61935766?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Habib</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hhourani27" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hhourani27" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hhourani27" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/Vinzent03"><img src="https://avatars.githubusercontent.com/u/63981639?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Vinzent</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=Vinzent03" title="Code">💻</a></td>
<td align="center"><a href="https://jamespre.dev/"><img src="https://avatars.githubusercontent.com/u/75621402?v=4?s=60" width="60px;" alt=""/><br /><sub><b>James Prevett</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=james-pre" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=james-pre" title="Tests">⚠️</a> <a href="#maintenance-james-pre" title="Maintenance">🚧</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/LokiMidgard"><img src="https://avatars.githubusercontent.com/u/389101?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Patrick Kranz</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=LokiMidgard" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=LokiMidgard" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=LokiMidgard" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/lukecotter"><img src="https://avatars.githubusercontent.com/u/4013877?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Luke Cotter</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=lukecotter" title="Code">💻</a></td>
<td align="center"><a href="https://tomlarkworthy.endpointservices.net/"><img src="https://avatars.githubusercontent.com/u/1848162?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Tom Larkworthy</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=tomlarkworthy" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/kofta999"><img src="https://avatars.githubusercontent.com/u/99273340?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mostafa Mahmoud</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=kofta999" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=kofta999" title="Tests">⚠️</a> <a href="#question-kofta999" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://github.com/ARBhosale"><img src="https://avatars.githubusercontent.com/u/26981417?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Aniket Bhosale</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=ARBhosale" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=ARBhosale" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=ARBhosale" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/gnillev"><img src="https://avatars.githubusercontent.com/u/8965094?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mathias Nisted Velling</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=gnillev" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=gnillev" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/acandoo"><img src="https://avatars.githubusercontent.com/u/117209328?v=4?s=60" width="60px;" alt=""/><br /><sub><b>acandoo</b></sub></a><br /><a href="#platform-acandoo" title="Packaging/porting to new platform">📦</a> <a href="#userTesting-acandoo" title="User Testing">📓</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/bekatan"><img src="https://avatars.githubusercontent.com/u/19550476?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Bekatan Satyev</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=bekatan" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=bekatan" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/hemanthkini"><img src="https://avatars.githubusercontent.com/u/3934055?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Hemanth Kini</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=hemanthkini" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/anish3333"><img src="https://avatars.githubusercontent.com/u/128889867?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Anish Awasthi</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=anish3333" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=anish3333" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=anish3333" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/fetsorn"><img src="https://avatars.githubusercontent.com/u/12858105?v=4?s=60" width="60px;" alt=""/><br /><sub><b>fetsorn</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=fetsorn" title="Documentation">📖</a></td>
<td align="center"><a href="http://www.dreamingcat.me/"><img src="https://avatars.githubusercontent.com/u/7752883?v=4?s=60" width="60px;" alt=""/><br /><sub><b>xiaoboost</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=xiaoboost" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=xiaoboost" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=xiaoboost" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/Andarist"><img src="https://avatars.githubusercontent.com/u/9800850?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Mateusz Burzyński</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=Andarist" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=Andarist" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/IAmSSH"><img src="https://avatars.githubusercontent.com/u/34162350?v=4?s=60" width="60px;" alt=""/><br /><sub><b>iamssh</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=IAmSSH" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=IAmSSH" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=IAmSSH" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/N0zoM1z0"><img src="https://avatars.githubusercontent.com/u/161784452?v=4?s=60" width="60px;" alt=""/><br /><sub><b>N0zoM1z0</b></sub></a><br /><a href="#security-N0zoM1z0" title="Security">🛡️</a></td>
<td align="center"><a href="https://github.com/amxmln"><img src="https://avatars.githubusercontent.com/u/15271679?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Amadeus Maximilian</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=amxmln" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Aamxmln" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://toxik.us/"><img src="https://avatars.githubusercontent.com/u/235319?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Alexandru Georoceanu</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=toxik" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/arisgk"><img src="https://avatars.githubusercontent.com/u/4354335?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Aris Goudouras</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=arisgk" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=arisgk" title="Documentation">📖</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=arisgk" title="Tests">⚠️</a> <a href="https://github.com/isomorphic-git/isomorphic-git/issues?q=author%3Aarisgk" title="Bug reports">🐛</a></td>
</tr>
</table>


This project follows the all-contributors specification. Contributions of any kind welcome!

Backers

Thank you to all our backers! 🙏 [Become a backer]

<a href="https://opencollective.com/isomorphic-git#backers" target="_blank"><img src="https://opencollective.com/isomorphic-git/backers.svg?width=890"></a>


Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

<a href="https://opencollective.com/isomorphic-git/sponsor/0/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/1/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/2/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/3/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/4/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/5/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/6/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/7/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/8/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/isomorphic-git/sponsor/9/website" target="_blank"><img src="https://opencollective.com/isomorphic-git/sponsor/9/avatar.svg"></a>

License

This work is released under The MIT License

[](https://app.fossa.io/projects/git%2Bgithub.com%2Fisomorphic-git%2Fisomorphic-git?ref=badge_large)

---