
Security News
/Company News
Socket Is Sponsoring Composer and Packagist
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.
@napi-rs/simple-git
Advanced tools
  [ // 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' 'github@lyn.one'
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' '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
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 })
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
}
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().
Referenceimport { 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
/** 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
}
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.
*Asyncmethods that accept anAbortSignalreject an aborted call with napi's ownAbortError, whose.code === "Cancelled". That is napi's runtime cancellation code — it is not aGitErrorCodemember (it comes from napi, not the git layer), because cancellation bypasses the task's own reject hook. Because"Cancelled"is not aGitErrorCodemember,isGitErrorreturnsfalsefor it. Detect an aborted call separately — via theAbortSignal'saborted/abortevent or by checkinge.code === "Cancelled"— rather than throughisGitError.
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
FAQs
  [
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
/Company News
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.

Research
/Security News
Benign-looking npm packages split malicious functionality across a dependency chain that deploys a cross-platform RAT targeting Alibaba developers.

Research
/Security News
Two Joyfill npm beta releases contain an import-time implant that uses blockchain transactions to retrieve a remote-access trojan.