-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathgit.ts
More file actions
102 lines (93 loc) · 2.84 KB
/
git.ts
File metadata and controls
102 lines (93 loc) · 2.84 KB
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
import path from 'node:path';
import { type StatusResult, simpleGit } from 'simple-git';
import { stringifyError } from '../errors.js';
import { logger } from '../logger.js';
import { toUnixPath } from '../transform.js';
export function getGitRoot(git = simpleGit()): Promise<string> {
return git.revparse('--show-toplevel');
}
export async function getGitDefaultBranch(git = simpleGit()): Promise<string> {
try {
const head = await git.revparse(['--abbrev-ref', 'origin/HEAD']);
return head.replace(/^origin\//, '');
} catch (error) {
logger.warn(
`Failed to get the default Git branch, falling back to main - ${stringifyError(error)}`,
);
return 'main';
}
}
export function formatGitPath(filePath: string, gitRoot: string): string {
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(process.cwd(), filePath);
const relativePath = path.relative(gitRoot, absolutePath);
return toUnixPath(relativePath);
}
export async function toGitPath(
filePath: string,
git = simpleGit(),
): Promise<string> {
const gitRoot = await getGitRoot(git);
return formatGitPath(filePath, gitRoot);
}
export class GitStatusError extends Error {
static ignoredProps = new Set(['current', 'tracking']);
static getReducedStatus(status: StatusResult) {
return Object.fromEntries(
Object.entries(status)
.filter(([key]) => !this.ignoredProps.has(key))
.filter(
(
entry: [
string,
number | string | boolean | null | undefined | unknown[],
],
) => {
const value = entry[1];
if (value == null) {
return false;
}
if (Array.isArray(value) && value.length === 0) {
return false;
}
if (typeof value === 'number' && value === 0) {
return false;
}
return !(typeof value === 'boolean' && !value);
},
),
);
}
constructor(status: StatusResult) {
super(
`Working directory needs to be clean before we you can proceed. Commit your local changes or stash them: \n ${JSON.stringify(
GitStatusError.getReducedStatus(status),
null,
2,
)}`,
);
}
}
export async function guardAgainstLocalChanges(
git = simpleGit(),
): Promise<void> {
const status = await git.status(['-s']);
if (status.files.length > 0) {
throw new GitStatusError(status);
}
}
export async function safeCheckout(
branchOrHash: string,
forceCleanStatus = false,
git = simpleGit(),
): Promise<void> {
// git requires a clean history to check out a branch
if (forceCleanStatus) {
await git.raw(['reset', '--hard']);
await git.clean(['f', 'd']);
logger.info(`git status cleaned`);
}
await guardAgainstLocalChanges(git);
await git.checkout(branchOrHash);
}