@mcarvin/smart-diff 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -10
- package/dist/index.cjs +372 -281
- package/dist/index.cjs.map +1 -1
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.cjs.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.min.umd.js +1 -1
- package/dist/index.min.umd.js.map +1 -1
- package/dist/index.mjs +372 -281
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +372 -281
- package/dist/index.umd.js.map +1 -1
- package/dist/typings/{src/ai/aiSummary.d.ts → ai/aiConstants.d.ts} +1 -17
- package/dist/typings/ai/aiSummary.d.ts +2 -17
- package/dist/typings/ai/aiTypes.d.ts +21 -0
- package/dist/typings/git/commitMessageFilter.d.ts +2 -0
- package/dist/typings/git/diffGitStatus.d.ts +3 -0
- package/dist/typings/git/diffNameStatusParse.d.ts +8 -0
- package/dist/typings/git/diffNumstatParse.d.ts +4 -0
- package/dist/typings/git/diffPathspecs.d.ts +2 -0
- package/dist/typings/git/diffSummaryBuild.d.ts +2 -0
- package/dist/typings/git/diffSummaryParse.d.ts +2 -0
- package/dist/typings/git/diffTypes.d.ts +31 -0
- package/dist/typings/git/gitDiff.d.ts +5 -33
- package/dist/typings/git/gitDiffOps.d.ts +8 -0
- package/dist/typings/index.d.ts +9 -8
- package/package.json +1 -1
- package/dist/typings/src/ai/openAIConfig.d.ts +0 -21
- package/dist/typings/src/git/gitDiff.d.ts +0 -33
- package/dist/typings/src/index.d.ts +0 -24
- package/dist/typings/test/aiSummary.spec.d.ts +0 -1
- package/dist/typings/test/gitDiff.async.spec.d.ts +0 -1
- package/dist/typings/test/gitDiff.spec.d.ts +0 -1
- package/dist/typings/test/index.spec.d.ts +0 -1
- package/dist/typings/test/openAIConfig.spec.d.ts +0 -1
- package/dist/typings/test/openAiSdk.spec.d.ts +0 -1
- package/dist/typings/test/summarizeGitDiff.spec.d.ts +0 -1
package/dist/index.umd.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
function resolveLlmBaseUrl() {
|
|
40
40
|
var _a, _b, _c;
|
|
41
|
-
return (_b = (_a = process.env.LLM_BASE_URL) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : (_c = process.env.OPENAI_BASE_URL) === null || _c === void 0 ? void 0 : _c.trim();
|
|
41
|
+
return ((_b = (_a = process.env.LLM_BASE_URL) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : (_c = process.env.OPENAI_BASE_URL) === null || _c === void 0 ? void 0 : _c.trim());
|
|
42
42
|
}
|
|
43
43
|
function parseHeaderJsonObject(raw) {
|
|
44
44
|
const trimmed = raw === null || raw === void 0 ? void 0 : raw.trim();
|
|
@@ -46,12 +46,14 @@
|
|
|
46
46
|
return {};
|
|
47
47
|
try {
|
|
48
48
|
const parsed = JSON.parse(trimmed);
|
|
49
|
-
if (typeof parsed !==
|
|
49
|
+
if (typeof parsed !== "object" ||
|
|
50
|
+
parsed === null ||
|
|
51
|
+
Array.isArray(parsed)) {
|
|
50
52
|
return {};
|
|
51
53
|
}
|
|
52
54
|
const out = {};
|
|
53
55
|
for (const [key, value] of Object.entries(parsed)) {
|
|
54
|
-
if (typeof value ===
|
|
56
|
+
if (typeof value === "string" && value.length > 0) {
|
|
55
57
|
out[key] = value;
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -68,7 +70,7 @@
|
|
|
68
70
|
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
69
71
|
}
|
|
70
72
|
function findAuthorizationHeaderName(headers) {
|
|
71
|
-
return Object.keys(headers).find((k) => k.toLowerCase() ===
|
|
73
|
+
return Object.keys(headers).find((k) => k.toLowerCase() === "authorization");
|
|
72
74
|
}
|
|
73
75
|
function stripBearerPrefix(value) {
|
|
74
76
|
var _a;
|
|
@@ -111,7 +113,7 @@
|
|
|
111
113
|
var _a, _b, _c, _d, _e;
|
|
112
114
|
const baseURL = resolveLlmBaseUrl();
|
|
113
115
|
const mergedHeaders = (_a = parseLlmDefaultHeadersFromEnv()) !== null && _a !== void 0 ? _a : {};
|
|
114
|
-
const envApiKey = (_e = (_c = (_b = process.env.LLM_API_KEY) === null || _b === void 0 ? void 0 : _b.trim()) !== null && _c !== void 0 ? _c : (_d = process.env.OPENAI_API_KEY) === null || _d === void 0 ? void 0 : _d.trim()) !== null && _e !== void 0 ? _e :
|
|
116
|
+
const envApiKey = (_e = (_c = (_b = process.env.LLM_API_KEY) === null || _b === void 0 ? void 0 : _b.trim()) !== null && _c !== void 0 ? _c : (_d = process.env.OPENAI_API_KEY) === null || _d === void 0 ? void 0 : _d.trim()) !== null && _e !== void 0 ? _e : "";
|
|
115
117
|
let defaultHeaders;
|
|
116
118
|
let apiKey = envApiKey;
|
|
117
119
|
if (apiKey.length === 0) {
|
|
@@ -119,12 +121,16 @@
|
|
|
119
121
|
if (split.apiKeyFromAuthHeader) {
|
|
120
122
|
apiKey = split.apiKeyFromAuthHeader;
|
|
121
123
|
}
|
|
122
|
-
defaultHeaders =
|
|
124
|
+
defaultHeaders =
|
|
125
|
+
Object.keys(split.defaultHeaders).length > 0
|
|
126
|
+
? split.defaultHeaders
|
|
127
|
+
: undefined;
|
|
123
128
|
}
|
|
124
129
|
else {
|
|
125
|
-
defaultHeaders =
|
|
130
|
+
defaultHeaders =
|
|
131
|
+
Object.keys(mergedHeaders).length > 0 ? mergedHeaders : undefined;
|
|
126
132
|
}
|
|
127
|
-
return Object.assign(Object.assign({ apiKey: apiKey.length > 0 ? apiKey :
|
|
133
|
+
return Object.assign(Object.assign({ apiKey: apiKey.length > 0 ? apiKey : "unused" }, (baseURL ? { baseURL } : {})), (defaultHeaders ? { defaultHeaders } : {}));
|
|
128
134
|
}
|
|
129
135
|
function createOpenAiLikeClient() {
|
|
130
136
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -134,9 +140,22 @@
|
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
const DEFAULT_LLM_MAX_DIFF_CHARS = 120000;
|
|
143
|
+
const DEFAULT_GIT_DIFF_SYSTEM_PROMPT = `You are a senior software engineer helping developers understand code and configuration changes from the git context they supplied.
|
|
144
|
+
You receive: commit subject lines (when available), changed file paths, and unified git patch(es)—either one range diff or concatenated per-commit patches, depending on how the diff was produced. Patches may be truncated mid-section with an explicit marker—do not infer changes beyond visible lines.
|
|
145
|
+
Explain what changed in terms of behavior, APIs, data, configuration, security, and operational risk. Tie claims to the patch when possible.
|
|
146
|
+
Produce a concise, developer-focused summary in Markdown.
|
|
147
|
+
Use sections that fit the change (for example: Highlights, Breaking or risky changes, API / contract changes, Data & schema, Configuration & infra, Security & auth, Tests & quality). Omit empty sections.
|
|
148
|
+
Group related changes; do not list every individual file. When multiple commits appear in the context, briefly separate notable themes by commit when helpful.
|
|
149
|
+
If the user message includes a Team line, use that exact team name in the summary title (for example: "## <Team> – Change summary" or similar).`;
|
|
150
|
+
const LLM_GATEWAY_REQUIRED_MESSAGE = "No LLM gateway configured. Set OPENAI_API_KEY or LLM_API_KEY, and/or LLM_BASE_URL or OPENAI_BASE_URL, " +
|
|
151
|
+
"and/or JSON in OPENAI_DEFAULT_HEADERS or LLM_DEFAULT_HEADERS. " +
|
|
152
|
+
"Alternatively pass openAiClientProvider to generateSummary or summarizeGitDiff.";
|
|
153
|
+
|
|
137
154
|
function resolveLlmMaxDiffChars(cliOverride) {
|
|
138
155
|
var _a;
|
|
139
|
-
if (cliOverride !== undefined &&
|
|
156
|
+
if (cliOverride !== undefined &&
|
|
157
|
+
Number.isFinite(cliOverride) &&
|
|
158
|
+
cliOverride > 0) {
|
|
140
159
|
return Math.trunc(cliOverride);
|
|
141
160
|
}
|
|
142
161
|
const raw = (_a = process.env.LLM_MAX_DIFF_CHARS) === null || _a === void 0 ? void 0 : _a.trim();
|
|
@@ -155,70 +174,67 @@
|
|
|
155
174
|
const marker = `\n\n--- TRUNCATED: unified diff was ${diffText.length} characters; only the first ${maxChars} were sent. Narrow the ref range, adjust commit/path filters, or raise maxDiffChars / LLM_MAX_DIFF_CHARS only if your model context allows. ---\n`;
|
|
156
175
|
return diffText.slice(0, maxChars) + marker;
|
|
157
176
|
}
|
|
158
|
-
|
|
159
|
-
You receive: commit subject lines (when available), changed file paths, and unified git patch(es)—either one range diff or concatenated per-commit patches, depending on how the diff was produced. Patches may be truncated mid-section with an explicit marker—do not infer changes beyond visible lines.
|
|
160
|
-
Explain what changed in terms of behavior, APIs, data, configuration, security, and operational risk. Tie claims to the patch when possible.
|
|
161
|
-
Produce a concise, developer-focused summary in Markdown.
|
|
162
|
-
Use sections that fit the change (for example: Highlights, Breaking or risky changes, API / contract changes, Data & schema, Configuration & infra, Security & auth, Tests & quality). Omit empty sections.
|
|
163
|
-
Group related changes; do not list every individual file. When multiple commits appear in the context, briefly separate notable themes by commit when helpful.
|
|
164
|
-
If the user message includes a Team line, use that exact team name in the summary title (for example: "## <Team> – Change summary" or similar).`;
|
|
165
|
-
const LLM_GATEWAY_REQUIRED_MESSAGE = 'No LLM gateway configured. Set OPENAI_API_KEY or LLM_API_KEY, and/or LLM_BASE_URL or OPENAI_BASE_URL, ' +
|
|
166
|
-
'and/or JSON in OPENAI_DEFAULT_HEADERS or LLM_DEFAULT_HEADERS. ' +
|
|
167
|
-
'Alternatively pass openAiClientProvider to generateSummary or summarizeGitDiff.';
|
|
168
|
-
function generateSummary(diffText, fileNames, commits, flags, openAiClientProvider, diffSummary) {
|
|
177
|
+
function generateSummary(input) {
|
|
169
178
|
return __awaiter(this, void 0, void 0, function* () {
|
|
170
179
|
var _a, _b;
|
|
180
|
+
const { diffText, fileNames, commits, flags, openAiClientProvider, diffSummary, } = input;
|
|
171
181
|
if (!shouldUseLlmGateway() && openAiClientProvider === undefined) {
|
|
172
182
|
throw new Error(LLM_GATEWAY_REQUIRED_MESSAGE);
|
|
173
183
|
}
|
|
174
184
|
const maxDiffChars = resolveLlmMaxDiffChars(flags.maxDiffChars);
|
|
175
185
|
const diffForLlm = truncateUnifiedDiffForLlm(diffText, maxDiffChars);
|
|
176
186
|
const userContent = buildOpenAiUserContent(flags, commits, fileNames, diffForLlm, diffSummary);
|
|
177
|
-
return callOpenAi(userContent, (_a = flags.model) !== null && _a !== void 0 ? _a :
|
|
187
|
+
return callOpenAi(userContent, (_a = flags.model) !== null && _a !== void 0 ? _a : "gpt-4o-mini", (_b = flags.systemPrompt) !== null && _b !== void 0 ? _b : DEFAULT_GIT_DIFF_SYSTEM_PROMPT, openAiClientProvider !== null && openAiClientProvider !== void 0 ? openAiClientProvider : (() => __awaiter(this, void 0, void 0, function* () { return createOpenAiLikeClient(); })));
|
|
178
188
|
});
|
|
179
189
|
}
|
|
180
190
|
function formatRegexFilterLines(flags) {
|
|
181
191
|
var _a, _b;
|
|
182
|
-
const includes = ((_a = flags.commitMessageIncludeRegexes) !== null && _a !== void 0 ? _a : [])
|
|
183
|
-
|
|
192
|
+
const includes = ((_a = flags.commitMessageIncludeRegexes) !== null && _a !== void 0 ? _a : [])
|
|
193
|
+
.map((s) => s.trim())
|
|
194
|
+
.filter(Boolean);
|
|
195
|
+
const excludes = ((_b = flags.commitMessageExcludeRegexes) !== null && _b !== void 0 ? _b : [])
|
|
196
|
+
.map((s) => s.trim())
|
|
197
|
+
.filter(Boolean);
|
|
184
198
|
const incLine = includes.length > 0
|
|
185
|
-
? `Commit message include regexes (OR): ${includes.map((r) => JSON.stringify(r)).join(
|
|
186
|
-
:
|
|
199
|
+
? `Commit message include regexes (OR): ${includes.map((r) => JSON.stringify(r)).join(", ")}\n`
|
|
200
|
+
: "";
|
|
187
201
|
const excLine = excludes.length > 0
|
|
188
|
-
? `Commit message exclude regexes: ${excludes.map((r) => JSON.stringify(r)).join(
|
|
189
|
-
:
|
|
202
|
+
? `Commit message exclude regexes: ${excludes.map((r) => JSON.stringify(r)).join(", ")}\n`
|
|
203
|
+
: "";
|
|
190
204
|
if (!incLine && !excLine) {
|
|
191
|
-
return
|
|
205
|
+
return "Commit message filters: none.\nGit context shape: single unified diff for the full ref range.\n";
|
|
192
206
|
}
|
|
193
207
|
return (`${incLine}${excLine}` +
|
|
194
|
-
|
|
208
|
+
"Git context shape: concatenated per-commit unified patches for commits that pass the message filters.\n");
|
|
195
209
|
}
|
|
196
210
|
function buildOpenAiUserContent(flags, commits, fileNames, diffText, diffSummary) {
|
|
197
211
|
var _a, _b;
|
|
198
212
|
const from = flags.from;
|
|
199
|
-
const to = (_a = flags.to) !== null && _a !== void 0 ? _a :
|
|
213
|
+
const to = (_a = flags.to) !== null && _a !== void 0 ? _a : "HEAD";
|
|
200
214
|
const team = (_b = flags.team) === null || _b === void 0 ? void 0 : _b.trim();
|
|
201
215
|
const ts = new Date().toISOString();
|
|
202
|
-
const teamLine = team ? `Team: ${team}\n` :
|
|
216
|
+
const teamLine = team ? `Team: ${team}\n` : "";
|
|
203
217
|
const filterBlock = formatRegexFilterLines(flags);
|
|
204
218
|
const commitBlock = commits.length > 0
|
|
205
|
-
? commits
|
|
206
|
-
|
|
207
|
-
|
|
219
|
+
? commits
|
|
220
|
+
.map((c) => `- ${c.hash.slice(0, 7)} ${c.message.replace(/\r?\n/g, " ")}`)
|
|
221
|
+
.join("\n")
|
|
222
|
+
: "- (no commits in range after filtering)";
|
|
223
|
+
const pathsBlock = fileNames.length > 0 ? fileNames.join("\n") : "(no paths in diff scope)";
|
|
208
224
|
const structuredDiffSection = diffSummary
|
|
209
225
|
? `=== Structured git context (JSON summary) ===\n${JSON.stringify(diffSummary, null, 2)}\n\n`
|
|
210
|
-
:
|
|
226
|
+
: "";
|
|
211
227
|
return (`${teamLine}` +
|
|
212
228
|
`Date: ${ts}\n\n` +
|
|
213
229
|
`Git refs: ${from}..${to}\n` +
|
|
214
230
|
filterBlock +
|
|
215
|
-
|
|
216
|
-
|
|
231
|
+
"\n" +
|
|
232
|
+
"=== Included commits (subject lines) ===\n" +
|
|
217
233
|
`${commitBlock}\n\n` +
|
|
218
|
-
|
|
234
|
+
"=== Changed paths ===\n" +
|
|
219
235
|
`${pathsBlock}\n\n` +
|
|
220
236
|
structuredDiffSection +
|
|
221
|
-
|
|
237
|
+
"=== Git context (unified diff(s); patches may be truncated with an explicit marker) ===\n" +
|
|
222
238
|
diffText);
|
|
223
239
|
}
|
|
224
240
|
function callOpenAi(userContent, model, systemPrompt, openAiClientProvider) {
|
|
@@ -232,11 +248,11 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
232
248
|
model,
|
|
233
249
|
messages: [
|
|
234
250
|
{
|
|
235
|
-
role:
|
|
251
|
+
role: "system",
|
|
236
252
|
content: systemPrompt,
|
|
237
253
|
},
|
|
238
254
|
{
|
|
239
|
-
role:
|
|
255
|
+
role: "user",
|
|
240
256
|
content: userContent,
|
|
241
257
|
},
|
|
242
258
|
],
|
|
@@ -244,63 +260,25 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
244
260
|
max_tokens: maxTokens,
|
|
245
261
|
});
|
|
246
262
|
const typedResponse = response;
|
|
247
|
-
const text = (_f = (_e = (_d = (_c = (_b = typedResponse.choices) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.trim()) !== null && _f !== void 0 ? _f :
|
|
248
|
-
return text.length > 0 ? text :
|
|
263
|
+
const text = (_f = (_e = (_d = (_c = (_b = typedResponse.choices) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.trim()) !== null && _f !== void 0 ? _f : "";
|
|
264
|
+
return text.length > 0 ? text : "No summary generated by OpenAI.";
|
|
249
265
|
});
|
|
250
266
|
}
|
|
251
267
|
|
|
252
|
-
function createGitClient(cwd = process.cwd()) {
|
|
253
|
-
return simpleGit.simpleGit(cwd);
|
|
254
|
-
}
|
|
255
|
-
function getCommits(git, from, to) {
|
|
256
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
257
|
-
const logResult = yield git.log({ from, to });
|
|
258
|
-
return logResult.all;
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
function compileRegex(pattern, label) {
|
|
262
|
-
try {
|
|
263
|
-
return new RegExp(pattern, 'i');
|
|
264
|
-
}
|
|
265
|
-
catch (_a) {
|
|
266
|
-
throw new Error(`Invalid ${label} regular expression: ${JSON.stringify(pattern)}`);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
function filterCommitsByMessageRegexes(commits, includePatterns, excludePatterns) {
|
|
270
|
-
const includes = (includePatterns !== null && includePatterns !== void 0 ? includePatterns : []).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
271
|
-
const excludes = (excludePatterns !== null && excludePatterns !== void 0 ? excludePatterns : []).map((p) => p.trim()).filter((p) => p.length > 0);
|
|
272
|
-
const includeRes = includes.map((p, i) => compileRegex(p, `commit message include pattern[${i}]`));
|
|
273
|
-
const excludeRes = excludes.map((p, i) => compileRegex(p, `commit message exclude pattern[${i}]`));
|
|
274
|
-
return commits.filter((c) => {
|
|
275
|
-
for (const ex of excludeRes) {
|
|
276
|
-
if (ex.test(c.message))
|
|
277
|
-
return false;
|
|
278
|
-
}
|
|
279
|
-
if (includeRes.length > 0 && !includeRes.some((r) => r.test(c.message)))
|
|
280
|
-
return false;
|
|
281
|
-
return true;
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
function getRepoRoot(git) {
|
|
285
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
286
|
-
const root = yield git.revparse(['--show-toplevel']);
|
|
287
|
-
return root.trim();
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
268
|
function normalizeRepoRelativePath(p) {
|
|
291
|
-
const trimmed = p.trim().replace(/\\/g,
|
|
292
|
-
const noLeading = trimmed.replace(/^\/+/,
|
|
293
|
-
const noTrailingSlash = noLeading.replace(/\/+$/,
|
|
294
|
-
return noTrailingSlash.length > 0 ? noTrailingSlash :
|
|
269
|
+
const trimmed = p.trim().replace(/\\/g, "/");
|
|
270
|
+
const noLeading = trimmed.replace(/^\/+/, "");
|
|
271
|
+
const noTrailingSlash = noLeading.replace(/\/+$/, "");
|
|
272
|
+
return noTrailingSlash.length > 0 ? noTrailingSlash : ".";
|
|
295
273
|
}
|
|
296
274
|
function assertPathUnderRepo(repoRoot, userPath) {
|
|
297
275
|
const abs = node_path.resolve(repoRoot, userPath);
|
|
298
276
|
const rel = node_path.relative(repoRoot, abs);
|
|
299
|
-
if (rel ===
|
|
277
|
+
if (rel === "..") {
|
|
300
278
|
throw new Error(`Path escapes repository root: ${JSON.stringify(userPath)}`);
|
|
301
279
|
}
|
|
302
280
|
const segments = rel.split(/[/\\]/);
|
|
303
|
-
if (segments.includes(
|
|
281
|
+
if (segments.includes("..")) {
|
|
304
282
|
throw new Error(`Path escapes repository root: ${JSON.stringify(userPath)}`);
|
|
305
283
|
}
|
|
306
284
|
}
|
|
@@ -308,9 +286,13 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
308
286
|
var _a, _b, _c, _d;
|
|
309
287
|
const includeRaw = (_b = (_a = pathFilter === null || pathFilter === void 0 ? void 0 : pathFilter.includeFolders) === null || _a === void 0 ? void 0 : _a.filter((p) => p.trim().length > 0)) !== null && _b !== void 0 ? _b : [];
|
|
310
288
|
const excludeRaw = (_d = (_c = pathFilter === null || pathFilter === void 0 ? void 0 : pathFilter.excludeFolders) === null || _c === void 0 ? void 0 : _c.filter((p) => p.trim().length > 0)) !== null && _d !== void 0 ? _d : [];
|
|
311
|
-
const includes = includeRaw
|
|
312
|
-
|
|
313
|
-
|
|
289
|
+
const includes = includeRaw
|
|
290
|
+
.map(normalizeRepoRelativePath)
|
|
291
|
+
.filter((p) => p !== "." && p !== "");
|
|
292
|
+
const excludes = excludeRaw
|
|
293
|
+
.map(normalizeRepoRelativePath)
|
|
294
|
+
.filter((p) => p !== "." && p !== "");
|
|
295
|
+
const toValidate = includes.length > 0 ? includes : ["."];
|
|
314
296
|
for (const inc of toValidate) {
|
|
315
297
|
assertPathUnderRepo(repoRoot, inc);
|
|
316
298
|
}
|
|
@@ -319,7 +301,7 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
319
301
|
}
|
|
320
302
|
const specs = [];
|
|
321
303
|
if (includes.length === 0) {
|
|
322
|
-
specs.push(
|
|
304
|
+
specs.push(".");
|
|
323
305
|
}
|
|
324
306
|
else {
|
|
325
307
|
for (const inc of includes) {
|
|
@@ -331,123 +313,99 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
331
313
|
}
|
|
332
314
|
return specs;
|
|
333
315
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
343
|
-
const { specs } = yield getDiffPathContext(git, pathFilter, repoRootOverride);
|
|
344
|
-
if (!filterByCommits) {
|
|
345
|
-
return git.diff([`${from}..${to}`, '--', ...specs]);
|
|
346
|
-
}
|
|
347
|
-
const patches = yield Promise.all(commits.map((c) => git.diff([`${c.hash}^!`, '--', ...specs])));
|
|
348
|
-
return patches.filter(Boolean).join('\n');
|
|
349
|
-
});
|
|
316
|
+
|
|
317
|
+
function compileRegex(pattern, label) {
|
|
318
|
+
try {
|
|
319
|
+
return new RegExp(pattern, "i");
|
|
320
|
+
}
|
|
321
|
+
catch (_a) {
|
|
322
|
+
throw new Error(`Invalid ${label} regular expression: ${JSON.stringify(pattern)}`);
|
|
323
|
+
}
|
|
350
324
|
}
|
|
351
|
-
function
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return buildDiffSummaryFromGitOutputs(nameOutput, numOutput);
|
|
360
|
-
}
|
|
361
|
-
const pairs = yield Promise.all(commits.map((c) => __awaiter(this, void 0, void 0, function* () {
|
|
362
|
-
const range = `${c.hash}^!`;
|
|
363
|
-
const [numOutput, nameOutput] = yield Promise.all([
|
|
364
|
-
git.diff(['--numstat', range, '--', ...specs]),
|
|
365
|
-
git.diff(['--name-status', range, '--', ...specs]),
|
|
366
|
-
]);
|
|
367
|
-
return { numOutput, nameOutput };
|
|
368
|
-
})));
|
|
369
|
-
const nameJoined = pairs
|
|
370
|
-
.map((p) => p.nameOutput)
|
|
371
|
-
.filter(Boolean)
|
|
372
|
-
.join('\n');
|
|
373
|
-
const numJoined = pairs
|
|
374
|
-
.map((p) => p.numOutput)
|
|
375
|
-
.filter(Boolean)
|
|
376
|
-
.join('\n');
|
|
377
|
-
return buildDiffSummaryFromGitOutputs(nameJoined, numJoined);
|
|
378
|
-
});
|
|
325
|
+
function commitMessagePassesFilters(message, includeRes, excludeRes) {
|
|
326
|
+
for (const ex of excludeRes) {
|
|
327
|
+
if (ex.test(message))
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
if (includeRes.length > 0 && !includeRes.some((r) => r.test(message)))
|
|
331
|
+
return false;
|
|
332
|
+
return true;
|
|
379
333
|
}
|
|
380
|
-
function
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const fileSet = new Set();
|
|
391
|
-
yield Promise.all(commits.map((c) => __awaiter(this, void 0, void 0, function* () {
|
|
392
|
-
const output = yield git.show(['--name-only', '--pretty=format:', c.hash, '--', ...specs]);
|
|
393
|
-
output
|
|
394
|
-
.split(/\r?\n/)
|
|
395
|
-
.map((f) => f.trim())
|
|
396
|
-
.filter(Boolean)
|
|
397
|
-
.forEach((f) => fileSet.add(f));
|
|
398
|
-
})));
|
|
399
|
-
return Array.from(fileSet);
|
|
400
|
-
});
|
|
334
|
+
function filterCommitsByMessageRegexes(commits, includePatterns, excludePatterns) {
|
|
335
|
+
const includes = (includePatterns !== null && includePatterns !== void 0 ? includePatterns : [])
|
|
336
|
+
.map((p) => p.trim())
|
|
337
|
+
.filter((p) => p.length > 0);
|
|
338
|
+
const excludes = (excludePatterns !== null && excludePatterns !== void 0 ? excludePatterns : [])
|
|
339
|
+
.map((p) => p.trim())
|
|
340
|
+
.filter((p) => p.length > 0);
|
|
341
|
+
const includeRes = includes.map((p, i) => compileRegex(p, `commit message include pattern[${i}]`));
|
|
342
|
+
const excludeRes = excludes.map((p, i) => compileRegex(p, `commit message exclude pattern[${i}]`));
|
|
343
|
+
return commits.filter((c) => commitMessagePassesFilters(c.message, includeRes, excludeRes));
|
|
401
344
|
}
|
|
345
|
+
|
|
346
|
+
const GIT_STATUS_BY_FIRST_CHAR = {
|
|
347
|
+
A: "added",
|
|
348
|
+
D: "deleted",
|
|
349
|
+
R: "renamed",
|
|
350
|
+
C: "copied",
|
|
351
|
+
T: "type-changed",
|
|
352
|
+
M: "modified",
|
|
353
|
+
};
|
|
402
354
|
function mapGitStatus(statusCode) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (statusCode.startsWith('D'))
|
|
406
|
-
return 'deleted';
|
|
407
|
-
if (statusCode.startsWith('R'))
|
|
408
|
-
return 'renamed';
|
|
409
|
-
if (statusCode.startsWith('C'))
|
|
410
|
-
return 'copied';
|
|
411
|
-
if (statusCode.startsWith('T'))
|
|
412
|
-
return 'type-changed';
|
|
413
|
-
if (statusCode.startsWith('M'))
|
|
414
|
-
return 'modified';
|
|
415
|
-
return 'unknown';
|
|
355
|
+
var _a;
|
|
356
|
+
return (_a = GIT_STATUS_BY_FIRST_CHAR[statusCode.charAt(0)]) !== null && _a !== void 0 ? _a : "unknown";
|
|
416
357
|
}
|
|
417
358
|
function mergeStatus(existing, next) {
|
|
418
359
|
if (existing === next)
|
|
419
360
|
return existing;
|
|
420
|
-
const precedence = [
|
|
421
|
-
|
|
361
|
+
const precedence = [
|
|
362
|
+
"deleted",
|
|
363
|
+
"added",
|
|
364
|
+
"renamed",
|
|
365
|
+
"copied",
|
|
366
|
+
"type-changed",
|
|
367
|
+
"modified",
|
|
368
|
+
"unknown",
|
|
369
|
+
];
|
|
370
|
+
return precedence.indexOf(existing) <= precedence.indexOf(next)
|
|
371
|
+
? existing
|
|
372
|
+
: next;
|
|
422
373
|
}
|
|
423
|
-
|
|
374
|
+
|
|
375
|
+
function parseNameStatusLine(line) {
|
|
424
376
|
var _a;
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
continue;
|
|
430
|
-
const parts = line.split('\t');
|
|
431
|
-
if (parts.length < 2)
|
|
432
|
-
continue;
|
|
433
|
-
const statusToken = (_a = parts[0]) !== null && _a !== void 0 ? _a : '';
|
|
377
|
+
const parts = line.split("\t");
|
|
378
|
+
let entry = null;
|
|
379
|
+
if (parts.length >= 2) {
|
|
380
|
+
const statusToken = (_a = parts[0]) !== null && _a !== void 0 ? _a : "";
|
|
434
381
|
const status = mapGitStatus(statusToken);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
continue;
|
|
382
|
+
const isRenameOrCopy = statusToken.startsWith("R") || statusToken.startsWith("C");
|
|
383
|
+
if (isRenameOrCopy && parts.length >= 3) {
|
|
438
384
|
const oldPath = parts[1];
|
|
439
385
|
const newPath = parts[2];
|
|
440
|
-
if (oldPath
|
|
441
|
-
|
|
442
|
-
|
|
386
|
+
if (oldPath !== undefined && newPath !== undefined) {
|
|
387
|
+
entry = { path: newPath, status, oldPath };
|
|
388
|
+
}
|
|
443
389
|
}
|
|
444
|
-
else {
|
|
390
|
+
else if (!isRenameOrCopy) {
|
|
445
391
|
const pathOnly = parts[1];
|
|
446
|
-
if (pathOnly
|
|
447
|
-
|
|
448
|
-
|
|
392
|
+
if (pathOnly !== undefined) {
|
|
393
|
+
entry = { path: pathOnly, status };
|
|
394
|
+
}
|
|
449
395
|
}
|
|
450
396
|
}
|
|
397
|
+
return entry;
|
|
398
|
+
}
|
|
399
|
+
function parseNameStatusLines(nameStatusOutput) {
|
|
400
|
+
const entries = [];
|
|
401
|
+
for (const rawLine of nameStatusOutput.split(/\r?\n/)) {
|
|
402
|
+
const line = rawLine.trim();
|
|
403
|
+
if (!line)
|
|
404
|
+
continue;
|
|
405
|
+
const entry = parseNameStatusLine(line);
|
|
406
|
+
if (entry)
|
|
407
|
+
entries.push(entry);
|
|
408
|
+
}
|
|
451
409
|
return entries;
|
|
452
410
|
}
|
|
453
411
|
function mergeNameEntriesByPath(entries) {
|
|
@@ -467,6 +425,7 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
467
425
|
}
|
|
468
426
|
return byPath;
|
|
469
427
|
}
|
|
428
|
+
|
|
470
429
|
function numStatPathToLookupKey(pathField) {
|
|
471
430
|
const brace = /^(.*)\{(.+) => (.+)\}$/.exec(pathField);
|
|
472
431
|
if (!brace) {
|
|
@@ -476,107 +435,93 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
476
435
|
const toSeg = brace[3].trim();
|
|
477
436
|
return `${dirRaw}${toSeg}`;
|
|
478
437
|
}
|
|
438
|
+
function parseNumStatLine(line) {
|
|
439
|
+
var _a, _b;
|
|
440
|
+
const parts = line.split("\t");
|
|
441
|
+
if (parts.length < 3)
|
|
442
|
+
return null;
|
|
443
|
+
const addStr = (_a = parts[0]) !== null && _a !== void 0 ? _a : "";
|
|
444
|
+
const delStr = (_b = parts[1]) !== null && _b !== void 0 ? _b : "";
|
|
445
|
+
const pathField = parts.slice(2).join("\t");
|
|
446
|
+
const additions = addStr !== "-" ? Number.parseInt(addStr, 10) || 0 : 0;
|
|
447
|
+
const deletions = delStr !== "-" ? Number.parseInt(delStr, 10) || 0 : 0;
|
|
448
|
+
const key = numStatPathToLookupKey(pathField);
|
|
449
|
+
return { key, additions, deletions };
|
|
450
|
+
}
|
|
479
451
|
function accumulateNumStat(numStatOutput, into) {
|
|
480
|
-
var _a
|
|
452
|
+
var _a;
|
|
481
453
|
for (const rawLine of numStatOutput.split(/\r?\n/)) {
|
|
482
454
|
const line = rawLine.trim();
|
|
483
455
|
if (!line)
|
|
484
456
|
continue;
|
|
485
|
-
const
|
|
486
|
-
if (
|
|
457
|
+
const parsed = parseNumStatLine(line);
|
|
458
|
+
if (!parsed)
|
|
487
459
|
continue;
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const key = numStatPathToLookupKey(pathField);
|
|
494
|
-
const prev = (_c = into.get(key)) !== null && _c !== void 0 ? _c : { additions: 0, deletions: 0 };
|
|
495
|
-
into.set(key, { additions: prev.additions + additions, deletions: prev.deletions + deletions });
|
|
460
|
+
const prev = (_a = into.get(parsed.key)) !== null && _a !== void 0 ? _a : { additions: 0, deletions: 0 };
|
|
461
|
+
into.set(parsed.key, {
|
|
462
|
+
additions: prev.additions + parsed.additions,
|
|
463
|
+
deletions: prev.deletions + parsed.deletions,
|
|
464
|
+
});
|
|
496
465
|
}
|
|
497
466
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
467
|
+
|
|
468
|
+
function parseTabDiffSummaryLine(line) {
|
|
469
|
+
var _a;
|
|
470
|
+
const parts = line.split("\t");
|
|
471
|
+
if (parts.length < 3)
|
|
472
|
+
return null;
|
|
473
|
+
const statusToken = (_a = parts.shift()) !== null && _a !== void 0 ? _a : "";
|
|
474
|
+
const status = mapGitStatus(statusToken);
|
|
475
|
+
const add0 = parts[0];
|
|
476
|
+
const del0 = parts[1];
|
|
477
|
+
const additions = add0 && add0 !== "-" ? Number.parseInt(add0, 10) || 0 : 0;
|
|
478
|
+
const deletions = del0 && del0 !== "-" ? Number.parseInt(del0, 10) || 0 : 0;
|
|
479
|
+
if (parts.length === 3) {
|
|
480
|
+
return { status, additions, deletions, newPath: parts[2] };
|
|
481
|
+
}
|
|
482
|
+
if (parts.length === 4) {
|
|
483
|
+
return {
|
|
484
|
+
status,
|
|
485
|
+
additions,
|
|
486
|
+
deletions,
|
|
487
|
+
oldPath: parts[2],
|
|
488
|
+
newPath: parts[3],
|
|
489
|
+
};
|
|
514
490
|
}
|
|
491
|
+
return null;
|
|
515
492
|
}
|
|
516
|
-
function
|
|
517
|
-
var _a;
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
493
|
+
function mergeParsedDiffSummaryLine(fileMap, p) {
|
|
494
|
+
var _a, _b;
|
|
495
|
+
const { newPath, status, additions, deletions, oldPath } = p;
|
|
496
|
+
const existing = fileMap.get(newPath);
|
|
497
|
+
if (existing) {
|
|
498
|
+
existing.additions += additions;
|
|
499
|
+
existing.deletions += deletions;
|
|
500
|
+
existing.status = mergeStatus(existing.status, status);
|
|
501
|
+
if (oldPath)
|
|
502
|
+
existing.oldPath = (_a = existing.oldPath) !== null && _a !== void 0 ? _a : oldPath;
|
|
503
|
+
existing.newPath = (_b = existing.newPath) !== null && _b !== void 0 ? _b : newPath;
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
fileMap.set(newPath, {
|
|
507
|
+
path: newPath,
|
|
508
|
+
status,
|
|
509
|
+
additions,
|
|
510
|
+
deletions,
|
|
511
|
+
oldPath,
|
|
512
|
+
newPath: oldPath ? newPath : undefined,
|
|
513
|
+
});
|
|
531
514
|
}
|
|
532
|
-
return parseDiffSummary(syntheticLines.join('\n'));
|
|
533
515
|
}
|
|
534
516
|
function parseDiffSummary(diffOutput) {
|
|
535
|
-
var _a, _b, _c;
|
|
536
517
|
const fileMap = new Map();
|
|
537
518
|
for (const rawLine of diffOutput.split(/\r?\n/)) {
|
|
538
519
|
const line = rawLine.trim();
|
|
539
520
|
if (!line)
|
|
540
521
|
continue;
|
|
541
|
-
const
|
|
542
|
-
if (
|
|
543
|
-
|
|
544
|
-
const statusToken = (_a = parts.shift()) !== null && _a !== void 0 ? _a : '';
|
|
545
|
-
const status = mapGitStatus(statusToken);
|
|
546
|
-
const additions = parts[0] && parts[0] !== '-' ? Number.parseInt(parts[0], 10) || 0 : 0;
|
|
547
|
-
const deletions = parts[1] && parts[1] !== '-' ? Number.parseInt(parts[1], 10) || 0 : 0;
|
|
548
|
-
let oldPath;
|
|
549
|
-
let newPath;
|
|
550
|
-
if (parts.length === 3) {
|
|
551
|
-
newPath = parts[2];
|
|
552
|
-
}
|
|
553
|
-
else if (parts.length === 4) {
|
|
554
|
-
oldPath = parts[2];
|
|
555
|
-
newPath = parts[3];
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
const path = newPath;
|
|
561
|
-
const existing = fileMap.get(path);
|
|
562
|
-
if (existing) {
|
|
563
|
-
existing.additions += additions;
|
|
564
|
-
existing.deletions += deletions;
|
|
565
|
-
existing.status = mergeStatus(existing.status, status);
|
|
566
|
-
if (oldPath)
|
|
567
|
-
existing.oldPath = (_b = existing.oldPath) !== null && _b !== void 0 ? _b : oldPath;
|
|
568
|
-
existing.newPath = (_c = existing.newPath) !== null && _c !== void 0 ? _c : newPath;
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
fileMap.set(path, {
|
|
572
|
-
path,
|
|
573
|
-
status,
|
|
574
|
-
additions,
|
|
575
|
-
deletions,
|
|
576
|
-
oldPath,
|
|
577
|
-
newPath: oldPath ? newPath : undefined,
|
|
578
|
-
});
|
|
579
|
-
}
|
|
522
|
+
const parsed = parseTabDiffSummaryLine(line);
|
|
523
|
+
if (parsed)
|
|
524
|
+
mergeParsedDiffSummaryLine(fileMap, parsed);
|
|
580
525
|
}
|
|
581
526
|
const files = Array.from(fileMap.values());
|
|
582
527
|
return {
|
|
@@ -587,11 +532,142 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
587
532
|
};
|
|
588
533
|
}
|
|
589
534
|
|
|
535
|
+
const STATUS_TO_SYNTHETIC_PREFIX = {
|
|
536
|
+
added: "A",
|
|
537
|
+
deleted: "D",
|
|
538
|
+
renamed: "R100",
|
|
539
|
+
copied: "C100",
|
|
540
|
+
"type-changed": "T",
|
|
541
|
+
modified: "M",
|
|
542
|
+
unknown: "X",
|
|
543
|
+
};
|
|
544
|
+
function diffStatusToSyntheticPrefix(status) {
|
|
545
|
+
return STATUS_TO_SYNTHETIC_PREFIX[status];
|
|
546
|
+
}
|
|
547
|
+
function buildSyntheticDiffLine(meta, counts) {
|
|
548
|
+
const prefix = diffStatusToSyntheticPrefix(meta.status);
|
|
549
|
+
if (meta.oldPath) {
|
|
550
|
+
return `${prefix}\t${counts.additions}\t${counts.deletions}\t${meta.oldPath}\t${meta.path}`;
|
|
551
|
+
}
|
|
552
|
+
return `${prefix}\t${counts.additions}\t${counts.deletions}\t${meta.path}`;
|
|
553
|
+
}
|
|
554
|
+
function buildDiffSummaryFromGitOutputs(nameStatusOutput, numStatOutput) {
|
|
555
|
+
var _a;
|
|
556
|
+
const numMap = new Map();
|
|
557
|
+
accumulateNumStat(numStatOutput, numMap);
|
|
558
|
+
const mergedName = mergeNameEntriesByPath(parseNameStatusLines(nameStatusOutput));
|
|
559
|
+
const syntheticLines = [];
|
|
560
|
+
for (const [path, meta] of mergedName) {
|
|
561
|
+
const counts = (_a = numMap.get(path)) !== null && _a !== void 0 ? _a : { additions: 0, deletions: 0 };
|
|
562
|
+
syntheticLines.push(buildSyntheticDiffLine(meta, counts));
|
|
563
|
+
}
|
|
564
|
+
return parseDiffSummary(syntheticLines.join("\n"));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function createGitClient(cwd = process.cwd()) {
|
|
568
|
+
return simpleGit.simpleGit(cwd);
|
|
569
|
+
}
|
|
570
|
+
function getCommits(git, from, to) {
|
|
571
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
572
|
+
const logResult = yield git.log({ from, to });
|
|
573
|
+
return logResult.all;
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function getRepoRoot(git) {
|
|
577
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
578
|
+
const root = yield git.revparse(["--show-toplevel"]);
|
|
579
|
+
return root.trim();
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
function getDiffPathContext(git, pathFilter, repoRootOverride) {
|
|
583
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
584
|
+
const repoRoot = repoRootOverride !== null && repoRootOverride !== void 0 ? repoRootOverride : (yield getRepoRoot(git));
|
|
585
|
+
const specs = buildDiffPathspecs(repoRoot, pathFilter);
|
|
586
|
+
return { repoRoot, specs };
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function getDiff(git, query) {
|
|
590
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
591
|
+
const { from, to, commits, filterByCommits, pathFilter, repoRootOverride } = query;
|
|
592
|
+
const { specs } = yield getDiffPathContext(git, pathFilter, repoRootOverride);
|
|
593
|
+
if (!filterByCommits) {
|
|
594
|
+
return git.diff([`${from}..${to}`, "--", ...specs]);
|
|
595
|
+
}
|
|
596
|
+
const patches = yield Promise.all(commits.map((c) => git.diff([`${c.hash}^!`, "--", ...specs])));
|
|
597
|
+
return patches.filter(Boolean).join("\n");
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
function getDiffSummary(git, query) {
|
|
601
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
602
|
+
const { from, to, commits, filterByCommits, pathFilter, repoRootOverride } = query;
|
|
603
|
+
const { specs } = yield getDiffPathContext(git, pathFilter, repoRootOverride);
|
|
604
|
+
if (!filterByCommits) {
|
|
605
|
+
const [numOutput, nameOutput] = yield Promise.all([
|
|
606
|
+
git.diff(["--numstat", `${from}..${to}`, "--", ...specs]),
|
|
607
|
+
git.diff(["--name-status", `${from}..${to}`, "--", ...specs]),
|
|
608
|
+
]);
|
|
609
|
+
return buildDiffSummaryFromGitOutputs(nameOutput, numOutput);
|
|
610
|
+
}
|
|
611
|
+
const pairs = yield Promise.all(commits.map((c) => __awaiter(this, void 0, void 0, function* () {
|
|
612
|
+
const range = `${c.hash}^!`;
|
|
613
|
+
const [numOutput, nameOutput] = yield Promise.all([
|
|
614
|
+
git.diff(["--numstat", range, "--", ...specs]),
|
|
615
|
+
git.diff(["--name-status", range, "--", ...specs]),
|
|
616
|
+
]);
|
|
617
|
+
return { numOutput, nameOutput };
|
|
618
|
+
})));
|
|
619
|
+
const nameJoined = pairs
|
|
620
|
+
.map((p) => p.nameOutput)
|
|
621
|
+
.filter(Boolean)
|
|
622
|
+
.join("\n");
|
|
623
|
+
const numJoined = pairs
|
|
624
|
+
.map((p) => p.numOutput)
|
|
625
|
+
.filter(Boolean)
|
|
626
|
+
.join("\n");
|
|
627
|
+
return buildDiffSummaryFromGitOutputs(nameJoined, numJoined);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
function getChangedFiles(git, query) {
|
|
631
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
632
|
+
const { from, to, commits, filterByCommits, pathFilter, repoRootOverride } = query;
|
|
633
|
+
const { specs } = yield getDiffPathContext(git, pathFilter, repoRootOverride);
|
|
634
|
+
if (!filterByCommits) {
|
|
635
|
+
const output = yield git.diff([
|
|
636
|
+
"--name-only",
|
|
637
|
+
`${from}..${to}`,
|
|
638
|
+
"--",
|
|
639
|
+
...specs,
|
|
640
|
+
]);
|
|
641
|
+
return output
|
|
642
|
+
.split(/\r?\n/)
|
|
643
|
+
.map((f) => f.trim())
|
|
644
|
+
.filter(Boolean);
|
|
645
|
+
}
|
|
646
|
+
const fileSet = new Set();
|
|
647
|
+
yield Promise.all(commits.map((c) => __awaiter(this, void 0, void 0, function* () {
|
|
648
|
+
const output = yield git.show([
|
|
649
|
+
"--name-only",
|
|
650
|
+
"--pretty=format:",
|
|
651
|
+
c.hash,
|
|
652
|
+
"--",
|
|
653
|
+
...specs,
|
|
654
|
+
]);
|
|
655
|
+
output
|
|
656
|
+
.split(/\r?\n/)
|
|
657
|
+
.map((f) => f.trim())
|
|
658
|
+
.filter(Boolean)
|
|
659
|
+
.forEach((f) => fileSet.add(f));
|
|
660
|
+
})));
|
|
661
|
+
return Array.from(fileSet);
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
590
665
|
function hasNonEmptyTrimmed(arr) {
|
|
591
666
|
return (arr !== null && arr !== void 0 ? arr : []).some((s) => s.trim().length > 0);
|
|
592
667
|
}
|
|
593
668
|
function shouldFilterByCommits(allCommits, filtered, opts) {
|
|
594
|
-
if (hasNonEmptyTrimmed(opts.commitMessageIncludeRegexes) ||
|
|
669
|
+
if (hasNonEmptyTrimmed(opts.commitMessageIncludeRegexes) ||
|
|
670
|
+
hasNonEmptyTrimmed(opts.commitMessageExcludeRegexes)) {
|
|
595
671
|
return true;
|
|
596
672
|
}
|
|
597
673
|
return filtered.length !== allCommits.length;
|
|
@@ -601,8 +677,9 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
601
677
|
var _a, _b;
|
|
602
678
|
const git = (_a = options.git) !== null && _a !== void 0 ? _a : createGitClient(options.cwd);
|
|
603
679
|
const from = options.from;
|
|
604
|
-
const to = (_b = options.to) !== null && _b !== void 0 ? _b :
|
|
605
|
-
const pathFilter = hasNonEmptyTrimmed(options.includeFolders) ||
|
|
680
|
+
const to = (_b = options.to) !== null && _b !== void 0 ? _b : "HEAD";
|
|
681
|
+
const pathFilter = hasNonEmptyTrimmed(options.includeFolders) ||
|
|
682
|
+
hasNonEmptyTrimmed(options.excludeFolders)
|
|
606
683
|
? {
|
|
607
684
|
includeFolders: options.includeFolders,
|
|
608
685
|
excludeFolders: options.excludeFolders,
|
|
@@ -611,10 +688,17 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
611
688
|
const allCommits = yield getCommits(git, from, to);
|
|
612
689
|
const filteredCommits = filterCommitsByMessageRegexes(allCommits, options.commitMessageIncludeRegexes, options.commitMessageExcludeRegexes);
|
|
613
690
|
const filterByCommits = shouldFilterByCommits(allCommits, filteredCommits, options);
|
|
691
|
+
const rangeQuery = {
|
|
692
|
+
from,
|
|
693
|
+
to,
|
|
694
|
+
commits: filteredCommits,
|
|
695
|
+
filterByCommits,
|
|
696
|
+
pathFilter,
|
|
697
|
+
};
|
|
614
698
|
const [diffText, fileNames, diffSummary] = yield Promise.all([
|
|
615
|
-
getDiff(git,
|
|
616
|
-
getChangedFiles(git,
|
|
617
|
-
getDiffSummary(git,
|
|
699
|
+
getDiff(git, rangeQuery),
|
|
700
|
+
getChangedFiles(git, rangeQuery),
|
|
701
|
+
getDiffSummary(git, rangeQuery),
|
|
618
702
|
]);
|
|
619
703
|
const summarizeFlags = {
|
|
620
704
|
from,
|
|
@@ -626,7 +710,14 @@ If the user message includes a Team line, use that exact team name in the summar
|
|
|
626
710
|
commitMessageIncludeRegexes: options.commitMessageIncludeRegexes,
|
|
627
711
|
commitMessageExcludeRegexes: options.commitMessageExcludeRegexes,
|
|
628
712
|
};
|
|
629
|
-
return generateSummary(
|
|
713
|
+
return generateSummary({
|
|
714
|
+
diffText,
|
|
715
|
+
fileNames,
|
|
716
|
+
commits: filteredCommits,
|
|
717
|
+
flags: summarizeFlags,
|
|
718
|
+
openAiClientProvider: options.openAiClientProvider,
|
|
719
|
+
diffSummary,
|
|
720
|
+
});
|
|
630
721
|
});
|
|
631
722
|
}
|
|
632
723
|
|