-
Notifications
You must be signed in to change notification settings - Fork 37
/
git.ts
109 lines (93 loc) · 3.14 KB
/
git.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//
// Basic git wrappers
//
import { SpawnSyncReturns } from "child_process";
import { spawnSync, SpawnSyncOptions } from "child_process";
export class GitError extends Error {
public originalError: unknown;
constructor(message: string, originalError?: unknown) {
if (originalError instanceof Error) {
super(`${message}: ${originalError.message}`);
} else {
super(message);
}
this.originalError = originalError;
}
}
/**
* A global maxBuffer override for all git operations.
* Bumps up the default to 500MB instead of 1MB.
* Override this value with the `GIT_MAX_BUFFER` environment variable.
*/
const defaultMaxBuffer = process.env.GIT_MAX_BUFFER ? parseInt(process.env.GIT_MAX_BUFFER) : 500 * 1024 * 1024;
const isDebug = !!process.env.GIT_DEBUG;
export type GitProcessOutput = {
stderr: string;
stdout: string;
success: boolean;
} & Omit<SpawnSyncReturns<string | Buffer>, "stdout" | "stderr">;
/** Observes the git operations called from `git()` or `gitFailFast()` */
export type GitObserver = (args: string[], output: GitProcessOutput) => void;
const observers: GitObserver[] = [];
let observing: boolean;
/**
* Adds an observer for the git operations, e.g. for testing
* @returns a function to remove the observer
*/
export function addGitObserver(observer: GitObserver) {
observers.push(observer);
return () => removeGitObserver(observer);
}
/** Clear all git observers */
export function clearGitObservers() {
observers.splice(0, observers.length);
}
/** Remove a git observer */
function removeGitObserver(observer: GitObserver) {
const index = observers.indexOf(observer);
if (index > -1) {
observers.splice(index, 1);
}
}
/**
* Runs git command - use this for read-only commands
*/
export function git(args: string[], options?: SpawnSyncOptions): GitProcessOutput {
isDebug && console.log(`git ${args.join(" ")}`);
const results = spawnSync("git", args, { maxBuffer: defaultMaxBuffer, ...options });
const output: GitProcessOutput = {
...results,
// these may be undefined if stdio: inherit is set
stderr: (results.stderr || "").toString().trimEnd(),
stdout: (results.stdout || "").toString().trimEnd(),
success: results.status === 0,
};
if (isDebug) {
console.log("exited with code " + results.status);
output.stdout && console.log("git stdout:\n", output.stdout);
output.stderr && console.warn("git stderr:\n", output.stderr);
}
// notify observers, flipping the observing bit to prevent infinite loops
if (!observing) {
observing = true;
for (const observer of observers) {
observer(args, output);
}
observing = false;
}
return output;
}
/**
* Runs git command - use this for commands that make changes to the filesystem
*/
export function gitFailFast(args: string[], options?: SpawnSyncOptions & { noExitCode?: boolean }) {
const gitResult = git(args, options);
if (!gitResult.success) {
if (!options?.noExitCode) {
process.exitCode = 1;
}
throw new GitError(`CRITICAL ERROR: running git command: git ${args.join(" ")}!
${gitResult.stdout?.toString().trimEnd()}
${gitResult.stderr?.toString().trimEnd()}`);
}
}