simple-git
semver
>=3.32.3postconditions17functions17last verified2026-04-17coverage score81%Postconditions — what we check
- push · simple-git-push-missing-try-catcherrorWhenasync function calls git.push() or this.git.push() without wrapping in try-catch. Throws GitError on network failures, SSH/HTTPS authentication failures, remote rejection (non-fast-forward), or permission denied.Throws
GitError — base error class exported from simple-git. Has .message (git stderr output), .stack. Remote push errors include: "Permission denied (publickey)", "remote rejected", "rejected (fetch first)", "could not read Username", "fatal: repository not found".Required handlingCaller MUST wrap git.push() in try-catch and handle GitError. Minimum handling: try { await git.push('origin', branch); } catch (err) { console.error('Git push failed:', err.message); throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - pull · simple-git-pull-missing-try-catcherrorWhenasync function calls git.pull() without wrapping in try-catch. Throws GitError on network failures or auth failures. Throws GitResponseError on merge conflicts during pull.Throws
GitError — on network/auth failures. GitResponseError<PullResult> — on merge conflicts, has .git property with conflict details.Required handlingCaller MUST wrap git.pull() in try-catch. Minimum handling: try { await git.pull('origin', branch); } catch (err) { console.error('Git pull failed:', err.message); throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - clone · simple-git-clone-missing-try-catcherrorWhenasync function calls git.clone() without wrapping in try-catch. Throws GitError on network failures, auth failures, invalid repository URL, or destination path permission errors.Throws
GitError — on network/auth/path failures. Common messages: "Repository not found", "Permission denied (publickey)", "fatal: destination path already exists", "could not read Username".Required handlingCaller MUST wrap git.clone() in try-catch. Minimum handling: try { await git.clone(repoUrl, localPath); } catch (err) { console.error('Git clone failed:', err.message); throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - merge · simple-git-merge-missing-try-catcherrorWhenasync function calls git.merge() without wrapping in try-catch. Throws GitResponseError<MergeSummary> on merge conflicts — EVEN WHEN the underlying git process exits with code 0. The .git.conflicts[] array contains the conflicting file paths.Throws
GitResponseError<MergeSummary> — on merge conflicts, has .git.conflicts (string[]) and .git.failed (boolean). Note: git exits 0 but simple-git still throws. GitError — on other merge failures (invalid ref, etc.).Required handlingCaller MUST wrap git.merge() in try-catch and handle merge conflicts. Minimum handling: try { await git.merge(['sourceBranch', '--no-edit']); } catch (err) { if (err.git?.conflicts?.length > 0) { console.error('Merge conflicts:', err.git.conflicts); await git.merge(['--abort']); } throw err; }costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - fetch · simple-git-fetch-missing-try-catcherrorWhenasync function calls git.fetch() or git.fetch(remote, branch) without wrapping in try-catch. Throws GitError on network failure, SSH/HTTPS authentication failure, remote not found, or git binary not installed. Common in background sync workers that run fetch periodically.Throws
GitError — on any failure: network error (ECONNREFUSED, ETIMEDOUT), SSH auth failure ("Permission denied (publickey)"), HTTPS auth failure ("remote: Repository not found", "could not read Username for ..."), remote reference not found ("couldn't find remote ref <branch>"), or git not installed ("ENOENT: no such file or directory, git").Required handlingCaller MUST wrap git.fetch() in try-catch. Minimum handling: try { await git.fetch('origin', 'main'); } catch (err) { if (err instanceof GitError) { console.error('Fetch failed:', err.message); } throw err; } For background sync workers, consider retry with exponential backoff on network errors, but treat auth errors as non-retryable alerts.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - commit · simple-git-commit-missing-try-catcherrorWhenasync function calls git.commit() without wrapping in try-catch. Common failure in CI/CD automation scripts and git-sync workers: if there are no staged changes, git exits non-zero and GitError is thrown. Also fails when git identity (user.name, user.email) is not configured in the environment — common in Docker containers and CI runners.Throws
GitError — on any failure: "nothing to commit" (exit code 1 when nothing staged), "Please tell me who you are" (user.name / user.email not configured), "Aborting commit due to empty commit message" (empty message string), pre-commit hook failure (non-zero exit from .git/hooks/pre-commit).Required handlingCaller MUST wrap git.commit() in try-catch. Pattern for idempotent commit workflows: try { const result = await git.commit('chore: automated update'); if (result.commit) { console.log('Committed:', result.commit); } } catch (err) { if (err instanceof GitError && err.message.includes('nothing to commit')) { console.log('Nothing to commit, skipping'); return; // not a fatal error } throw err; // rethrow unexpected errors } Also configure git identity before committing in CI: await git.addConfig('user.name', 'Bot'); await git.addConfig('user.email', 'bot@example.com');costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - checkout · simple-git-checkout-missing-try-catcherrorWhenasync function calls git.checkout(), git.checkoutBranch(), or git.checkoutLocalBranch() without wrapping in try-catch. Fails when working tree has uncommitted changes that would be overwritten, when the ref/branch does not exist, or when there are file permission issues in the working directory.Throws
GitError — on any failure: "Your local changes to the following files would be overwritten by checkout" (dirty tree), "error: pathspec '...' did not match any file(s) known to git" (ref not found), "A branch named '...' already exists" (checkoutLocalBranch / checkoutBranch on existing), "fatal: reference is not a tree" (invalid commit hash).Required handlingCaller MUST wrap git.checkout() in try-catch. Minimum handling: try { await git.checkout(targetBranch); } catch (err) { if (err instanceof GitError) { if (err.message.includes('would be overwritten')) { // Stash first or abort console.error('Dirty working tree, cannot checkout:', err.message); } } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - rebase · simple-git-rebase-missing-try-catcherrorWhenasync function calls git.rebase() without wrapping in try-catch. Rebase conflicts leave the repo in a partially-rebased state (detached HEAD, .git/rebase-merge/ directory exists). All subsequent git operations on the same instance fail with "fatal: It seems that there is already a rebase-merge directory, and I wonder if you are in the middle of another rebase." This is the most dangerous uncaught error pattern — it corrupts the working repo state for all future operations.Throws
GitError — on rebase conflict (exit code 1), upstream diverged, or any git failure. Unlike git.merge(), rebase does NOT throw GitResponseError<T> with a structured conflicts array — the conflict details are only in err.message (git stderr output). Common messages: "CONFLICT (content): Merge conflict in <file>", "error: could not apply <sha>... <message>", "Cannot rebase: You have unstaged changes" (dirty tree).Required handlingCaller MUST wrap git.rebase() in try-catch AND abort on failure to restore the repo to a usable state: try { await git.rebase(['origin/main']); } catch (err) { if (err instanceof GitError) { console.error('Rebase failed, aborting:', err.message); // CRITICAL: abort to restore the repo to pre-rebase state try { await git.rebase(['--abort']); } catch (abortErr) { console.error('Rebase abort also failed:', abortErr.message); } } throw err; } Always run rebase on a clean working tree (check git.status() first). Never use git.rebase() in automated workflows without abort handling.costhighin proddegraded serviceusers seeservice unavailablevisibilitysilent - raw · simple-git-raw-missing-try-catcherrorWhenasync function calls git.raw() without wrapping in try-catch. Because raw() runs arbitrary git commands, any git failure propagates. Commonly used for cherry-pick (can fail on conflicts), worktree operations (can fail on path conflicts), bisect, sparse-checkout, and other advanced operations — all of which can fail and need explicit handling.Throws
GitError — when git exits non-zero and printed to stderr. The .message property contains git's stderr output verbatim. Unlike structured methods, raw() never throws GitResponseError — always GitError. Examples: "CONFLICT" from cherry-pick, "fatal: not a git repository", "error: unknown switch `z'" from invalid flags.Required handlingCaller MUST wrap git.raw() in try-catch. Minimum handling: try { const result = await git.raw(['cherry-pick', commitHash]); console.log('Cherry-pick succeeded:', result); } catch (err) { if (err instanceof GitError) { console.error('Git command failed:', err.message); // For cherry-pick, may need: await git.raw(['cherry-pick', '--abort']) } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - clean · simple-git-clean-missing-try-catcherrorWhenasync function calls git.clean() without wrapping in try-catch. Most common failure: calling git.clean() without the required FORCE or DRY_RUN mode — throws TaskConfigurationError synchronously (before git runs). Also fails when git exits non-zero (permission errors, path issues).Throws
TaskConfigurationError (extends GitError) — thrown immediately (before git runs) when the mode is missing or invalid: 'Git clean mode parameter ("n" or "f") is required', 'Git clean interactive mode is not supported', 'Git clean unknown option found in: ...' GitError — thrown when the underlying git process fails (permission denied, path does not exist, etc.).Required handlingCaller MUST wrap git.clean() in try-catch AND always specify mode: import { simpleGit, CleanOptions } from 'simple-git'; try { // DRY_RUN first to preview what would be deleted const preview = await git.clean(CleanOptions.DRY_RUN + CleanOptions.RECURSIVE); console.log('Would delete:', preview); // Then FORCE to actually delete await git.clean(CleanOptions.FORCE + CleanOptions.RECURSIVE); } catch (err) { if (err instanceof GitError) { console.error('Git clean failed:', err.message); } throw err; } WARNING: git.clean() permanently deletes untracked files. Always preview with CleanOptions.DRY_RUN before running with CleanOptions.FORCE in production automation.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - stash · simple-git-stash-missing-try-catcherrorWhenasync function calls git.stash(), git.stash(['pop']), git.stash(['apply']), or git.stash(['drop']) without wrapping in try-catch. The most dangerous case: git.stash(['pop']) and git.stash(['apply']) can throw GitError when conflicts occur between stashed changes and the current branch — leaving partial conflict markers in the working tree. git.stash() on a clean tree also throws.Throws
GitError — on any failure: "No local changes to save" (stash on clean tree, exit 1), "CONFLICT (content): Merge conflict in <file>" (stash pop/apply conflict, exit 1), "error: Your local changes to the following files would be overwritten by merge" (dirty tree during pop), "fatal: Ref refs/stash is not a stash reference" (drop/apply on empty stash), "ENOENT: no such file or directory, git" (git not installed).Required handlingCaller MUST wrap git.stash() in try-catch for all stash subcommands. For stash save: try { await git.stash(); } catch (err) { if (err instanceof GitError && err.message.includes('No local changes to save')) { // Nothing to stash — not a fatal error return; } throw err; } For stash pop (most important — conflict handling): try { await git.stash(['pop']); } catch (err) { if (err instanceof GitError && err.message.includes('CONFLICT')) { // Stash pop produced conflicts — working tree is dirty // Must resolve conflicts manually or run: git checkout -- . console.error('Stash pop produced conflicts:', err.message); } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - reset · simple-git-reset-missing-try-catcherrorWhenasync function calls git.reset() or git.reset(ResetMode.HARD) without wrapping in try-catch. Critical failure modes: (1) ResetMode.HARD called programmatically — if anything throws before completion, partially-reset files may be deleted with no undo path. (2) reset() to a specific commit hash fails when the hash does not exist. (3) reset() on a bare repository fails ("fatal: Failed to resolve HEAD"). (4) ResetMode.MERGE fails if there are unmerged paths.Throws
GitError — on any non-zero git exit: "fatal: Failed to resolve 'HEAD'" (empty/bare repo), "fatal: ambiguous argument '<hash>': unknown revision or path" (bad commit reference), "fatal: Cannot do a hard reset in the middle of a merge" (merge in progress), "error: Entry '<file>' not uptodate. Cannot merge" (ResetMode.MERGE with local changes).Required handlingCaller MUST wrap git.reset() in try-catch. For hard reset, always verify the reset target is valid before calling: import { simpleGit, ResetMode } from 'simple-git'; try { // Soft reset: HEAD~1 to uncommit last commit (keeps changes staged) await git.reset(ResetMode.SOFT, ['HEAD~1']); } catch (err) { if (err instanceof GitError) { console.error('Reset failed:', err.message); } throw err; } For HARD reset (DESTRUCTIVE — use with extreme caution): try { await git.reset(ResetMode.HARD); // All uncommitted changes are now permanently gone } catch (err) { // Even HARD reset can fail (e.g., merge in progress) throw err; } NEVER call git.reset(ResetMode.HARD) in automated workflows without: (1) explicit user confirmation or (2) backup of important changes.costhighin prodimmediate exceptionusers seeservice unavailablevisibilitysilent - revert · simple-git-revert-missing-try-catcherrorWhenasync function calls git.revert(commitHash) without wrapping in try-catch. Revert conflicts — where the changes being undone have since been modified — leave the repo in "reverting" state (.git/REVERT_HEAD exists). All subsequent git operations on that working directory fail with "error: you need to resolve your current index first" until the revert is aborted or completed. Also throws when the commit hash is invalid or not found.Throws
GitError — on conflict or invalid ref: "CONFLICT (content): Merge conflict in <file>" (conflict during revert, exit 1), "error: could not revert <hash>" (conflict in fast path), "error: commit <hash> is a merge but no -m option was given" (reverting a merge commit), "fatal: bad object <hash>" (invalid commit hash), "error: you need to resolve your current index first" (revert in progress).Required handlingCaller MUST wrap git.revert() in try-catch AND abort on conflict to restore the working directory to a usable state: try { await git.revert(commitHash); } catch (err) { if (err instanceof GitError && err.message.includes('CONFLICT')) { console.error('Revert produced conflicts, aborting:', err.message); // CRITICAL: abort to clear the reverting state try { await git.revert(['--abort']); } catch (abortErr) { console.error('Revert abort failed:', abortErr.message); } } throw err; } To revert a merge commit, supply the parent option: await git.revert(commitHash, ['-m', '1']);costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - pushTags · simple-git-push-tags-missing-try-catcherrorWhenasync function calls git.pushTags() without wrapping in try-catch. pushTags() is equivalent to push(['--tags']) — all push failure modes apply: network failures, authentication failures, remote rejection (tag already exists with different content), and permission denied.Throws
GitError — on any non-zero exit: "Permission denied (publickey)" (SSH auth failure), "could not read Username for 'https://...'" (HTTPS auth prompt unavailable), "remote: Repository not found" (invalid remote URL), "! [rejected] v1.0.0 -> v1.0.0 (already exists)" (tag already exists), "ECONNREFUSED / ETIMEDOUT" (network failure).Required handlingCaller MUST wrap git.pushTags() in try-catch. Minimum handling: try { await git.pushTags('origin'); } catch (err) { if (err instanceof GitError) { console.error('Tag push failed:', err.message); } throw err; } For release automation, always push tags separately from commits so that failure to push tags does not block the commit push.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - deleteLocalBranch · simple-git-delete-local-branch-missing-try-catcherrorWhenasync function calls git.deleteLocalBranch(branchName) without wrapping in try-catch. The most common failure: deleting a branch with unmerged commits without the forceDelete flag throws GitResponseError<BranchSingleDeleteResult> with the parsed failure details. Also throws when the branch does not exist or when trying to delete the currently checked-out branch.Throws
GitResponseError<BranchSingleDeleteResult> — when the branch has unmerged commits and forceDelete is false (default). The .git property contains the parsed BranchSingleDeleteResult with { branch, hash, success: false }. Error message from git: "error: The branch '<name>' is not fully merged. If you are sure you want to delete it, run 'git branch -D <name>'." GitError — for other failures: "error: branch '<name>' not found" (branch does not exist), "error: Cannot delete branch '<name>' checked out at '<path>'" (current branch).Required handlingCaller MUST wrap git.deleteLocalBranch() in try-catch. Pattern for safe branch deletion: try { const result = await git.deleteLocalBranch(branchName); // result.success is always true here (GitResponseError thrown otherwise) console.log('Deleted branch:', result.branch); } catch (err) { if (err instanceof GitResponseError) { // Unmerged branch — either force-delete or handle gracefully const parsed = err.git; // BranchSingleDeleteResult console.warn('Branch not fully merged:', parsed?.branch); // Option: force delete // await git.deleteLocalBranch(branchName, true); } else if (err instanceof GitError) { console.error('Branch delete failed:', err.message); } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - submoduleAdd · simple-git-submodule-add-missing-try-catcherrorWhenasync function calls git.submoduleAdd(repo, path) without wrapping in try-catch. submoduleAdd clones the submodule repository over the network — all clone error modes apply. Also fails when the path already exists, when git cannot write .gitmodules, or when the local path conflicts with existing files.Throws
GitError — on any non-zero exit: "fatal: repository '<url>' not found" (invalid remote URL or private repo without auth), "Permission denied (publickey)" (SSH auth failure), "fatal: '<path>' already exists in the index" (path conflict), "fatal: A git repository cannot be used as a subdirectory of itself" (nested git repo), "ECONNREFUSED / ETIMEDOUT" (network failure during submodule clone).Required handlingCaller MUST wrap git.submoduleAdd() in try-catch. Minimum handling: try { await git.submoduleAdd('https://github.com/user/repo.git', 'vendor/repo'); // Remember to stage and commit .gitmodules after adding await git.add('.gitmodules'); await git.commit('Add vendor/repo submodule'); } catch (err) { if (err instanceof GitError) { console.error('Submodule add failed:', err.message); } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - submoduleUpdate · simple-git-submodule-update-missing-try-catcherrorWhenasync function calls git.submoduleUpdate() or git.submoduleUpdate(['--init']) without wrapping in try-catch. submoduleUpdate fetches each registered submodule from its remote — all fetch/clone failure modes apply. A single failing submodule causes the entire update to fail, leaving other submodules un-updated. Common in post-clone CI scripts that do: await git.submoduleUpdate(['--init', '--recursive']).Throws
GitError — on any non-zero exit: "fatal: repository '<url>' not found" (submodule remote no longer exists), "Permission denied (publickey)" (SSH auth failure for any submodule remote), "fatal: No url found for submodule path '<path>'" (.gitmodules not initialized), "ECONNREFUSED / ETIMEDOUT" (network failure during submodule fetch), "fatal: Needed a single revision" (corrupt submodule reference in parent repo).Required handlingCaller MUST wrap git.submoduleUpdate() in try-catch. Minimum handling (common CI pattern): try { // Initialize + update all submodules recursively await git.submoduleUpdate(['--init', '--recursive']); } catch (err) { if (err instanceof GitError) { console.error('Submodule update failed:', err.message); // Check: are .gitmodules configured? Is the remote accessible? } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]raw.githubusercontent.com/steveukx/git-jshttps://raw.githubusercontent.com/steveukx/git-js/refs/heads/main/simple-git/readme.md
- [2]git-scm.com/docs/git-fetchhttps://git-scm.com/docs/git-fetch
- [3]git-scm.com/docs/git-commithttps://git-scm.com/docs/git-commit
- [4]git-scm.com/docs/git-checkouthttps://git-scm.com/docs/git-checkout
- [5]git-scm.com/docs/git-rebasehttps://git-scm.com/docs/git-rebase
- [6]git-scm.com/docs/githttps://git-scm.com/docs/git
- [7]git-scm.com/docs/git-cleanhttps://git-scm.com/docs/git-clean
- [8]git-scm.com/docs/git-stashhttps://git-scm.com/docs/git-stash
- [9]git-scm.com/docs/git-resethttps://git-scm.com/docs/git-reset
- [10]git-scm.com/docs/git-reverthttps://git-scm.com/docs/git-revert
- [11]git-scm.com/docs/git-pushhttps://git-scm.com/docs/git-push
- [12]git-scm.com/docs/git-branchhttps://git-scm.com/docs/git-branch
- [13]git-scm.com/docs/git-submodulehttps://git-scm.com/docs/git-submodule
Need a different package?
Request a profile