-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathprepareCommitMsg.ts
More file actions
231 lines (200 loc) · 6.78 KB
/
prepareCommitMsg.ts
File metadata and controls
231 lines (200 loc) · 6.78 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
/**
* 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 { lookupDiffIndexAction } 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 { 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, in case only one item is set.
*/
export function _cleanJoin(first: string, second: string) {
first = first.trim();
second = second.trim();
if (first === second) {
return first;
}
return `${first} ${second}`.trim();
}
/**
* Determine the Conventional Commit type prefix for a file change.
*/
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.
*/
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 prefix = _prefixFromChange(line),
description = oneChange(line);
return { prefix, description };
}
/**
* Get single Conventional Commit type prefix from multiple items given.
*
* 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-package changes but you know it does
* in this case because it changed with the lock file.
*/
function _collapse(types: CONVENTIONAL_TYPE[]) {
if (equal(types)) {
return types[0];
}
if (types.includes(CONVENTIONAL_TYPE.BUILD_DEPENDENCIES)) {
return CONVENTIONAL_TYPE.BUILD_DEPENDENCIES;
}
return CONVENTIONAL_TYPE.UNKNOWN;
}
/**
* 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.
*/
export function _msgNamed(lines: string[]): ConvCommitMsg {
const conventions = lines.map(_prefixFromChange);
const prefix = _collapse(conventions);
const changes = lines.map(parseDiffIndex);
const description = namedFilesDesc(changes);
return { prefix, description };
}
/**
* Generate prefix and count description for multiple file changes.
*
* TODO: Use prefix.
*/
export function _msgCount(lines: string[]): ConvCommitMsg {
const prefix = CONVENTIONAL_TYPE.UNKNOWN;
const changes = lines.map(parseDiffIndex);
const description = countFilesDesc(changes);
return { prefix, description };
}
/**
* Generate message from changes to one or more files.
*
* @param lines Lines from the `git diff-index` function, describing changes to files.
*
* @returns Conventional Commit 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.
*/
export function _formatMsg(convCommitMsg: ConvCommitMsg) {
if (convCommitMsg.prefix === CONVENTIONAL_TYPE.UNKNOWN) {
return convCommitMsg.description;
}
return `${convCommitMsg.prefix}: ${convCommitMsg.description}`;
}
/**
* Generate a new commit message and format it as a string.
*/
export function _newMsg(lines: string[]) {
const convCommitMsg = _msgFromChanges(lines);
return _formatMsg(convCommitMsg);
}
/**
* 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 What 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 and description.
*/
export function _combineOldAndNew(
autoType: CONVENTIONAL_TYPE,
autoDesc: string,
oldMsg?: string
) {
if (!oldMsg) {
const convCommitMsg = { prefix: autoType, description: autoDesc };
return _formatMsg(convCommitMsg);
}
const {
customPrefix: oldCustomPrefix,
typePrefix: oldType,
description: oldDesc,
} = splitMsg(oldMsg);
const descResult = _cleanJoin(autoDesc, oldDesc);
if (oldType) {
return `${_cleanJoin(oldCustomPrefix, oldType)}: ${descResult}`;
}
if (autoType !== CONVENTIONAL_TYPE.UNKNOWN) {
return `${_cleanJoin(oldCustomPrefix, autoType)}: ${descResult}`;
}
return descResult;
}
/**
* 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.
*/
export function _generateMsgWithOld(lines: string[], oldMsg: string) {
if (!oldMsg) {
throw new Error(
"`oldMsg` must be non-empty - or use `generateNewMsg` instead."
);
}
const { prefix, description } = _msgFromChanges(lines);
return _combineOldAndNew(prefix, description, oldMsg);
}
/**
* Generate commit message.
*
* This is a public wrapper function to allow an existing message to be set or not.
*
* Old message 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 (built-in Git functionality).
*
* @param lines A list of text values describing how files changes.
*/
export function generateMsg(lines: string[], oldMsg?: string): string {
if (!oldMsg) {
return _newMsg(lines);
}
return _generateMsgWithOld(lines, oldMsg);
}