Skip to content
NATIVE NODE ADDON · POWERED BY RUST · libgit2

Git for Node, at native speed.

Open, inspect, stage, commit, blame, branch and push real repositories through libgit2 — no git shell-out, with JS-native Date / number / Buffer types.

$npm install @napi-rs/simple-git
last-updated.ts
import { Repository, BranchType, RemoteCallbacks, PushOptions } from '@napi-rs/simple-git'

const repo = new Repository('/path/to/repo') // Open an existed repo

// Last-modified commit time of `build.rs` in milliseconds since the Unix epoch.
// Returns a `number`; throws when no commit in history touched the path.
const lastModified = repo.getFileLatestModifiedDate('build.rs')
console.log(new Date(lastModified)) // 2022-03-13T12:47:47.920Z

// Null-safe alternative: a `Date`, or `null` (never throws) when no commit ever
// touched the path.
const lastModifiedDate = repo.getFileLastModifiedDate('build.rs')
if (lastModifiedDate) console.log(lastModifiedDate) // 2022-03-13T12:47:47.920Z
29×
faster
15
platforms
0
dependencies
01 — BENCHMARK

~30× faster than shelling out

Reading getFileLatestModifiedDate 1,000× — the native call versus spawning the git CLI as a child process.

speedup

29×

1,000 reads drop from 1.9 s of child processes to 65 ms in-process.

git CLI (child_process)1.9 s
@napi-rs/simple-git65 ms

Reading getFileLatestModifiedDate 1000× vs spawning the git CLI as a child process (source: README Performance · performance.mjs).

02 — WHY

Powers ‘Last updated on’ for your docs

Doc generators compute 'last updated' per page from git history. @napi-rs/simple-git does it in-process — fast, with no child_process per file.

Last updated on Jul 6, 2026 by LongYinan
last-updated.ts
const repo = new Repository('/path/to/repo') // Open an existed repo

// Bulk: resolve many files in a single history walk (early-exits once all are found).
// Every input path is present as a key; a never-committed path maps to `null`.
const mods = repo.getFilesLatestModified(['build.rs', 'Cargo.toml'])
console.log(mods['build.rs']?.committerName)
console.log(mods['Cargo.toml']?.committerTime) // a `Date`
// Empty input returns `{}`:
console.log(repo.getFilesLatestModified([])) // {}
03 — FEATURES

A full Git toolbox

Native libgit2 speed

Talks to Git through compiled Rust/libgit2, not a child-process git shell-out.

"Last updated" dates for docs

getFileLatestModifiedDate / getFileLastModifiedDate / getFilesLatestModified power "last updated on" stamps in Nextra, Docusaurus, Starlight, Fumadocs, Rspress.

Full repository toolbox

Init, open, discover, clone; status, stage, commit, blame, diff, branch, checkout, tag, refs, remotes, revwalk — one cohesive Repository API.

Off-main-thread async

cloneAsync, commitAsync, fetchAsync, pushAsync, plus async status/blame/file-date variants, each accepting an AbortSignal.

First-class TypeScript

Hand-annotated index.d.ts with JS-native types: Date, number, Buffer instead of raw libgit2 primitives.

Typed, catchable errors

Git-layer failures carry a stable GitErrorCode, narrowable with the total, never-throwing isGitError() guard; an aborted async call rejects with napi's AbortError instead.

Prebuilt for 15 platforms

Windows (x64/x86/ARM64), macOS (Intel + Apple Silicon), Linux glibc & musl, Android, FreeBSD — no compiler needed.

Push/fetch with real creds & progress

SSH-agent, SSH-key, userpass via Cred; transfer + per-ref update callbacks via RemoteCallbacks.

Deterministic cleanup

dispose() / free() release native handles eagerly (frees Windows packfile fds); using supported.

04 — API

Show me the code

Status
status.ts
// ---- Working-tree status (like `git status`) ----
const changes = repo.statuses() // => FileStatus[]
for (const file of changes) {
  console.log(file.path, file.isWtModified, file.isIndexNew)
}
console.log(repo.statusFile('README.md').isWtModified) // status of a single path
const scanned = await repo.statusesAsync({ includeIgnored: true }) // off-thread scan
Stage & commit
commit.ts
// ---- Config + default signature ----
const config = repo.config() // => Config (system + global + repo, prioritized)
config.setString('user.name', 'LongYinan')
console.log(config.getString('user.name')) // 'LongYinan'
console.log(config.getBoolean('core.bare')) // false
const sig = repo.signature() // built from user.name / user.email
console.log(sig.name(), sig.email()) // 'LongYinan' 'github@lyn.one'

// ---- Stage from the working tree and commit ----
const index = repo.index() // => Index (the staging area)
index.addPath('file.txt')
index.write()
const treeOid = index.writeTree() // OID of the staged tree
const tree = repo.findTree(treeOid)!
const parent = repo.head().target()! // current tip OID
const commitId = repo.commit('HEAD', sig, sig, 'commit from workdir', tree, [parent])
console.log(commitId) // 40-char hex OID
Blame
blame.ts
// ---- Blame ----
for (const hunk of repo.blameFile('build.rs')) {
  console.log(hunk.finalStartLine, hunk.linesInHunk, hunk.finalCommitId, hunk.finalAuthorName)
}
console.log(repo.blameLine('build.rs', 10)?.finalAuthorName) // hunk for line 10, or null
Push
push.ts
// ---- Push ----
const remote = repo.findRemote('origin')!
const callbacks = new RemoteCallbacks()
  // Per-ref result: one object per updated reference.
  .pushUpdateReference(({ refname, status }) => {
    console.log(refname, status) // 'refs/heads/main' null   (null === accepted)
  })
  // Pack-transfer progress: a single PushTransferProgress object.
  .pushTransferProgress(({ current, total, bytes }) => {
    console.log(`${current}/${total} objects, ${bytes} bytes`)
  })
remote.push(['refs/heads/main'], new PushOptions().remoteCallback(callbacks))
Typed errors
errors.ts
import { isGitError, GitErrorCode } from '@napi-rs/simple-git'

try {
  // …some git operation…
} catch (e) {
  if (isGitError(e) && e.code === GitErrorCode.NotFound) {
    // handle the missing object/reference/config entry
  }
}
05 — PLATFORMS

Prebuilt everywhere

npm install pulls a ready binary — no compiler, no node-gyp.

Windows

3
  • x64x86_64-pc-windows-msvc
  • x86i686-pc-windows-msvc
  • ARM64aarch64-pc-windows-msvc

macOS

2
  • Intelx86_64-apple-darwin
  • Apple Siliconaarch64-apple-darwin

Linux (glibc)

5
  • x64x86_64-unknown-linux-gnu
  • ARM64aarch64-unknown-linux-gnu
  • ARMv7armv7-unknown-linux-gnueabihf
  • ppc64lepowerpc64le-unknown-linux-gnu
  • s390xs390x-unknown-linux-gnu

Linux (musl)

2
  • x64x86_64-unknown-linux-musl
  • ARM64aarch64-unknown-linux-musl

Android

2
  • ARM64aarch64-linux-android
  • ARMv7armv7-linux-androideabi

FreeBSD

1
  • x64x86_64-unknown-freebsd

15 prebuilt triples · Node >= 10

GET STARTED

Add it in one line

$npm install @napi-rs/simple-git