Skip to content

Commit 6f62946

Browse files
committed
refactor(scm): 🔨 simplify commit message architecture and use async git operations
This commit refactors the AI commit message generator to reduce complexity and prevent blocking the VS Code extension host during Git commands. - Rewrite `GitExtensionService` to use asynchronous `child_process.execFile` instead of synchronous `spawnSync`. - Remove abstraction layers (`CommitMessageOrchestrator`, `VSCodeCommitMessageAdapter`) and handle VS Code UI progress natively in `CommitMessageProvider`. - Drop custom ignore logic and lockfile exclusions in favor of Git's native status parsing (`--name-status` and `--porcelain`). - Centralize `commitMessageApiConfigId` state updates in `SettingsView`, removing the direct `vscode.postMessage` handler. - Add unit tests for the newly refactored `GitExtensionService`.
1 parent a9fa691 commit 6f62946

16 files changed

Lines changed: 438 additions & 685 deletions

‎packages/types/src/vscode-extension-host.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,6 @@ export interface WebviewMessage {
491491
| "copySystemPrompt"
492492
| "systemPrompt"
493493
| "enhancementApiConfigId"
494-
| "commitMessageApiConfigId"
495494
| "autoApprovalEnabled"
496495
| "updateCustomMode"
497496
| "deleteCustomMode"

‎src/core/webview/webviewMessageHandler.ts‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,11 +1606,6 @@ export const webviewMessageHandler = async (
16061606
await updateGlobalState("enhancementApiConfigId", message.text)
16071607
await provider.postStateToWebview()
16081608
break
1609-
case "commitMessageApiConfigId":
1610-
await updateGlobalState("commitMessageApiConfigId", message.text)
1611-
await provider.postStateToWebview()
1612-
break
1613-
16141609
case "autoApprovalEnabled":
16151610
await updateGlobalState("autoApprovalEnabled", message.bool ?? false)
16161611
await provider.postStateToWebview()

‎src/services/commit-message/CommitMessageOrchestrator.ts‎

Lines changed: 0 additions & 121 deletions
This file was deleted.

‎src/services/commit-message/CommitMessageProvider.ts‎

Lines changed: 125 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManag
33
import { t } from "../../i18n"
44
import { Package } from "../../shared/package"
55

6-
import { CommitMessageRequest, CommitMessageResult } from "./types/core"
76
import { CommitMessageGenerator } from "./CommitMessageGenerator"
8-
import { VSCodeCommitMessageAdapter } from "./adapters/VSCodeCommitMessageAdapter"
9-
import { VscGenerationRequest } from "./types"
7+
import { GitExtensionService } from "./GitExtensionService"
8+
import { GitChange } from "./types"
9+
10+
interface VscGenerationRequest {
11+
inputBox: { value: string }
12+
rootUri?: vscode.Uri
13+
}
1014

1115
export class CommitMessageProvider implements vscode.Disposable {
1216
private generator: CommitMessageGenerator
13-
private vscodeAdapter: VSCodeCommitMessageAdapter
1417

1518
constructor(
1619
private context: vscode.ExtensionContext,
@@ -19,7 +22,6 @@ export class CommitMessageProvider implements vscode.Disposable {
1922
const providerSettingsManager = new ProviderSettingsManager(this.context)
2023

2124
this.generator = new CommitMessageGenerator(providerSettingsManager)
22-
this.vscodeAdapter = new VSCodeCommitMessageAdapter(this.generator)
2325
}
2426

2527
public async activate(): Promise<void> {
@@ -35,11 +37,125 @@ export class CommitMessageProvider implements vscode.Disposable {
3537
}
3638

3739
private async handleVSCodeCommand(vsRequest?: VscGenerationRequest): Promise<void> {
38-
const request: CommitMessageRequest = {
39-
workspacePath: this.determineWorkspacePath(vsRequest?.rootUri),
40+
try {
41+
const workspacePath = this.determineWorkspacePath(vsRequest?.rootUri)
42+
const targetRepository = await this.determineTargetRepository(workspacePath)
43+
if (!targetRepository?.rootUri) {
44+
throw new Error("Could not determine Git repository")
45+
}
46+
47+
await vscode.window.withProgress(
48+
{
49+
location: vscode.ProgressLocation.SourceControl,
50+
title: t("common:commitMessage.generating"),
51+
cancellable: false,
52+
},
53+
async (progress) => {
54+
let lastPercentage = 0
55+
const reportProgress = (percentage: number, message?: string) => {
56+
progress.report({
57+
increment: Math.max(0, percentage - lastPercentage),
58+
message: message || t("common:commitMessage.generating"),
59+
})
60+
lastPercentage = percentage
61+
}
62+
63+
reportProgress(5, t("common:commitMessage.initializing"))
64+
const gitService = new GitExtensionService(workspacePath)
65+
66+
try {
67+
reportProgress(15, t("common:commitMessage.discoveringFiles"))
68+
const resolution = await this.resolveCommitChanges(gitService)
69+
70+
if (resolution.changes.length === 0) {
71+
vscode.window.showInformationMessage(t("common:commitMessage.noChanges"))
72+
return
73+
}
74+
75+
reportProgress(25, t("common:commitMessage.foundChanges", { count: resolution.changes.length }))
76+
77+
if (!resolution.usedStaged) {
78+
vscode.window.showInformationMessage(t("common:commitMessage.generatingFromUnstaged"))
79+
}
80+
81+
reportProgress(40, t("common:commitMessage.gettingContext"))
82+
const gitContext = await gitService.getCommitContext(
83+
resolution.changes,
84+
{ staged: resolution.usedStaged, includeRepoContext: true },
85+
resolution.files,
86+
)
87+
88+
reportProgress(70, t("common:commitMessage.generating"))
89+
const message = await this.generator.generateMessage({
90+
workspacePath,
91+
selectedFiles: resolution.files,
92+
gitContext,
93+
onProgress: (update) => {
94+
if (update.percentage !== undefined) {
95+
reportProgress(70 + update.percentage * 0.25, update.message)
96+
}
97+
},
98+
})
99+
100+
targetRepository.inputBox.value = message
101+
reportProgress(100, t("common:commitMessage.generated"))
102+
} finally {
103+
gitService.dispose()
104+
}
105+
},
106+
)
107+
} catch (error) {
108+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"
109+
vscode.window.showErrorMessage(t("common:commitMessage.generationFailed", { errorMessage }))
110+
}
111+
}
112+
113+
private async resolveCommitChanges(gitService: GitExtensionService): Promise<{
114+
changes: GitChange[]
115+
files: string[]
116+
usedStaged: boolean
117+
}> {
118+
let changes = await gitService.gatherChanges({ staged: true })
119+
let usedStaged = true
120+
121+
if (changes.length === 0) {
122+
changes = await gitService.gatherChanges({ staged: false })
123+
usedStaged = false
40124
}
41125

42-
await this.vscodeAdapter.generateCommitMessage(request)
126+
return {
127+
changes,
128+
files: changes.map((change) => change.filePath),
129+
usedStaged,
130+
}
131+
}
132+
133+
private async determineTargetRepository(workspacePath: string): Promise<VscGenerationRequest | null> {
134+
try {
135+
const gitExtension = vscode.extensions.getExtension("vscode.git")
136+
if (!gitExtension) {
137+
return null
138+
}
139+
140+
if (!gitExtension.isActive) {
141+
await gitExtension.activate()
142+
}
143+
144+
const gitApi = gitExtension.exports.getAPI(1)
145+
if (!gitApi) {
146+
return null
147+
}
148+
149+
for (const repo of gitApi.repositories ?? []) {
150+
if (repo.rootUri && workspacePath.startsWith(repo.rootUri.fsPath)) {
151+
return repo
152+
}
153+
}
154+
155+
return gitApi.repositories[0] ?? null
156+
} catch (error) {
157+
return null
158+
}
43159
}
44160

45161
private determineWorkspacePath(resourceUri?: vscode.Uri): string {
@@ -55,7 +171,5 @@ export class CommitMessageProvider implements vscode.Disposable {
55171
throw new Error("Could not determine workspace path")
56172
}
57173

58-
public dispose(): void {
59-
this.vscodeAdapter?.dispose()
60-
}
174+
public dispose(): void {}
61175
}

0 commit comments

Comments
 (0)