(args: string[], opts: GitOptions)
| 24 | } |
| 25 | |
| 26 | export async function git(args: string[], opts: GitOptions): Promise<GitResult> { |
| 27 | const fullArgs = [ |
| 28 | '-C', opts.cwd, |
| 29 | '--no-pager', |
| 30 | '-c', 'color.ui=false', |
| 31 | ...args, |
| 32 | ]; |
| 33 | const timeoutMs = opts.timeoutMs ?? 60_000; |
| 34 | |
| 35 | return new Promise<GitResult>((resolve) => { |
| 36 | let proc: ReturnType<typeof spawn>; |
| 37 | try { |
| 38 | proc = spawn('git', fullArgs, { |
| 39 | signal: opts.signal, |
| 40 | stdio: [opts.stdin !== undefined ? 'pipe' : 'ignore', 'pipe', 'pipe'], |
| 41 | }); |
| 42 | } catch (e: any) { |
| 43 | resolve({ |
| 44 | exitCode: 127, |
| 45 | stdout: '', |
| 46 | stderr: `git spawn failed: ${e.message}`, |
| 47 | timedOut: false, |
| 48 | }); |
| 49 | return; |
| 50 | } |
| 51 | |
| 52 | let stdout = ''; |
| 53 | let stderr = ''; |
| 54 | let timedOut = false; |
| 55 | let settled = false; |
| 56 | const settle = (r: GitResult): void => { |
| 57 | if (settled) return; |
| 58 | settled = true; |
| 59 | clearTimeout(termTimer); |
| 60 | clearTimeout(killTimer); |
| 61 | resolve(r); |
| 62 | }; |
| 63 | |
| 64 | proc.stdout?.on('data', (d: Buffer) => { stdout += d.toString(); }); |
| 65 | proc.stderr?.on('data', (d: Buffer) => { stderr += d.toString(); }); |
| 66 | |
| 67 | if (opts.stdin !== undefined && proc.stdin) { |
| 68 | proc.stdin.write(opts.stdin); |
| 69 | proc.stdin.end(); |
| 70 | } |
| 71 | |
| 72 | const termTimer = setTimeout(() => { |
| 73 | timedOut = true; |
| 74 | try { proc.kill('SIGTERM'); } catch {} |
| 75 | }, timeoutMs); |
| 76 | const killTimer = setTimeout(() => { |
| 77 | try { proc.kill('SIGKILL'); } catch {} |
| 78 | }, timeoutMs + 2000); |
| 79 | |
| 80 | proc.on('close', (code, signal) => { |
| 81 | const exitCode = code ?? (signal === 'SIGTERM' ? 124 : signal === 'SIGKILL' ? 137 : 130); |
| 82 | settle({ exitCode, stdout, stderr, timedOut }); |
| 83 | }); |
no test coverage detected