Parser for git config files that follows git's own rules and is checked against git itself.
import { parse, load } from 'gitconfig-parse';
const cfg = parse('[remote "Origin"]\n\turl = https://example.com/a.git ; mirror\n\tpushurl\n');
cfg.get('remote.Origin.url'); // 'https://example.com/a.git'
cfg.get('remote.Origin.pushurl'); // null (bare key, git reads it as true)
cfg.entries[0]; // { section: 'remote', subsection: 'Origin', key: 'url', name: 'remote.Origin.url', value: '...', line: 2, file: '<config>' }
const repo = load('/path/to/.git/config', { context: { gitDir: '/path/to/.git', branch: 'main' } });
repo.getBool('core.bare');The two npm packages people use for .git/config read it as INI. Git's format differs in ways that
change values: \n, \t, \" escapes, \ line continuation, " quoting, ; and # comments after
a value, a bare key meaning true, case rules for sections and subsections, multi-valued keys, [s "sub"]
escapes, a BOM, and hard errors for invalid input. Neither package rejects anything git rejects.
Measured: cd bench && npm install && node incumbents.mjs, over a corpus of 4000 random configs
(2475 accepted by git 2.55.0, 1525 rejected), comparing each package's values with git config --file f --list -z.
Bare keys are compared leniently and key order across sections is ignored, which favors the packages.
ini 7.0.0 valid 644/2475 invalid rejected 0/1525 agree 644/4000 = 16.1%
parse-git-config 3.0.0 valid 923/2475 invalid rejected 0/1525 agree 923/4000 = 23.1%
This package agrees on all 4000 (values, order, error line numbers and bool/int/path conversions).
Other npm packages found by name pattern: gitconfiglocal (2019) and git-config (2015) are unmaintained
INI-style readers; gitconfig runs git config as a subprocess. Not measured.
npm install gitconfig-parseNode 20.6 or later. No dependencies.
const cfg = parse(text, { file: 'config' });
cfg.get('user.name'); // last value, like `git config --get`
cfg.getAll('remote.origin.fetch'); // every value, in file order
cfg.has('core.editor');Lookup keys follow git: the section and variable name are case-insensitive, the subsection is not.
cfg.getBool('core.filemode'); // true for yes/on/true/1, bare key; false for no/off/false/0/empty
cfg.getInt('http.postbuffer'); // '1m' -> 1048576; getInt64 returns a bigint
cfg.getPath('core.excludesfile', { home: '/home/me' }); // '~/x' -> '/home/me/x'const cfg = load('/home/me/.gitconfig', {
home: '/home/me',
context: { gitDir: '/home/me/work/app/.git', branch: 'main' },
});include.path and includeIf with gitdir:, gitdir/i:, onbranch: and hasconfig:remote.*.url: are
followed. Relative paths resolve against the including file. Missing files are skipped. Nesting deeper
than 10 is an error.
try { parse('[core]\nname = "open\n', { file: 'config' }); }
catch (e) { e.message; /* 'bad config line 2 in file config' */ }| Name | Description |
|---|---|
parse(input, { file? }) |
Parse a string or UTF-8 bytes into a Config. Follows no includes. |
load(path, options?) |
Read a file and follow its includes. |
resolveIncludes(text, file, options?) |
Same as load for text already in memory. |
parseEntries(text, file, onEntry?) |
Low-level entry list. |
parseBool(value) |
Git's boolean rules. null is true. Throws ConfigValueError. |
parseInt64(value) |
Git's integer rules as a bigint: decimal, 0x, octal, sign, k/m/g. |
expandPath(value, ctx?) |
~/, ~user/, %(prefix)/ and :(optional). |
canonicalKey(key) |
Normalize Section.Sub.Key. Throws for keys git refuses. |
wildmatch(pattern, text, { pathname?, foldCase? }) |
Git's glob matcher. |
| Member | Description |
|---|---|
entries |
Entry[] in file order (included files inline, after the include entry). |
get(key) |
Last value, undefined if absent, null for a bare key. |
getAll(key) |
All values. |
has(key) |
Whether the key exists. |
getBool(key), getInt(key), getInt64(key), getPath(key, ctx?) |
Typed last value; undefined if absent. |
| Field | Type | Description |
|---|---|---|
section |
string | Lowercased. |
subsection |
string or null | Case kept for [s "Sub"], lowered for [s.Sub]. |
key |
string | Lowercased. |
name |
string | section.subsection.key. |
value |
string or null | null for a bare key. |
line |
number | 1-based line where the key starts. |
file |
string | Source file. |
| Name | Type | Default | Description |
|---|---|---|---|
context.gitDir |
string | none | Git directory for gitdir: conditions. |
context.branch |
string | none | Branch for onbranch:. |
context.remoteUrls |
string[] | collected from the files | URLs for hasconfig:remote.*.url:. |
home |
string | $HOME |
Replaces ~/. |
homeOf |
function | none | Resolves ~user/. |
prefix |
string | none | Replaces %(prefix)/. |
maxDepth |
number | 10 | Include nesting limit. |
readFile |
function | filesystem | Return undefined for a missing file. |
realpath |
function | fs.realpathSync |
Symlink resolution for gitdir:. |
Errors: ConfigSyntaxError (file, line, message bad config line N in file F), ConfigValueError
(bad typed value or lookup key), ConfigIncludeError (depth limit, unreadable top-level file, remote URL in a
hasconfig include).
The parser reads one character at a time as git does: CRLF reads as a newline, end of input reads as a
final newline, a value keeps interior whitespace exactly as written and drops leading and trailing
whitespace outside quotes, and each error reports the line git would. The rules were reimplemented
from git-config(1) and from observed behavior; no code was taken from git.
corpus/gen.mjs builds random configs (valid, invalid and mutated), asks git for the accept/reject
decision, the ordered key=value list, the error line and --type=bool/int/path results, and writes
test/fixtures/corpus.jsonl. The live tests do the same with fresh random cases on every run.
colorvalues (--type=color) are not implemented.- No edit API. This package reads only.
- Typed accessors convert the last value only. Git also converts earlier values of the same key and fails if any is invalid.
- Text is UTF-8. Invalid bytes become U+FFFD when parsing bytes.
- Include paths and
gitdir:matching use/separators; Windows paths are untested. gitdir:matching needs the git directory from the caller. Worktree and.gitfile discovery are not done.- Only the four documented
includeIfconditions exist. Unknown conditions are false, as in git. ~user/needs ahomeOfcallback.- The scope files (system, global, local) and
GIT_CONFIG_*variables are not read. Load the files you want.
Run: Node 24.20 on macOS, git 2.55.0. The CI workflow also runs the built package on Node 20.6, 22 and
24 (test/smoke.mjs); those runs have not been done locally. Other git versions are untested, and
%(prefix) expansion and ~user lookups depend on the machine's git.
npm install
npx tsc --noEmit
npm test # corpus + live tests; needs git on PATH, LIVE_N and LIVE_SEED tune the live run
npm run build && npm run test:dist
npm run corpus # regenerate test/fixtures/corpus.jsonl with the installed gitMIT. See LICENSE.