npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@napi-rs/simple-git

v1.1.0

Published

![https://github.com/Brooooooklyn/simple-git/actions](https://github.com/Brooooooklyn/simple-git/workflows/CI/badge.svg) ![](https://img.shields.io/npm/dm/@napi-rs/simple-git.svg?sanitize=true) [![Install size](https://packagephobia.com/badge?p=@napi-rs/s

Readme

@napi-rs/simple-git

https://github.com/Brooooooklyn/simple-git/actions Install size

Repository

Usage

import { Repository, BranchType, RemoteCallbacks, PushOptions } from '@napi-rs/simple-git'

Repository.init('/path/to/repo') // init a git repository

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

// Async version of `getFileLatestModifiedDate`, also a `number` (rejects if no history).
const lastModifiedAsync = await repo.getFileLatestModifiedDateAsync('build.rs')
console.log(lastModifiedAsync) // 1647175667920

// 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

// Enriched metadata for the last commit that touched a file.
// Returns `null` (does **not** throw) when the path has no commit history.
const mod = repo.getFileLatestModified('build.rs')
if (mod) {
  console.log(mod.authorName, mod.authorEmail) // 'LongYinan' '[email protected]'
  console.log(mod.committerTime) // a `Date`, identical to getFileLastModifiedDate('build.rs')
  console.log(mod.commitId, mod.summary)
}

// 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([])) // {}

// ---- 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

// ---- 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' '[email protected]'

// ---- 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
console.log(repo.blob(Buffer.from('hello'))) // write a blob straight to the ODB => OID

// ---- Branches ----
const tip = repo.findCommit(parent)!
const branch = repo.branch('feature', tip, false) // => Branch
console.log(branch.name(), branch.isHead()) // 'feature' false
for (const b of repo.branches(BranchType.Local)) console.log(b.name())
repo.findBranch('feature', BranchType.Local)?.delete()

// ---- Checkout / HEAD / refs ----
repo.checkoutTree(tip.asObject(), { force: true }) // update workdir to a tree-ish
repo.setHead('refs/heads/feature') // move HEAD (without touching the workdir)
repo.checkoutHead({ force: true }) // sync the workdir to HEAD
repo.setHeadDetached(tip.id()) // detach HEAD at a commit
repo.reference('refs/heads/tmp', tip.id(), true, 'create tmp') // a direct ref
repo.referenceSymbolic('refs/heads/alias', 'refs/heads/main', true, 'alias main')

// ---- 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 ----
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))

// ---- Async (off-main-thread) network + commit ----
// Clone, commit, fetch and push also have async variants that run their git
// work on a worker thread and return a Promise. `commitAsync` mirrors `commit`.
const cloned = await Repository.cloneAsync('https://example.com/repo.git', '/tmp/clone')
const asyncOid = await repo.commitAsync('HEAD', sig, sig, 'async commit', tree, [parent])
// fetchAsync / pushAsync accept data-only Fetch/Push options. They do NOT accept
// RemoteCallbacks (JS callbacks can't run off the main thread) — use the sync
// fetch()/push() when you need credential or progress callbacks.
await remote.fetchAsync(['refs/heads/main:refs/remotes/origin/main'])
await remote.pushAsync(['refs/heads/main'], new PushOptions().packbuilderParallelism(1))

// ---- Tags & diff ----
// tagForeach hands the callback a single { id, nameBytes } object per tag.
repo.tagForeach(({ id, nameBytes }) => {
  console.log(id, nameBytes.toString('utf8')) // '<40-hex>' 'refs/tags/v1.0.0'
  return true // return false to stop iterating
})
// DiffOptions.showUnmodified pulls unmodified files into the diff so they show
// up in `deltas()` (with an Unmodified status) instead of being skipped.
const headTree = repo.head().peelToTree()
repo.diffTreeToWorkdir(headTree, { showUnmodified: true })

API

export class Repository {
  static init(p: string): Repository
  constructor(gitDir: string)
  /**
   * Asynchronous variant of `clone`, performed off the main thread. Resolves
   * with a ready-to-use `Repository` once the clone completes.
   */
  static cloneAsync(url: string, path: string, signal?: AbortSignal | undefined | null): Promise<Repository>
  /** Retrieve and resolve the reference pointed at by HEAD. */
  head(): Reference
  /**
   * Last-modified commit time of `filepath` in milliseconds since the Unix
   * epoch. Throws when no commit in history touched the path. For a
   * `null`-on-missing `Date`, use `getFileLastModifiedDate`.
   */
  getFileLatestModifiedDate(filepath: string): number
  getFileLatestModifiedDateAsync(filepath: string, signal?: AbortSignal | undefined | null): Promise<number>
  /**
   * Last-modified commit time of `filepath` as a `Date`, or `null` when no
   * commit in history touched the path (never throws for the missing case).
   */
  getFileLastModifiedDate(filepath: string): Date | null
  getFileLastModifiedDateAsync(filepath: string, signal?: AbortSignal | undefined | null): Promise<Date | null>
  /**
   * Last commit that modified `filepath`, with author/committer identity.
   * Returns `null` when no commit in history touched the path.
   */
  getFileLatestModified(filepath: string): FileModification | null
  getFileLatestModifiedAsync(filepath: string, signal?: AbortSignal | undefined | null): Promise<FileModification | null>
  /**
   * Resolve the last commit that modified each of `filepaths` in a single
   * history walk. Every input path is a key; never-committed paths map to `null`.
   */
  getFilesLatestModified(filepaths: Array<string>): Record<string, FileModification | null>
  getFilesLatestModifiedAsync(filepaths: Array<string>, signal?: AbortSignal | undefined | null): Promise<Record<string, FileModification | null>>
  /** Repository config view (system + global + repo, prioritized). */
  config(): Config
  /** Default signature built from `user.name` / `user.email`. */
  signature(): Signature
  /** List the branches in the repository, optionally filtered by type. */
  branches(filter?: BranchType | undefined | null): Array<Branch>
  /** Lookup a branch by name and type; `null` when it does not exist. */
  findBranch(name: string, branchType: BranchType): Branch | null
  /** Create a new branch pointing at a target commit. */
  branch(branchName: string, target: Commit, force: boolean): Branch
  /** Check out a tree-ish into the working directory (does not move HEAD). */
  checkoutTree(treeish: GitObject, options?: CheckoutOptions | undefined | null): void
  /** Update the index and the working tree to match HEAD. */
  checkoutHead(options?: CheckoutOptions | undefined | null): void
  /** Update the working tree to match the index. */
  checkoutIndex(options?: CheckoutOptions | undefined | null): void
  /** Make HEAD point to the reference named `refname`. */
  setHead(refname: string): void
  /** Detach HEAD directly at the commit with the given OID. */
  setHeadDetached(oid: string): void
  /** Create a new direct reference named `name` pointing at `oid`. */
  reference(name: string, oid: string, force: boolean, logMessage: string): Reference
  /** Create a new symbolic reference named `name` pointing at `target`. */
  referenceSymbolic(name: string, target: string, force: boolean, logMessage: string): Reference
  /** Create a new commit; `parents` is an optional list of parent OID hex strings. */
  commit(updateRef: string | undefined | null, author: Signature, committer: Signature, message: string, tree: Tree, parents?: Array<string> | undefined | null): string
  /** Asynchronous variant of `commit`, performed off the main thread. Resolves with the new commit's OID hex. */
  commitAsync(updateRef: string | undefined | null, author: Signature, committer: Signature, message: string, tree: Tree, parents?: Array<string> | undefined | null, signal?: AbortSignal | undefined | null): Promise<string>
  /** Get the index (staging area) for this repository. */
  index(): Index
  /** Write an in-memory buffer to the object database as a blob; returns its OID hex. */
  blob(data: Uint8Array): string
  /** Read a file and write its content to the object database as a blob; returns its OID hex. */
  blobPath(path: string): string
  /** List the working-tree and index status of files (like `git status`). */
  statuses(options?: StatusOptions | undefined | null): Array<FileStatus>
  /** Status of a single file by its workdir-relative path. */
  statusFile(path: string): FileStatus
  /** Asynchronous variant of `statuses`, computed off the main thread. */
  statusesAsync(options?: StatusOptions | undefined | null, signal?: AbortSignal | undefined | null): Promise<Array<FileStatus>>
  /** Blame `path`: who last changed each line, as ordered hunks. */
  blameFile(path: string, options?: BlameOptions | undefined | null): Array<BlameHunk>
  /** Blame `path` and return only the hunk covering `lineNo` (1-based), or `null`. */
  blameLine(path: string, lineNo: number, options?: BlameOptions | undefined | null): BlameHunk | null
  /** Asynchronous variant of `blameFile`, computed off the main thread. */
  blameFileAsync(path: string, options?: BlameOptions | undefined | null, signal?: AbortSignal | undefined | null): Promise<Array<BlameHunk>>
  /** Create an annotated tag (and its ref); returns the new tag object's OID. */
  tag(name: string, target: GitObject, tagger: Signature, message: string, force: boolean): string
  /** Create an annotated tag object WITHOUT a ref; returns its OID. */
  tagAnnotation(name: string, target: GitObject, tagger: Signature, message: string): string
  /** Lookup a tag object by OID; `null` when it does not exist. */
  findTag(oid: string): Tag | null
  /** Lookup a tag object by hash prefix; `null` when no tag matches. */
  findTagByPrefix(prefixHash: string): Tag | null
  /** Read libgit2's merge message (`.git/MERGE_MSG`). */
  mergeMessage(): string
  /** Remove the merge message (`.git/MERGE_MSG`). */
  removeMergeMessage(): void
  /** Add a remote with the provided fetch refspec to the configuration. */
  remoteWithFetch(name: string, url: string, refspec: string): Remote
}

/**
 * Last commit that modified a file, with author/committer identity.
 * All times are `Date`s (UTC; timezone offset ignored).
 */
export interface FileModification {
  /** 40-char lowercase hex OID of the last commit that modified the file. */
  commitId: string
  /** Commit summary (first line). Undefined if absent or not valid UTF-8. */
  summary?: string
  /** Author name. Undefined if not valid UTF-8. */
  authorName?: string
  /** Author email. Undefined if not valid UTF-8. */
  authorEmail?: string
  /** Author time, as a `Date`. */
  authorTime: Date
  /** Committer name. Undefined if not valid UTF-8. */
  committerName?: string
  /** Committer email. Undefined if not valid UTF-8. */
  committerEmail?: string
  /** Committer time, as a `Date`. Identical to `getFileLastModifiedDate`. */
  committerTime: Date
  /**
   * The commit that FIRST added this file (its creation), or `undefined` for a
   * glob, or a directory/submodule path at HEAD. A delete-then-re-add returns
   * the ORIGINAL add; merge commits are included; no rename-follow.
   */
  created?: CommitInfo
}

/**
 * A single commit's identity/time metadata, reused for the commit that CREATED
 * a file (`FileModification.created`). Same 8 fields as `FileModification`'s
 * commit metadata. All times are `Date`s (UTC; timezone offset ignored).
 */
export interface CommitInfo {
  /** 40-char lowercase hex OID of the commit that first added the file. */
  commitId: string
  /** Commit summary (first line). Undefined if absent or not valid UTF-8. */
  summary?: string
  /** Author name. Undefined if not valid UTF-8. */
  authorName?: string
  /** Author email. Undefined if not valid UTF-8. */
  authorEmail?: string
  /** Author time, as a `Date`. */
  authorTime: Date
  /** Committer name. Undefined if not valid UTF-8. */
  committerName?: string
  /** Committer email. Undefined if not valid UTF-8. */
  committerEmail?: string
  /** Committer time, as a `Date`. */
  committerTime: Date
}

/**
 * A git configuration store. Obtain one with `repo.config()` (system + global +
 * repository, prioritized) or `Config.openDefault()` (system/global/XDG only).
 */
export class Config {
  /** Open global, XDG and system config into one prioritized object. */
  static openDefault(): Config
  /** Get a string config value (highest-priority occurrence wins). */
  getString(name: string): string
  getBoolean(name: string): boolean
  getNumber(name: string): number
  /** i64 value as a `bigint` (no >2^53 truncation). */
  getBigInt(name: string): bigint
  /** Set a value in the highest-level config file (usually the local one). */
  setString(name: string, value: string): void
  setBoolean(name: string, value: boolean): void
  setNumber(name: string, value: number): void
  /** Set an i64 value from a `bigint`; throws if it doesn't fit in i64. */
  setBigInt(name: string, value: bigint): void
  /** Delete a variable from the highest-level config file. */
  removeEntry(name: string): void
  /** Create a read-only point-in-time snapshot of this configuration. */
  snapshot(): Config
  /** List entries, optionally filtered by a glob pattern. */
  entries(glob?: string | undefined | null): Array<ConfigEntry>
}

/**
 * A single configuration entry: its fully-qualified name, value, and the
 * level (file) it was read from.
 */
export interface ConfigEntry {
  name: string
  value: string
  level: ConfigLevel
}

/**
 * The priority level a configuration entry or file applies to. Higher levels
 * take precedence; `Local` (the repository's own `.git/config`) is where
 * `set_*`/`remove_entry` write by default.
 */
export declare const enum ConfigLevel {
  /** System-wide on Windows, for compatibility with portable git */
  ProgramData = 0,
  /** System-wide configuration file, e.g. /etc/gitconfig */
  System = 1,
  /** XDG-compatible configuration file, e.g. ~/.config/git/config */
  Xdg = 2,
  /** User-specific configuration, e.g. ~/.gitconfig */
  Global = 3,
  /** Repository specific config, e.g. $PWD/.git/config */
  Local = 4,
  /** Worktree specific configuration file, e.g. $GIT_DIR/config.worktree */
  Worktree = 5,
  /** Application specific configuration file */
  App = 6,
  /** Highest level available */
  Highest = 7
}

/**
 * A git index (the staging area). Obtain one with `repo.index()`. Mutating
 * methods change memory only; call `write()` to persist it or `writeTree()` to
 * write its current state to the object database as a tree.
 */
export class Index {
  /** Add or update an index entry from a file on disk. */
  addPath(path: string): void
  /** Add or update entries matching files in the working directory. */
  addAll(pathspecs?: Array<string> | undefined | null, force?: boolean | undefined | null): void
  /** Update all index entries to match the working directory. */
  updateAll(pathspecs?: Array<string> | undefined | null): void
  /** Remove an index entry corresponding to a file on disk. */
  removePath(path: string): void
  /** Get the count of entries currently in the index. */
  size(): number
  /** Write the in-memory index back to disk using an atomic file lock. */
  write(): void
  /** Write the index as a tree to the object database and return its OID. */
  writeTree(): string
}

/**
 * A git branch — a thin wrapper around an underlying reference; the full
 * reference name is available via `referenceName`.
 */
export class Branch {
  /** Name of the local or remote branch (`null` if not valid utf-8). */
  name(): string | null
  /** Whether the current local branch is pointed at by HEAD. */
  isHead(): boolean
  /** Full reference name backing this branch (e.g. `refs/heads/main`). */
  referenceName(): string | null
  /** Delete this branch reference. */
  delete(): void
  /** The configured upstream tracking branch, or `null`. */
  upstream(): Branch | null
  /** The reference backing this branch as a live `Reference`. */
  get(): Reference
}

/** An enumeration for the possible types of branches. */
export declare const enum BranchType {
  /** A local branch not on a remote. */
  Local = 0,
  /** A branch for a remote. */
  Remote = 1
}

export class PushOptions {
  constructor()
  /** Set the callbacks to use for the push operation. */
  remoteCallback(callback: RemoteCallbacks): this
  /** Set the proxy options to use for the push operation. */
  proxyOptions(options: ProxyOptions): this
  /** Number of packbuilder worker threads (0 = auto-detect, default 1). */
  packbuilderParallelism(parallel: number): this
  /** Set remote redirection settings. */
  followRedirects(opt: RemoteRedirect): this
  /** Set extra headers for this push operation. */
  customHeaders(headers: Array<string>): this
  /** Set "push options" to deliver to the remote. */
  remotePushOptions(options: Array<string>): this
}

/**
 * Status of a single file in the working tree and/or index.
 *
 * The boolean flags mirror the `git2::Status` bits; `bits` carries the raw
 * value as a forward-compatible escape hatch for flags not surfaced here.
 */
export interface FileStatus {
  /** Workdir-relative path. Undefined if the path is not valid UTF-8. */
  path?: string
  /** Raw `git2::Status` bits — forward-compat escape hatch. */
  bits: number
  /** Staged: a new file was added to the index. */
  isIndexNew: boolean
  /** Staged: a tracked file was modified in the index. */
  isIndexModified: boolean
  /** Staged: a tracked file was deleted from the index. */
  isIndexDeleted: boolean
  /** Staged: a tracked file was renamed in the index. */
  isIndexRenamed: boolean
  /** Staged: a tracked file changed type in the index. */
  isIndexTypechange: boolean
  /** Unstaged: an untracked file (new in the working directory). */
  isWtNew: boolean
  /** Unstaged: a tracked file was modified in the working directory. */
  isWtModified: boolean
  /** Unstaged: a tracked file was deleted from the working directory. */
  isWtDeleted: boolean
  /** Unstaged: a tracked file changed type in the working directory. */
  isWtTypechange: boolean
  /** Unstaged: a tracked file was renamed in the working directory. */
  isWtRenamed: boolean
  /** The file is ignored. */
  isIgnored: boolean
  /** The file has merge conflicts. */
  isConflicted: boolean
}

/**
 * Options controlling how a working-tree status scan is performed.
 *
 * Every field is optional; omitted fields fall back to the git CLI defaults
 * (`include_untracked` is `true`, everything else `false`).
 */
export interface StatusOptions {
  /** Include untracked files in the status. Defaults to `true`. */
  includeUntracked?: boolean
  /** Include ignored files in the status. Defaults to `false`. */
  includeIgnored?: boolean
  /** Include unmodified files in the status. Defaults to `false`. */
  includeUnmodified?: boolean
  /** Skip submodules. Defaults to `false`. */
  excludeSubmodules?: boolean
  /**
   * Recurse into untracked directories instead of reporting the directory
   * itself. Defaults to `false`.
   */
  recurseUntrackedDirs?: boolean
  /** Detect renames between the HEAD tree and the index. Defaults to `false`. */
  renamesHeadToIndex?: boolean
  /** Detect renames between the index and the working directory. Defaults to `false`. */
  renamesIndexToWorkdir?: boolean
  /** Restrict the scan to the given pathspecs. */
  pathspec?: Array<string>
}

/**
 * A single blame hunk: a contiguous run of lines attributed to one commit.
 *
 * All identity fields are copied out of the borrowed `git2::BlameHunk` so the
 * value can safely outlive the underlying `git2::Blame`.
 */
export interface BlameHunk {
  /** Number of lines covered by this hunk. */
  linesInHunk: number
  /** 40-char lowercase hex OID of the commit where these lines were last changed. */
  finalCommitId: string
  /** Line number where this hunk begins in the final file (1-based). */
  finalStartLine: number
  /** Author name of the final commit. Undefined if absent or not valid UTF-8. */
  finalAuthorName?: string
  /** Author email of the final commit. Undefined if absent or not valid UTF-8. */
  finalAuthorEmail?: string
  /** Author time of the final commit, as a `Date`. The Unix epoch if no signature. */
  finalTime: Date
  /** 40-char lowercase hex OID of the commit where this hunk was found. */
  origCommitId: string
  /** Line number where this hunk begins in the original file (1-based). */
  origStartLine: number
  /** Path to the file where this hunk originated. Undefined if not valid UTF-8. */
  origPath?: string
  /** Whether this hunk was tracked to a boundary commit (root or `oldest_commit`). */
  isBoundary: boolean
}

/**
 * Options controlling how a blame is computed.
 *
 * Every field is optional; omitted fields fall back to libgit2's defaults
 * (no copy tracking, the whole file, starting from the current HEAD).
 */
export interface BlameOptions {
  /**
   * Track lines that have moved within a file. Defaults to `false`.
   *
   * Note: libgit2 1.9.4 does not implement blame copy/move tracking, so setting
   * this flag has no effect today (accepted for forward-compat; effectively a no-op).
   */
  trackCopiesSameFile?: boolean
  /**
   * Track lines that have moved across files in the same commit. Defaults to `false`.
   *
   * Note: libgit2 1.9.4 does not implement blame copy/move tracking, so setting
   * this flag has no effect today (accepted for forward-compat; effectively a no-op).
   */
  trackCopiesSameCommitMoves?: boolean
  /** 40-char hex OID of the newest commit to consider (the blame starts here). */
  newestCommit?: string
  /** 40-char hex OID of the oldest commit to consider (a boundary). */
  oldestCommit?: string
  /** Restrict the search to first-parent history only. Defaults to `false`. */
  firstParent?: boolean
  /** Map names/emails through the repository's mailmap. Defaults to `false`. */
  useMailmap?: boolean
  /** Ignore whitespace differences. Defaults to `false`. */
  ignoreWhitespace?: boolean
  /** The first line in the file to blame (1-based). */
  minLine?: number
  /** The last line in the file to blame (1-based). */
  maxLine?: number
}

/**
 * Options controlling how a checkout writes files into the working directory.
 *
 * The default is a **safe** checkout (matching `git checkout`): files with
 * local modifications are left untouched. Set `force` to overwrite them, which
 * can discard uncommitted changes — use it deliberately.
 */
export interface CheckoutOptions {
  /**
   * Force the checkout, overwriting any local changes in the working tree.
   * Defaults to a safe checkout when omitted or `false`.
   */
  force?: boolean
  /**
   * Recreate files that are missing from the working tree even in a safe
   * checkout.
   */
  recreateMissing?: boolean
  /** Allow the checkout to write files that conflict with the working tree. */
  allowConflicts?: boolean
  /**
   * Restrict the checkout to these pathspecs. When omitted, all paths are
   * checked out.
   */
  paths?: Array<string>
  /**
   * Write the checked-out files into this directory instead of the
   * repository's working directory.
   */
  targetDir?: string
}

Disposal

dispose() — and its alias free() — eagerly releases the native git2 handle without waiting for garbage collection; both are idempotent. After disposal every throwing method throws "Repository has been disposed" (the Option-returning lookups return null instead), and every handle derived from the repo (Remote, Tree, Commit, Reference, RevWalk, …) throws the same error on use — whether it is the receiver or passed as an argument to another method (machine-enforced, not merely documented). Disposal does NOT cancel already-scheduled *Async operations: they reopen the repo on their own worker thread and run to completion. To cancel a pending async op, pass an AbortSignal to the *Async method rather than relying on dispose().

Reference

Usage

import { Repository } from '@napi-rs/simple-git'

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

const headReference = repo.head()

headReference.shorthand() // 'main'
headReference.name() // 'refs/heads/main'
headReference.target() // 7a1256e2f847f395219980bc06c6dadf0148f18d

API

/** An enumeration of all possible kinds of references. */
export const enum ReferenceType {
  /** A reference which points at an object id. */
  Direct = 0,
  /** A reference which points at another reference. */
  Symbolic = 1,
  Unknown = 2
}
export class Reference {
  /**
   * Ensure the reference name is well-formed.
   *
   * Validation is performed as if [`ReferenceFormat::ALLOW_ONELEVEL`]
   * was given to [`Reference.normalize_name`]. No normalization is
   * performed, however.
   *
   * ```ts
   * import { Reference } from '@napi-rs/simple-git'
   *
   * console.assert(Reference.isValidName("HEAD"));
   * console.assert(Reference.isValidName("refs/heads/main"));
   *
   * // But:
   * console.assert(!Reference.isValidName("main"));
   * console.assert(!Reference.isValidName("refs/heads/*"));
   * console.assert(!Reference.isValidName("foo//bar"));
   * ```
   */
  static isValidName(name: string): boolean
  /** Check if a reference is a local branch. */
  isBranch(): boolean
  /** Check if a reference is a note. */
  isNote(): boolean
  /** Check if a reference is a remote tracking branch */
  isRemote(): boolean
  /** Check if a reference is a tag */
  isTag(): boolean
  kind(): ReferenceType
  /**
   * Get the full name of a reference.
   *
   * Returns `None` if the name is not valid utf-8.
   */
  name(): string | null
  /**
   * Get the full shorthand of a reference.
   *
   * This will transform the reference name into a name "human-readable"
   * version. If no shortname is appropriate, it will return the full name.
   *
   * Returns `None` if the shorthand is not valid utf-8.
   */
  shorthand(): string | null
  /**
   * Get the OID pointed to by a direct reference.
   *
   * Only available if the reference is direct (i.e. an object id reference,
   * not a symbolic one).
   */
  target(): string | null
  /**
   * Return the peeled OID target of this reference.
   *
   * This peeled OID only applies to direct references that point to a hard
   * Tag object: it is the result of peeling such Tag.
   */
  targetPeel(): string | null
  /**
   * Get full name to the reference pointed to by a symbolic reference.
   *
   * May return `None` if the reference is either not symbolic or not a
   * valid utf-8 string.
   */
  symbolicTarget(): string | null
}

Error handling

Every error thrown by this library — from a synchronous method or from a rejected *Async promise — is a standard Error carrying a code string property drawn from a fixed union of tokens. The tokens are exported as the GitErrorCode const enum, and any caught value can be narrowed to a coded error with the isGitError(e) type guard:

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
  }
}

isGitError(e) returns true only when e is a genuine Error object whose code is a real member of the GitErrorCode enum; it narrows e to Error & { code: GitErrorCode } in TypeScript. The Error check is the native Node-API napi_is_error test (not a JS-level instanceof), so it recognizes cross-realm and subclassed errors while rejecting look-alike proxies and plain objects. Membership is validated against the enum (the single source of truth), so a non-git Error that merely exposes some other string .code (e.g. Node's ENOENT) returns false. The guard is total — it never throws for any input, even a hostile value with a throwing proxy trap or Symbol.hasInstance, or an Error whose code is a throwing getter (all return false) — so it is always safe to call inside a catch.

The 29 members of GitErrorCode are the 28 libgit2 error classes plus one napi-level token:

| Token | Meaning | | --- | --- | | GenericError | Unclassified catch-all. Any unmapped or future libgit2 code collapses here — treat it as "something went wrong" rather than a specific class. | | NotFound | A requested object/reference/config entry does not exist. | | Exists | The object already exists and cannot be overwritten. | | Ambiguous | A short OID or name matched more than one object. | | BufSize | An output buffer was too small. | | User | A user-supplied callback returned an error. | | BareRepo | The operation is not allowed on a bare repository. | | UnbornBranch | HEAD points at a branch with no commits yet. | | Unmerged | The index contains unmerged entries. | | NotFastForward | A reference update was rejected because it was not fast-forward. | | InvalidSpec | A refspec/name is not well-formed. | | Conflict | A checkout/merge conflict prevents the operation. | | Locked | The resource is locked by another operation. | | Modified | The reference/object was modified concurrently. | | Auth | Authentication failed. | | Certificate | The server TLS certificate was rejected. | | Applied | The patch/commit was already applied. | | Peel | An object could not be peeled to the requested type. | | Eof | Unexpected end of file/stream. | | Invalid | An invalid operation or input for the current state. | | Uncommitted | Uncommitted changes prevent the operation. | | Directory | The operation is not valid on a directory. | | MergeConflict | A merge produced conflicts. | | HashsumMismatch | A hash verification failed. | | IndexDirty | The index has unsaved changes. | | ApplyFail | A patch failed to apply. | | Owner | The repository is owned by an unexpected user. | | Timeout | The operation timed out. | | InvalidArg | napi-level argument validation / API misuse (e.g. an option object reused across two *Async calls, or RemoteCallbacks passed to fetchAsync/pushAsync). Not a libgit2 class. |

Synchronous methods and *Async promise rejections expose the same GitErrorCode token set, so you can branch on it either way:

import { isGitError, GitErrorCode } from '@napi-rs/simple-git'

// Synchronous
try {
  repo.findRemote('does-not-exist')
} catch (e) {
  if (isGitError(e) && e.code === GitErrorCode.NotFound) {
    // handle the missing remote
  }
}

// Asynchronous
await repo.getFileLatestModifiedDateAsync('build.rs').catch((e) => {
  if (isGitError(e) && e.code === GitErrorCode.GenericError) {
    // unclassified failure
  }
})

Cancellation is different. *Async methods that accept an AbortSignal reject an aborted call with napi's own AbortError, whose .code === "Cancelled". That is napi's runtime cancellation code — it is not a GitErrorCode member (it comes from napi, not the git layer), because cancellation bypasses the task's own reject hook. Because "Cancelled" is not a GitErrorCode member, isGitError returns false for it. Detect an aborted call separately — via the AbortSignal's aborted/abort event or by checking e.code === "Cancelled" — rather than through isGitError.

Performance

Compared with the exec function, which gets the file's latest modified date by spawning a child process. Getting the latest modified date from the file 1000 times:

Child process took 1.9s
@napi-rs/simple-git took 65ms