Skip to content

Commit 7edf40f

Browse files
authored
feat(commit): enhance commit message generation with localization and content filtering (#511)
* feat: add commit message generation feature * feat: enhance commit message generation with i18n support and API improvements * fix(i18n): update parameter placeholder syntax in commit localization files * feat(types): add isRateLimitRetry metadata field for rate limiting handling * fix(modelCache): change method call from getInstance to getAllInstance * refactor(commit): implement singleton pattern and execution lock * feat(commit): enhance commit message generation with localization and content filtering
1 parent 07450be commit 7edf40f

File tree

9 files changed

+779
-68
lines changed

9 files changed

+779
-68
lines changed

.gitattributes

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ locales/** linguist-generated=true
1616

1717
# Mark all locale directories as generated first
1818
src/i18n/locales/** linguist-generated=true
19+
src/i18n/costrict-i18n/locales/** linguist-generated=true
1920
webview-ui/src/i18n/locales/** linguist-generated=true
21+
webview-ui/src/i18n/costrict-i18n/locales/** linguist-generated=true
2022

2123
# Then explicitly mark English directories as NOT generated (override the above)
2224
src/i18n/locales/en/** linguist-generated=false
2325
webview-ui/src/i18n/locales/en/** linguist-generated=false
24-
26+
src/i18n/costrict-i18n/locales/en/** linguist-generated=false
27+
webview-ui/src/i18n/costrict-i18n/locales/en/** linguist-generated=false
2528
# This approach uses gitattributes' last-match-wins rule to exclude English while including all other locales

src/api/providers/zgsm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ export class ZgsmAiHandler extends BaseProvider implements SingleCompletionHandl
502502
model: model.id,
503503
messages: messages,
504504
temperature: 0.9,
505-
max_tokens: 200,
505+
max_tokens: metadata?.maxLength ?? 200,
506506
}
507507

508508
// Add max_tokens if needed

src/core/costrict/commit/__tests__/commitGenerator.test.ts

Lines changed: 262 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,49 @@
11
import { describe, it, expect, vi, beforeEach } from "vitest"
22
import { CommitMessageGenerator } from "../commitGenerator"
3-
import type { GitDiffInfo } from "../types"
3+
import type { GitDiffInfo, CommitGenerationOptions } from "../types"
44

55
// Mock child_process exec
66
vi.mock("child_process", () => ({
77
exec: vi.fn(),
88
}))
99

10+
// Mock vscode
11+
vi.mock("vscode", () => ({
12+
workspace: {
13+
getConfiguration: vi.fn(() => ({
14+
get: vi.fn((key: string, defaultValue: any) => defaultValue),
15+
})),
16+
createFileSystemWatcher: vi.fn(() => ({
17+
onDidChange: vi.fn(),
18+
onDidCreate: vi.fn(),
19+
onDidDelete: vi.fn(),
20+
dispose: vi.fn(),
21+
})),
22+
workspaceFolders: [],
23+
},
24+
env: {
25+
language: "en",
26+
clipboard: {
27+
writeText: vi.fn(),
28+
},
29+
},
30+
RelativePattern: vi.fn(),
31+
window: {
32+
withProgress: vi.fn(),
33+
showErrorMessage: vi.fn(),
34+
showInformationMessage: vi.fn(),
35+
createTextEditorDecorationType: vi.fn(() => ({
36+
dispose: vi.fn(),
37+
})),
38+
},
39+
ProgressLocation: {
40+
Notification: 15,
41+
},
42+
extensions: {
43+
getExtension: vi.fn(),
44+
},
45+
}))
46+
1047
describe("CommitMessageGenerator", () => {
1148
let generator: CommitMessageGenerator
1249

@@ -137,4 +174,228 @@ describe("CommitMessageGenerator", () => {
137174
expect(result).toBe(false)
138175
})
139176
})
177+
178+
describe("getCommitLanguage", () => {
179+
it("should return language from options when specified", () => {
180+
const options: CommitGenerationOptions = {
181+
language: "zh-CN",
182+
}
183+
184+
const result = (generator as any).getCommitLanguage(options)
185+
expect(result).toBe("zh-CN")
186+
})
187+
188+
it("should return 'en' when language is 'auto' and no VSCode language", () => {
189+
const options: CommitGenerationOptions = {
190+
language: "auto",
191+
}
192+
193+
const result = (generator as any).getCommitLanguage(options)
194+
expect(result).toBe("en")
195+
})
196+
197+
it("should return default language when no language specified", () => {
198+
const options: CommitGenerationOptions = {}
199+
200+
const result = (generator as any).getCommitLanguage(options)
201+
expect(result).toBe("en")
202+
})
203+
})
204+
205+
describe("getLocalizedPromptPrefix", () => {
206+
it("should return Chinese prompt for zh-CN", () => {
207+
const result = (generator as any).getLocalizedPromptPrefix("zh-CN")
208+
expect(result).toContain("根据以下 git 变更生成提交信息")
209+
})
210+
211+
it("should return Traditional Chinese prompt for zh-TW", () => {
212+
const result = (generator as any).getLocalizedPromptPrefix("zh-TW")
213+
expect(result).toContain("根據以下 git 變更生成提交訊息")
214+
})
215+
216+
it("should return English prompt for other languages", () => {
217+
const result = (generator as any).getLocalizedPromptPrefix("en")
218+
expect(result).toContain("Generate a commit message based on the following git changes")
219+
})
220+
})
221+
222+
describe("getLocalizedConventionalPrompt", () => {
223+
it("should return Chinese conventional prompt for zh-CN", () => {
224+
const result = (generator as any).getLocalizedConventionalPrompt("zh-CN")
225+
expect(result).toContain("约定式提交格式")
226+
})
227+
228+
it("should return Traditional Chinese conventional prompt for zh-TW", () => {
229+
const result = (generator as any).getLocalizedConventionalPrompt("zh-TW")
230+
expect(result).toContain("約定式提交格式")
231+
})
232+
233+
it("should return English conventional prompt for other languages", () => {
234+
const result = (generator as any).getLocalizedConventionalPrompt("en")
235+
expect(result).toContain("conventional commit format")
236+
})
237+
})
238+
239+
describe("getLocalizedSimplePrompt", () => {
240+
it("should return Chinese simple prompt for zh-CN", () => {
241+
const result = (generator as any).getLocalizedSimplePrompt("zh-CN")
242+
expect(result).toContain("简洁的提交信息")
243+
})
244+
245+
it("should return Traditional Chinese simple prompt for zh-TW", () => {
246+
const result = (generator as any).getLocalizedSimplePrompt("zh-TW")
247+
expect(result).toContain("簡潔的提交訊息")
248+
})
249+
250+
it("should return English simple prompt for other languages", () => {
251+
const result = (generator as any).getLocalizedSimplePrompt("en")
252+
expect(result).toContain("concise commit message")
253+
})
254+
})
255+
256+
describe("getLocalizedReturnPrompt", () => {
257+
it("should return Chinese return prompt for zh-CN", () => {
258+
const result = (generator as any).getLocalizedReturnPrompt("zh-CN")
259+
expect(result).toContain("只返回提交信息")
260+
})
261+
262+
it("should return Traditional Chinese return prompt for zh-TW", () => {
263+
const result = (generator as any).getLocalizedReturnPrompt("zh-TW")
264+
expect(result).toContain("只返回提交訊息")
265+
})
266+
267+
it("should return English return prompt for other languages", () => {
268+
const result = (generator as any).getLocalizedReturnPrompt("en")
269+
expect(result).toContain("Return only the commit message")
270+
})
271+
})
272+
273+
describe("shouldFilterFileContent", () => {
274+
it("should return true for image files", () => {
275+
const result = (generator as any).shouldFilterFileContent("image.png")
276+
expect(result).toBe(true)
277+
278+
const result2 = (generator as any).shouldFilterFileContent("photo.jpg")
279+
expect(result2).toBe(true)
280+
281+
const result3 = (generator as any).shouldFilterFileContent("logo.svg")
282+
expect(result3).toBe(true)
283+
})
284+
285+
it("should return true for lock files", () => {
286+
const result = (generator as any).shouldFilterFileContent("package-lock.json")
287+
expect(result).toBe(true)
288+
289+
const result2 = (generator as any).shouldFilterFileContent("yarn.lock")
290+
expect(result2).toBe(true)
291+
292+
const result3 = (generator as any).shouldFilterFileContent("pnpm-lock.yaml")
293+
expect(result3).toBe(true)
294+
})
295+
296+
it("should return true for binary files", () => {
297+
const result = (generator as any).shouldFilterFileContent("program.exe")
298+
expect(result).toBe(true)
299+
300+
const result2 = (generator as any).shouldFilterFileContent("library.so")
301+
expect(result2).toBe(true)
302+
})
303+
304+
it("should return false for regular source files", () => {
305+
const result = (generator as any).shouldFilterFileContent("index.ts")
306+
expect(result).toBe(false)
307+
308+
const result2 = (generator as any).shouldFilterFileContent("styles.css")
309+
expect(result2).toBe(false)
310+
311+
const result3 = (generator as any).shouldFilterFileContent("README.md")
312+
expect(result3).toBe(false)
313+
})
314+
})
315+
316+
describe("filterDiffContent", () => {
317+
it("should filter content for image files", () => {
318+
const diffContent = `diff --git a/image.png b/image.png
319+
new file mode 100644
320+
index 0000000..1234567
321+
--- /dev/null
322+
+++ b/image.png
323+
@@ -0,0 +1 @@
324+
+binary content here
325+
diff --git a/src/index.ts b/src/index.ts
326+
index 1234567..89abcde 100644
327+
--- a/src/index.ts
328+
+++ b/src/index.ts
329+
@@ -1,1 +1,2 @@
330+
console.log("hello")
331+
+console.log("world")`
332+
333+
const diffInfo: GitDiffInfo = {
334+
added: ["image.png", "src/index.ts"],
335+
modified: [],
336+
deleted: [],
337+
renamed: [],
338+
diffContent: "",
339+
}
340+
341+
const result = (generator as any).filterDiffContent(diffContent, diffInfo)
342+
expect(result).toContain("diff --git a/image.png b/image.png")
343+
expect(result).not.toContain("binary content here")
344+
expect(result).toContain('console.log("world")')
345+
})
346+
347+
it("should filter content for lock files", () => {
348+
const diffContent = `diff --git a/package-lock.json b/package-lock.json
349+
index 1234567..89abcde 100644
350+
--- a/package-lock.json
351+
+++ b/package-lock.json
352+
@@ -1,5 +1,5 @@
353+
{
354+
- "name": "test"
355+
+ "name": "test-updated"
356+
}
357+
diff --git a/src/app.ts b/src/app.ts
358+
index 1234567..89abcde 100644
359+
--- a/src/app.ts
360+
+++ b/src/app.ts
361+
@@ -1,1 +1,2 @@
362+
const app = "test"
363+
+const version = "1.0.0"`
364+
365+
const diffInfo: GitDiffInfo = {
366+
added: [],
367+
modified: ["package-lock.json", "src/app.ts"],
368+
deleted: [],
369+
renamed: [],
370+
diffContent: "",
371+
}
372+
373+
const result = (generator as any).filterDiffContent(diffContent, diffInfo)
374+
expect(result).toContain("diff --git a/package-lock.json b/package-lock.json")
375+
expect(result).not.toContain('"name": "test-updated"')
376+
expect(result).toContain('const version = "1.0.0"')
377+
})
378+
379+
it("should not filter content for regular files", () => {
380+
const diffContent = `diff --git a/src/index.ts b/src/index.ts
381+
index 1234567..89abcde 100644
382+
--- a/src/index.ts
383+
+++ b/src/index.ts
384+
@@ -1,1 +1,2 @@
385+
console.log("hello")
386+
+console.log("world")`
387+
388+
const diffInfo: GitDiffInfo = {
389+
added: [],
390+
modified: ["src/index.ts"],
391+
deleted: [],
392+
renamed: [],
393+
diffContent: "",
394+
}
395+
396+
const result = (generator as any).filterDiffContent(diffContent, diffInfo)
397+
expect(result).toContain('console.log("hello")')
398+
expect(result).toContain('console.log("world")')
399+
})
400+
})
140401
})

0 commit comments

Comments
 (0)