Skip to content

About

Parser for git config files that follows git's own rules, checked against git itself: escapes, continuation lines, subsections, bare keys, include and includeIf, bool/int/path values.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

gitconfig-parse

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');

Why

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.

Installation

npm install gitconfig-parse

Node 20.6 or later. No dependencies.

Usage

Read values

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.

Typed values

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'

Includes

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.

Errors

try { parse('[core]\nname = "open\n', { file: 'config' }); }
catch (e) { e.message; /* 'bad config line 2 in file config' */ }

Reference

Functions

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.

Config

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.

Entry

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.

LoadOptions

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

How it works

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.

Limitations

  • color values (--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 .git file discovery are not done.
  • Only the four documented includeIf conditions exist. Unknown conditions are false, as in git.
  • ~user/ needs a homeOf callback.
  • The scope files (system, global, local) and GIT_CONFIG_* variables are not read. Load the files you want.

Compatibility

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.

Development

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 git

License

MIT. See LICENSE.

About

Parser for git config files that follows git's own rules, checked against git itself: escapes, continuation lines, subsections, bare keys, include and includeIf, bool/int/path values.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages