-
Notifications
You must be signed in to change notification settings - Fork 0
/
util-git.ts
135 lines (106 loc) · 3.79 KB
/
util-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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import path from "path";
import cp from "child_process";
import assert from "assert";
import { cleanLines } from "./util";
import { ModCommitPair, getFileModHistories } from "./file-history";
export function getRepoRootPath(): string {
return cp.execSync(`git rev-parse --show-toplevel`).toString().trim();
}
export function getDotGitDirPath(): string {
return path.resolve(cp.execSync(`git rev-parse --git-dir`).toString().trim());
}
export function getHead(): string {
return cp.execSync(`git rev-parse HEAD`).toString().trim();
}
export function listRepoRelativeFilepaths(cwd: string = getRepoRootPath()): string[] {
return cp.execSync(`git ls-files`, { cwd })
.toString()
.split("\n")
.filter(x => !!x);
}
export function resolveRepoRelFilepath(pathSuffix: string, repoRelFilepaths: string[], sinceCommittish: string): string {
const matchingSuffixes: string[] = repoRelFilepaths.filter(x => x.endsWith(pathSuffix));
if (matchingSuffixes.length === 1) {
return matchingSuffixes[0];
} else if (matchingSuffixes.length > 1) {
/**
* TODO allow fixing later
*/
const msg = `matched > 1 files for given suffix "${pathSuffix}". provide longer suffix.`;
throw new Error(msg);
}
/**
* none found in current state of repo,
* thus search in earlier commits.
*/
const commits: string[] = listCommitsOfFile(pathSuffix, sinceCommittish);
if (commits.length) {
return pathSuffix;
}
throw new Error(`did not find file in repo for provided suffix "${pathSuffix}".`);
}
export function listCommitsSince(sinceCommittish: string): string[] {
return getFileModHistories({ sinceCommittish }).listCommits();
}
export function listCommitsOfFile(file: string, sinceCommittish?: string): string[] {
return getFileModHistories({ file, sinceCommittish }).listCommitsOfFile();
}
export function listModificationsOfFile(file: string, sinceCommittish?: string): ModCommitPair[] {
return getFileModHistories({ file, sinceCommittish }).listModificationsOfFile();
}
/**
* TODO further search & verify,
* e.g. if git couldn't auto-detect a rename.
*/
export function allCommitsOfFileAreWithinSince(
filepath: string, //
sinceCommittish: string,
commitsOfFileSince: string[] = listCommitsOfFile(filepath, sinceCommittish),
commitsOfFile: string[] = listCommitsOfFile(filepath),
): boolean {
if (commitsOfFile.length !== commitsOfFileSince.length) return false;
for (let i = 0; i < commitsOfFile.length; i++) {
if (commitsOfFile[i] !== commitsOfFileSince[i]) return false;
}
return true;
}
export function ensureRepoStateClean() {
if (repoHasUntrackedChanges()) {
const msg = `repo has untracked changes.`;
throw new Error(msg);
}
}
export function repoHasUntrackedChanges(): boolean {
return cp.execSync("git status -s").toString().length > 0;
}
export type FileRename = {
from: string;
to: string;
};
export type DidCommitRenameFileRet = false | FileRename;
/**
* TODO handle complex renames
*/
export function didCommitRenameFile(filepath: string, commit: string): DidCommitRenameFileRet {
const out = cp.execSync(`git show ${commit} --diff-filter=R --name-status`).toString();
const renameInfos: string[] = cleanLines(out).filter(x => x.includes(filepath));
if (!renameInfos.length) return false;
assert.deepStrictEqual(renameInfos.length, 1);
const info = renameInfos[0].split(spaceTabManyRegex).filter(x => !!x.trim());
assert.deepStrictEqual(info.length, 3);
const [_status, from, to] = info;
return {
from,
to,
};
}
export const spaceTabManyRegex = /[\s\t]+/g;
export function addGlobalChanges(pathspec: string = ".") {
cp.execSync(`git add ${pathspec}`, { cwd: getRepoRootPath() });
}
export function amendCommit(extraArgs: string = "") {
cp.execSync(`git commit --amend --no-edit ${extraArgs}`);
}
export function resetHard(committish: string) {
cp.execSync(`git reset --hard ${committish}`);
}