---
title: isomorphic-git
description: Push commits to Artifacts repos from Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/artifacts/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# isomorphic-git

Use [isomorphic-git ↗](https://isomorphic-git.org/) in a Cloudflare Worker when you need Git operations without a Git binary.

This works with Artifacts because Artifacts exposes standard Git smart HTTP remotes. In Workers, pair `isomorphic-git/http/web` with a small in-memory filesystem because the runtime does not expose a local disk.

## Install the dependency

Install `isomorphic-git` in your Worker project:

 npm  yarn  pnpm  bun 

```
npm i isomorphic-git
```

```
yarn add isomorphic-git
```

```
pnpm add isomorphic-git
```

```
bun add isomorphic-git
```

## Example

This demo creates a new repo on each request, writes two files, commits them, and pushes `main` to the new remote.

Use this as a reference for the end-to-end flow. In a production Worker, look up or reuse an existing repo instead of creating a new one for every request.

Protect write-capable routes

This example omits authentication so it can focus on the Git flow. In production, authorize the caller before creating repos or granting write capability.

* [  JavaScript ](#tab-panel-4582)
* [  TypeScript ](#tab-panel-4583)

src/index.js

```

import git from "isomorphic-git";

import http from "isomorphic-git/http/web";

import { MemoryFS } from "./memory-fs";


export default {

  async fetch(_request, env) {

    const repoName = `worker-demo-${crypto.randomUUID().slice(0, 8)}`;

    const created = await env.ARTIFACTS.create(repoName);


    // Artifacts returns art_v1_<secret>?expires=<unix_seconds>.

    // For Git Basic auth, pass only the secret as the password.

    const tokenSecret = created.token.split("?expires=")[0];

    const dir = "/workspace";

    const fs = new MemoryFS();


    await git.init({ fs, dir, defaultBranch: "main" });


    await fs.promises.writeFile(

      `${dir}/README.md`,

      "# Artifacts repo created from a Worker\n",

    );

    await fs.promises.writeFile(

      `${dir}/src/index.ts`,

      'export const message = "hello from Artifacts";\n',

    );


    await git.add({ fs, dir, filepath: "README.md" });

    await git.add({ fs, dir, filepath: "src/index.ts" });


    const commit = await git.commit({

      fs,

      dir,

      message: "Create starter files",

      author: {

        name: "Artifacts example",

        email: "artifacts@example.com",

      },

    });


    const push = await git.push({

      fs,

      http,

      dir,

      url: created.remote,

      ref: "main",

      onAuth: () => ({

        username: "x",

        password: tokenSecret,

      }),

    });


    return Response.json({

      repo: created.name,

      remote: created.remote,

      commit,

      refs: push.refs,

    });

  },

};


```

src/index.ts

```

import git from "isomorphic-git";

import http from "isomorphic-git/http/web";

import { MemoryFS } from "./memory-fs";


export interface Env {

  ARTIFACTS: Artifacts;

}


export default {

  async fetch(_request: Request, env: Env) {

    const repoName = `worker-demo-${crypto.randomUUID().slice(0, 8)}`;

    const created = await env.ARTIFACTS.create(repoName);


    // Artifacts returns art_v1_<secret>?expires=<unix_seconds>.

    // For Git Basic auth, pass only the secret as the password.

    const tokenSecret = created.token.split("?expires=")[0];

    const dir = "/workspace";

    const fs = new MemoryFS();


    await git.init({ fs, dir, defaultBranch: "main" });


    await fs.promises.writeFile(

      `${dir}/README.md`,

      "# Artifacts repo created from a Worker\n",

    );

    await fs.promises.writeFile(

      `${dir}/src/index.ts`,

      'export const message = "hello from Artifacts";\n',

    );


    await git.add({ fs, dir, filepath: "README.md" });

    await git.add({ fs, dir, filepath: "src/index.ts" });


    const commit = await git.commit({

      fs,

      dir,

      message: "Create starter files",

      author: {

        name: "Artifacts example",

        email: "artifacts@example.com",

      },

    });


    const push = await git.push({

      fs,

      http,

      dir,

      url: created.remote,

      ref: "main",

      onAuth: () => ({

        username: "x",

        password: tokenSecret,

      }),

    });


    return Response.json({

      repo: created.name,

      remote: created.remote,

      commit,

      refs: push.refs,

    });

  },

} satisfies ExportedHandler<Env>;


```

In-memory filesystem helper

Use this helper with `isomorphic-git` in Workers when you need a short-lived working tree in memory.

* [  JavaScript ](#tab-panel-4584)
* [  TypeScript ](#tab-panel-4585)

src/memory-fs.js

```

class MemoryStats {

  entry;


  constructor(entry) {

    this.entry = entry;

  }


  get size() {

    return this.entry.kind === "file" ? this.entry.data.byteLength : 0;

  }


  get mtimeMs() {

    return this.entry.mtimeMs;

  }


  get ctimeMs() {

    return this.entry.mtimeMs;

  }


  get mode() {

    return this.entry.kind === "file" ? 0o100644 : 0o040000;

  }


  isFile() {

    return this.entry.kind === "file";

  }


  isDirectory() {

    return this.entry.kind === "dir";

  }


  isSymbolicLink() {

    return false;

  }

}


export class MemoryFS {

  encoder = new TextEncoder();

  decoder = new TextDecoder();

  entries = new Map([

    ["/", { kind: "dir", children: new Set(), mtimeMs: Date.now() }],

  ]);


  promises = {

    readFile: this.readFile.bind(this),

    writeFile: this.writeFile.bind(this),

    unlink: this.unlink.bind(this),

    readdir: this.readdir.bind(this),

    mkdir: this.mkdir.bind(this),

    rmdir: this.rmdir.bind(this),

    stat: this.stat.bind(this),

    lstat: this.lstat.bind(this),

  };


  normalize(input) {

    const segments = [];


    for (const part of input.split("/")) {

      if (!part || part === ".") {

        continue;

      }


      if (part === "..") {

        segments.pop();

        continue;

      }


      segments.push(part);

    }


    return `/${segments.join("/")}` || "/";

  }


  parent(path) {

    const normalized = this.normalize(path);

    if (normalized === "/") {

      return "/";

    }


    const parts = normalized.split("/").filter(Boolean);

    parts.pop();

    return parts.length ? `/${parts.join("/")}` : "/";

  }


  basename(path) {

    return this.normalize(path).split("/").filter(Boolean).pop() ?? "";

  }


  getEntry(path) {

    return this.entries.get(this.normalize(path));

  }


  requireEntry(path) {

    const entry = this.getEntry(path);

    if (!entry) {

      throw new Error(`ENOENT: ${path}`);

    }


    return entry;

  }


  requireDir(path) {

    const entry = this.requireEntry(path);

    if (entry.kind !== "dir") {

      throw new Error(`ENOTDIR: ${path}`);

    }


    return entry;

  }


  async mkdir(path, options) {

    const target = this.normalize(path);

    if (target === "/") {

      return;

    }


    const recursive =

      typeof options === "object" && options !== null && options.recursive;

    const parent = this.parent(target);


    if (!this.entries.has(parent)) {

      if (!recursive) {

        throw new Error(`ENOENT: ${parent}`);

      }


      await this.mkdir(parent, { recursive: true });

    }


    if (this.entries.has(target)) {

      return;

    }


    this.entries.set(target, {

      kind: "dir",

      children: new Set(),

      mtimeMs: Date.now(),

    });


    this.requireDir(parent).children.add(this.basename(target));

  }


  async writeFile(path, data) {

    const target = this.normalize(path);

    await this.mkdir(this.parent(target), { recursive: true });


    const bytes =

      typeof data === "string"

        ? this.encoder.encode(data)

        : data instanceof Uint8Array

          ? data

          : new Uint8Array(data);


    this.entries.set(target, {

      kind: "file",

      data: bytes,

      mtimeMs: Date.now(),

    });


    this.requireDir(this.parent(target)).children.add(this.basename(target));

  }


  async readFile(path, options) {

    const entry = this.requireEntry(path);

    if (entry.kind !== "file") {

      throw new Error(`EISDIR: ${path}`);

    }


    const encoding = typeof options === "string" ? options : options?.encoding;

    return encoding ? this.decoder.decode(entry.data) : entry.data;

  }


  async readdir(path) {

    return [...this.requireDir(path).children].sort();

  }


  async unlink(path) {

    const target = this.normalize(path);

    const entry = this.requireEntry(target);

    if (entry.kind !== "file") {

      throw new Error(`EISDIR: ${path}`);

    }


    this.entries.delete(target);

    this.requireDir(this.parent(target)).children.delete(this.basename(target));

  }


  async rmdir(path) {

    const target = this.normalize(path);

    const entry = this.requireDir(target);

    if (entry.children.size > 0) {

      throw new Error(`ENOTEMPTY: ${path}`);

    }


    this.entries.delete(target);

    this.requireDir(this.parent(target)).children.delete(this.basename(target));

  }


  async stat(path) {

    return new MemoryStats(this.requireEntry(path));

  }


  async lstat(path) {

    return this.stat(path);

  }

}


```

src/memory-fs.ts

```

type Entry =

  | {

      kind: "dir";

      children: Set<string>;

      mtimeMs: number;

    }

  | {

      kind: "file";

      data: Uint8Array;

      mtimeMs: number;

    };


class MemoryStats {

  entry: Entry;


  constructor(entry: Entry) {

    this.entry = entry;

  }


  get size() {

    return this.entry.kind === "file" ? this.entry.data.byteLength : 0;

  }


  get mtimeMs() {

    return this.entry.mtimeMs;

  }


  get ctimeMs() {

    return this.entry.mtimeMs;

  }


  get mode() {

    return this.entry.kind === "file" ? 0o100644 : 0o040000;

  }


  isFile() {

    return this.entry.kind === "file";

  }


  isDirectory() {

    return this.entry.kind === "dir";

  }


  isSymbolicLink() {

    return false;

  }

}


export class MemoryFS {

  encoder = new TextEncoder();

  decoder = new TextDecoder();

  entries = new Map<string, Entry>([

    ["/", { kind: "dir", children: new Set(), mtimeMs: Date.now() }],

  ]);


  promises = {

    readFile: this.readFile.bind(this),

    writeFile: this.writeFile.bind(this),

    unlink: this.unlink.bind(this),

    readdir: this.readdir.bind(this),

    mkdir: this.mkdir.bind(this),

    rmdir: this.rmdir.bind(this),

    stat: this.stat.bind(this),

    lstat: this.lstat.bind(this),

  };


  normalize(input: string) {

    const segments: string[] = [];


    for (const part of input.split("/")) {

      if (!part || part === ".") {

        continue;

      }


      if (part === "..") {

        segments.pop();

        continue;

      }


      segments.push(part);

    }


    return `/${segments.join("/")}` || "/";

  }


  parent(path: string) {

    const normalized = this.normalize(path);

    if (normalized === "/") {

      return "/";

    }


    const parts = normalized.split("/").filter(Boolean);

    parts.pop();

    return parts.length ? `/${parts.join("/")}` : "/";

  }


  basename(path: string) {

    return this.normalize(path).split("/").filter(Boolean).pop() ?? "";

  }


  getEntry(path: string) {

    return this.entries.get(this.normalize(path));

  }


  requireEntry(path: string) {

    const entry = this.getEntry(path);

    if (!entry) {

      throw new Error(`ENOENT: ${path}`);

    }


    return entry;

  }


  requireDir(path: string) {

    const entry = this.requireEntry(path);

    if (entry.kind !== "dir") {

      throw new Error(`ENOTDIR: ${path}`);

    }


    return entry;

  }


  async mkdir(path: string, options?: { recursive?: boolean } | number) {

    const target = this.normalize(path);

    if (target === "/") {

      return;

    }


    const recursive =

      typeof options === "object" && options !== null && options.recursive;

    const parent = this.parent(target);


    if (!this.entries.has(parent)) {

      if (!recursive) {

        throw new Error(`ENOENT: ${parent}`);

      }


      await this.mkdir(parent, { recursive: true });

    }


    if (this.entries.has(target)) {

      return;

    }


    this.entries.set(target, {

      kind: "dir",

      children: new Set(),

      mtimeMs: Date.now(),

    });


    this.requireDir(parent).children.add(this.basename(target));

  }


  async writeFile(path: string, data: string | Uint8Array | ArrayBuffer) {

    const target = this.normalize(path);

    await this.mkdir(this.parent(target), { recursive: true });


    const bytes =

      typeof data === "string"

        ? this.encoder.encode(data)

        : data instanceof Uint8Array

          ? data

          : new Uint8Array(data);


    this.entries.set(target, {

      kind: "file",

      data: bytes,

      mtimeMs: Date.now(),

    });


    this.requireDir(this.parent(target)).children.add(this.basename(target));

  }


  async readFile(path: string, options?: string | { encoding?: string }) {

    const entry = this.requireEntry(path);

    if (entry.kind !== "file") {

      throw new Error(`EISDIR: ${path}`);

    }


    const encoding = typeof options === "string" ? options : options?.encoding;

    return encoding ? this.decoder.decode(entry.data) : entry.data;

  }


  async readdir(path: string) {

    return [...this.requireDir(path).children].sort();

  }


  async unlink(path: string) {

    const target = this.normalize(path);

    const entry = this.requireEntry(target);

    if (entry.kind !== "file") {

      throw new Error(`EISDIR: ${path}`);

    }


    this.entries.delete(target);

    this.requireDir(this.parent(target)).children.delete(this.basename(target));

  }


  async rmdir(path: string) {

    const target = this.normalize(path);

    const entry = this.requireDir(target);

    if (entry.children.size > 0) {

      throw new Error(`ENOTEMPTY: ${path}`);

    }


    this.entries.delete(target);

    this.requireDir(this.parent(target)).children.delete(this.basename(target));

  }


  async stat(path: string) {

    return new MemoryStats(this.requireEntry(path));

  }


  async lstat(path: string) {

    return this.stat(path);

  }

}


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/examples/isomorphic-git/","name":"isomorphic-git"}}]}
```
