-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathprepareCommitMsg.ts
More file actions
302 lines (264 loc) · 8.8 KB
/
prepareCommitMsg.ts
File metadata and controls
302 lines (264 loc) · 8.8 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
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/**
* Prepare commit message.
*
* This module ties together logic from independent modules in the `generate`
* module. So it is best kept on the outside here.
*
* The "message" is the full commit message. The "file change description" is
* the description portion, which describes how the files changed.
*
* This module doesn't interact with the git CLI or the extension. It just deals
* with text.
*/
import { lookup
7440
DiffIndexAction } from "./generate/action";
import { getConventionType } from "./generate/convCommit";
import { countFilesDesc } from "./generate/count";
import { namedFilesDesc, oneChange } from "./generate/message";
import { splitMsg } from "./generate/parseExisting";
import { MsgPieces } from "./generate/parseExisting.d";
import { parseDiffIndex } from "./git/parseOutput";
import { AGGREGATE_MIN, CONVENTIONAL_TYPE } from "./lib/constants";
import { equal } from "./lib/utils";
import { ConvCommitMsg } from "./prepareCommitMsg.d";
/**
* Join two strings together with a space.
*
* Use only one string if only one is set or if they are identical.
*
* Trimming on the outside is necessary here, in case only one item is set.
*/
export function _joinWithSpace(first: string, second: string) {
first = first.trim();
second = second.trim();
if (first === second) {
return first;
}
return `${first} ${second}`.trim();
}
/**
* Join two strings using a colon and space.
*/
export function _joinWithColon(first: string, second: string): string {
return `${first}: ${second}`;
}
/**
* Determine the Conventional Commit type prefix for a file change.
*
* @param line Description of a file change from Git output. e.g. "A baz.txt"
*/
export function _prefixFromChange(line: string) {
const { x: actionChar, from: filePath } = parseDiffIndex(line);
const action = lookupDiffIndexAction(actionChar);
return getConventionType(action, filePath);
}
/**
* Generate message for a single file change.
*
* @param line Description of a file change from Git output. e.g. "A baz.txt"
*/
export function _msgOne(line: string) {
// TODO: Pass FileChanges to oneChange and _prefixFromChange instead of string.
// Don't unpack as {x, y, from, to}
// const fileChanges = parseDiffIndex(line)
const typePrefix = _prefixFromChange(line),
description = oneChange(line);
return { typePrefix, description };
}
/**
* Determine a single type prefix from multiple values given.
*
* @param types An array of Convention Commit type prefixes.
*
* @returns A single prefix type.
* - Unknown if zero items - not likely in real life but covered anyway.
* - The first item if they are equal.
* - Use unknown if they are different.
* - If at least one item is build dependencies even if the others are
* different, then use that. This covers the case where `package.json` may
* have non-deps changes, but the `package-lock.json` is enough to want to
* use the deps scope.
*/
export function _collapse(types: CONVENTIONAL_TYPE[]) {
let result = CONVENTIONAL_TYPE.UNKNOWN;
if (!types.length) {
return result;
}
if (equal(types)) {
result = types[0];
} else if (types.includes(CONVENTIONAL_TYPE.BUILD_DEPENDENCIES)) {
result = CONVENTIONAL_TYPE.BUILD_DEPENDENCIES;
}
return result;
}
/**
* Generate prefix and named description for multiple file changes.
*
* This finds a common Conventional Commit prefix if one is appropriate and
* returns a message listing all the file names.
*
* @param lines An array of values describing file change from Git output.
* e.g. ["A baz.txt"]
*/
export function _msgNamed(lines: string[]): ConvCommitMsg {
const conventions = lines.map(_prefixFromChange);
const typePrefix = _collapse(conventions);
const changes = lines.map(parseDiffIndex);
const description = namedFilesDesc(changes);
return { typePrefix, description };
}
/**
* Generate prefix and count description for multiple file changes.
*
* @param lines An array of values describing file change from Git output.
* e.g. ["A baz.txt"]
*/
export function _msgCount(lines: string[]): ConvCommitMsg {
const prefix = CONVENTIONAL_TYPE.UNKNOWN;
const changes = lines.map(parseDiffIndex);
const description = countFilesDesc(changes);
return { typePrefix: prefix, description };
}
/**
* Generate message from changes to one or more files.
*
* @param lines An array of values describing file change from Git output.
* e.g. ["A baz.txt"]
*
* @returns Commit message containing a type prefix and a description of changed
* paths.
*/
export function _msgFromChanges(lines: string[]) {
let result: ConvCommitMsg;
if (lines.length === 1) {
const line = lines[0];
result = _msgOne(line);
} else if (lines.length < AGGREGATE_MIN) {
result = _msgNamed(lines);
} else {
result = _msgCount(lines);
}
return result;
}
/**
* Output a readable conventional commit message.
*
* Use the Conventional Commit type as the prefix if it is known, otherwise
* just use the description.
*/
export function _formatMsg(convCommitMsg: ConvCommitMsg) {
if (convCommitMsg.typePrefix === CONVENTIONAL_TYPE.UNKNOWN) {
return convCommitMsg.description;
}
return _joinWithColon(convCommitMsg.typePrefix, convCommitMsg.description);
}
/**
* Generate a new commit message and format it as a string.
*
* @param lines An array of values describing file change from Git output.
* e.g. ["A baz.txt"]
*/
export function _newMsg(lines: string[]) {
const convCommitMsg = _msgFromChanges(lines);
return _formatMsg(convCommitMsg);
}
/**
* Combine old and generated values as a single commit message.
*
* @param autoMsgPieces Auto-generated new commit message pieces.
* @param oldMsgPieces The original commit message pieces.
*/
export function _joinOldAndNew(
autoMsgPieces: ConvCommitMsg,
oldMsgPieces: MsgPieces,
): string {
let typePrefix = "";
if (oldMsgPieces.typePrefix) {
typePrefix = oldMsgPieces.typePrefix;
} else if (autoMsgPieces.typePrefix !== CONVENTIONAL_TYPE.UNKNOWN) {
typePrefix = autoMsgPieces.typePrefix;
}
const descResult = _joinWithSpace(
autoMsgPieces.description,
oldMsgPieces.description,
);
if (!typePrefix) {
return descResult;
}
const prefix = _joinWithSpace(oldMsgPieces.customPrefix, typePrefix);
return _joinWithColon(prefix, descResult);
}
/**
* Create a commit message using an existing message and generated pieces.
*
* The point is to always use the new description, but respect the old
* description.
*
* An old type (possibly manually generated) must take preference over a
* generated one.
*
* See the "common scenarios" part of `prepareCommitMsg.test.ts` test spec.
*
* @param autoType The Conventional Commit type to use, as auto-generated by the
* extension, based on changed files.
* @param autoDesc A description of file changes, also auto-generated.
* @param oldMsg Value that exists in the commit message box at the time the
* extension is run, whether typed manually or generated previously by the
* extension. It could be a mix of custom prefix, type prefix, and
* description.
*/
export function _combineOldAndNew(
autoType: CONVENTIONAL_TYPE,
autoDesc: string,
oldMsg: string,
): string {
if (!oldMsg) {
const autoCommitMsg: ConvCommitMsg = {
typePrefix: autoType,
description: autoDesc,
};
return _formatMsg(autoCommitMsg);
}
const oldMsgPieces = splitMsg(oldMsg);
const autoMsgPieces: ConvCommitMsg = {
typePrefix: autoType,
description: autoDesc,
};
return _joinOldAndNew(autoMsgPieces, oldMsgPieces);
}
/**
* Generate commit message using existing message and new generated message.
*
* High-level function to process file changes and an old message, to generate a
* replacement commit message.
*
* @param lines An array of values describing file change from Git output.
* e.g. ["A baz.txt"]
* @param oldMsg Existing commit message.
*/
export function _generateMsgWithOld(lines: string[], oldMsg: string) {
if (oldMsg === "") {
throw new Error(
"`oldMsg` must be non-empty here, or use `generateNewMsg` instead.",
);
}
const { typePrefix, description } = _msgFromChanges(lines);
return _combineOldAndNew(typePrefix, description, oldMsg);
}
/**
* Generate commit message.
*
* A public wrapper function to allow an existing message to be set.
*
* @param lines An array of values describing file change from Git output.
* e.g. ["A baz.txt"]
* @param oldMsg Existing commit message.This could be the current commit
* message value in the UI box (which might be a commit message template that
* VS Code has filled in), or a commit message template read from a file in
* the case of a hook flow without VS Code.
*/
export function generateMsg(lines: string[], oldMsg?: string): string {
if (!oldMsg) {
return _newMsg(lines);
}
return _generateMsgWithOld(lines, oldMsg);
}