/*** Lightweight parser for .git/config files.** Verified against git's config.c:* - Section names: case-insensitive, alphanumeric + hyphen* - Subsection names (quoted): case-sensitive, backslash escapes (\\ and \")* - Key names: case-insensitive, alphanumeric + hyphen* - Values: optional quoting, inline comments (# or ;), backslash escapes*/import { readFile } from 'fs/promises'import { join } from 'path'/*** Parse a single value from .git/config.* Finds the first matching key under the given section/subsection.*/export async function parseGitConfigValue(gitDir: string,section: string,subsection: string | null,key: string,): Promise<string | null> {try {const config = await readFile(join(gitDir, 'config'), 'utf-8')return parseConfigString(config, section, subsection, key)} catch {return null}}/*** Parse a config value from an in-memory config string.* Exported for testing.*/export function parseConfigString(config: string,section: string,subsection: string | null,key: string,): string | null {const lines = config.split('\n')const sectionLower = section.toLowerCase()const keyLower = key.toLowerCase()let inSection = falsefor (const line of lines) {const trimmed = line.trim()// Skip empty lines and comment-only linesif (trimmed.length === 0 || trimmed[0] === '#' || trimmed[0] === ';') {continue}// Section headerif (trimmed[0] === '[') {inSection = matchesSectionHeader(trimmed, sectionLower, subsection)continue}if (!inSection) {continue}// Key-value line: find the key nameconst parsed = parseKeyValue(trimmed)if (parsed && parsed.key.toLowerCase() === keyLower) {return parsed.value}}return null}/*** Parse a key = value line. Returns null if the line doesn't contain a valid key.*/function parseKeyValue(line: string): { key: string; value: string } | null {// Read key: alphanumeric + hyphen, starting with alphalet i = 0while (i < line.length && isKeyChar(line[i]!)) {i++}if (i === 0) {return null}const key = line.slice(0, i)// Skip whitespacewhile (i < line.length && (line[i] === '' || line[i] === '\t')) {i++}// Must have '='if (i >= line.length || line[i] !== '=') {// Boolean key with no value — not relevant for our use casesreturn null}i++ // skip '='// Skip whitespace after '='while (i < line.length && (line[i] === '' || line[i] === '\t')) {i++}const value = parseValue(line, i)return { key, value }}/*** Parse a config value starting at position i.* Handles quoted strings, escape sequences, and inline comments.*/function parseValue(line: string, start: number): string {let result = ''let inQuote = falselet i = startwhile (i < line.length) {const ch = line[i]!// Inline comments outside quotes end the valueif (!inQuote && (ch === '#' || ch === ';')) {break}if (ch === '"') {inQuote = !inQuotei++continue}if (ch === '\\' && i + 1 < line.length) {const next = line[i + 1]!if (inQuote) {// Inside quotes: recognize escape sequencesswitch (next) {case 'n':result += '\n'breakcase 't':result += '\t'breakcase 'b':result += '\b'breakcase '"':result += '"'breakcase '\\':result += '\\'breakdefault:// Git silently drops the backslash for unknown escapesresult += nextbreak}i += 2continue}// Outside quotes: backslash at end of line = continuation (we don't// handle multi-line since we split on \n, but handle \\ and others)if (next === '\\') {result += '\\'i += 2continue}// Fallthrough — treat backslash literally outside quotes}result += chi++}// Trim trailing whitespace from unquoted portions.// Git trims trailing whitespace that isn't inside quotes, but since we// process char-by-char and quotes toggle, the simplest correct approach// for single-line values is to trim the result when not ending in a quote.if (!inQuote) {result = trimTrailingWhitespace(result)}return result}function trimTrailingWhitespace(s: string): string {let end = s.lengthwhile (end > 0 && (s[end - 1] === '' || s[end - 1] === '\t')) {end--}return s.slice(0, end)}/*** Check if a config line like `[remote "origin"]` matches the given section/subsection.* Section matching is case-insensitive; subsection matching is case-sensitive.*/function matchesSectionHeader(line: string,sectionLower: string,subsection: string | null,): boolean {// line starts with '['let i = 1// Read section namewhile (i < line.length &&line[i] !== ']' &&line[i] !== '' &&line[i] !== '\t' &&line[i] !== '"') {i++}const foundSection = line.slice(1, i).toLowerCase()if (foundSection !== sectionLower) {return false}if (subsection === null) {// Simple section: must end with ']'return i < line.length && line[i] === ']'}// Skip whitespace before subsection quotewhile (i < line.length && (line[i] === '' || line[i] === '\t')) {i++}// Must have opening quoteif (i >= line.length || line[i] !== '"') {return false}i++ // skip opening quote// Read subsection — case-sensitive, handle \\ and \" escapeslet foundSubsection = ''while (i < line.length && line[i] !== '"') {if (line[i] === '\\' && i + 1 < line.length) {const next = line[i + 1]!if (next === '\\' || next === '"') {foundSubsection += nexti += 2continue}// Git drops the backslash for other escapes in subsectionsfoundSubsection += nexti += 2continue}foundSubsection += line[i]i++}// Must have closing quote followed by ']'if (i >= line.length || line[i] !== '"') {return false}i++ // skip closing quoteif (i >= line.length || line[i] !== ']') {return false}return foundSubsection === subsection}function isKeyChar(ch: string): boolean {return ((ch >= 'a' && ch <= 'z') ||(ch >= 'A' && ch <= 'Z') ||(ch >= '0' && ch <= '9') ||ch === '-')}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。