Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
6.6 kB · 277 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278/** * 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 = false for (const line of lines) { const trimmed = line.trim()
// Skip empty lines and comment-only lines if (trimmed.length === 0 || trimmed[0] === '#' || trimmed[0] === ';') { continue }
// Section header if (trimmed[0] === '[') { inSection = matchesSectionHeader(trimmed, sectionLower, subsection) continue }
if (!inSection) { continue }
// Key-value line: find the key name const 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 alpha let i = 0 while (i < line.length && isKeyChar(line[i]!)) { i++ } if (i === 0) { return null } const key = line.slice(0, i)
// Skip whitespace while (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 cases return 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 = false let i = start
while (i < line.length) { const ch = line[i]!
// Inline comments outside quotes end the value if (!inQuote && (ch === '#' || ch === ';')) { break }
if (ch === '"') { inQuote = !inQuote i++ continue }
if (ch === '\\' && i + 1 < line.length) { const next = line[i + 1]! if (inQuote) { // Inside quotes: recognize escape sequences switch (next) { case 'n': result += '\n' break case 't': result += '\t' break case 'b': result += '\b' break case '"': result += '"' break case '\\': result += '\\' break default: // Git silently drops the backslash for unknown escapes result += next break } i += 2 continue } // 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 += 2 continue } // Fallthrough — treat backslash literally outside quotes }
result += ch i++ }
// 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.length while (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 name while ( 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 quote while (i < line.length && (line[i] === ' ' || line[i] === '\t')) { i++ }
// Must have opening quote if (i >= line.length || line[i] !== '"') { return false } i++ // skip opening quote
// Read subsection — case-sensitive, handle \\ and \" escapes let foundSubsection = '' while (i < line.length && line[i] !== '"') { if (line[i] === '\\' && i + 1 < line.length) { const next = line[i + 1]! if (next === '\\' || next === '"') { foundSubsection += next i += 2 continue } // Git drops the backslash for other escapes in subsections foundSubsection += next i += 2 continue } foundSubsection += line[i] i++ }
// Must have closing quote followed by ']' if (i >= line.length || line[i] !== '"') { return false } i++ // skip closing quote
if (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 === '-' )}