Skip to content

Commit 65c4ed6

Browse files
committed
feat(git-worktree): 新增 Worktree 创建与删除功能
- 在 Worktree 视图中新增“新建 Worktree”和“删除 Worktree”命令 - 新增 `repositoryResolver` 共享模块,统一 Git 仓库解析逻辑 - 重构 AI Commit 功能,使用新的仓库解析器支持多仓库选择 - 重构 Git Ignore Manager,使用新的仓库获取方式 - 更新 Worktree 视图,支持按仓库分组显示并显示仓库名称 - 新增相关单元测试
1 parent 92c6214 commit 65c4ed6

14 files changed

Lines changed: 719 additions & 98 deletions

File tree

package.json

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
"onCommand:fusi-tools.gitWorktree.push",
3838
"onCommand:fusi-tools.gitWorktree.revealInExplorer",
3939
"onCommand:fusi-tools.gitWorktree.openInVsCode",
40+
"onCommand:fusi-tools.gitWorktree.createWorktree",
41+
"onCommand:fusi-tools.gitWorktree.deleteWorktree",
4042
"onView:fusi-tools.projectFavorites.view",
4143
"onCommand:fusi-tools.projectFavorites.addFile",
4244
"onCommand:fusi-tools.projectFavorites.moveToCategory",
@@ -418,6 +420,18 @@
418420
"title": "在当前编辑器中打开",
419421
"category": "Fusi Tools"
420422
},
423+
{
424+
"command": "fusi-tools.gitWorktree.createWorktree",
425+
"title": "新建 Worktree",
426+
"icon": "$(add)",
427+
"category": "Fusi Tools"
428+
},
429+
{
430+
"command": "fusi-tools.gitWorktree.deleteWorktree",
431+
"title": "删除 Worktree",
432+
"icon": "$(trash)",
433+
"category": "Fusi Tools"
434+
},
421435
{
422436
"command": "fusi-tools.projectFavorites.addFile",
423437
"title": "添加到常用",
@@ -509,6 +523,16 @@
509523
"when": "view == fusi-tools.gitIgnoreManager.view && viewItem =~ /^ignoredFile/",
510524
"group": "inline"
511525
},
526+
{
527+
"command": "fusi-tools.gitWorktree.createWorktree",
528+
"when": "view == fusi-tools.gitWorktree.view && viewItem == repository",
529+
"group": "inline"
530+
},
531+
{
532+
"command": "fusi-tools.gitWorktree.deleteWorktree",
533+
"when": "view == fusi-tools.gitWorktree.view && viewItem == worktree",
534+
"group": "inline"
535+
},
512536
{
513537
"command": "fusi-tools.projectFavorites.renameCategory",
514538
"when": "view == fusi-tools.projectFavorites.view && viewItem == favoriteCategory",
@@ -561,10 +585,15 @@
561585
"when": "view == fusi-tools.gitIgnoreManager.view",
562586
"group": "navigation@2"
563587
},
588+
{
589+
"command": "fusi-tools.gitWorktree.createWorktree",
590+
"when": "view == fusi-tools.gitWorktree.view",
591+
"group": "navigation@1"
592+
},
564593
{
565594
"command": "fusi-tools.gitWorktree.refresh",
566595
"when": "view == fusi-tools.gitWorktree.view",
567-
"group": "navigation"
596+
"group": "navigation@2"
568597
},
569598
{
570599
"command": "fusi-tools.projectFavorites.addCategory",

src/features/aiCommit/git.ts

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import * as vscode from "vscode";
21
import * as cp from "child_process";
32
import * as path from "path";
3+
import { GitRepository } from "../git/shared/repositoryResolver";
44

55
export interface SmartChange {
66
relativePath: string;
@@ -15,31 +15,16 @@ export interface SmartChange {
1515
}
1616

1717
export class GitService {
18-
private get gitApi() {
19-
const extension = vscode.extensions.getExtension("vscode.git");
20-
return extension?.exports?.getAPI(1);
21-
}
22-
23-
private get repository() {
24-
const api = this.gitApi;
25-
if (!api || api.repositories.length === 0) {
26-
return undefined;
27-
}
28-
// In a real scenario, might want to pick the repo based on active editor
29-
// For now, simplicity: pick the first one
30-
return api.repositories[0];
31-
}
32-
3318
/**
3419
* 获取最近的 N 条 commit message (用于 Few-shot 学习)
3520
* @param count 获取的条数,默认 5
3621
* @param maxLength 每条消息的最大长度,默认 100
3722
*/
3823
async getRecentCommits(
24+
repo: GitRepository,
3925
count: number = 5,
4026
maxLength: number = 100,
4127
): Promise<string> {
42-
const repo = this.repository;
4328
if (!repo) return "(No recent commits)";
4429

4530
try {
@@ -70,8 +55,7 @@ export class GitService {
7055
/**
7156
* 1. 分析暂存区变更,返回结构化数据
7257
*/
73-
async analyzeChanges(): Promise<SmartChange[] | null> {
74-
const repo = this.repository;
58+
async analyzeChanges(repo: GitRepository): Promise<SmartChange[] | null> {
7559
if (!repo) return null;
7660

7761
const changes = repo.state.indexChanges;
@@ -247,8 +231,10 @@ export class GitService {
247231
* 2. 将分析结果格式化为 Prompt 字符串
248232
* (包含总大小检查,如果过大则降级为 stat)
249233
*/
250-
async formatSmartDiff(changes: SmartChange[]): Promise<string> {
251-
const repo = this.repository;
234+
async formatSmartDiff(
235+
repo: GitRepository,
236+
changes: SmartChange[],
237+
): Promise<string> {
252238
if (!repo || !changes || changes.length === 0) return "";
253239

254240
// 检查是否已经是 "文件过多" 的状态
@@ -302,10 +288,10 @@ export class GitService {
302288
/**
303289
* 旧接口兼容
304290
*/
305-
async getSmartDiff(): Promise<string | null> {
306-
const analysis = await this.analyzeChanges();
291+
async getSmartDiff(repo: GitRepository): Promise<string | null> {
292+
const analysis = await this.analyzeChanges(repo);
307293
if (!analysis) return null;
308-
return this.formatSmartDiff(analysis);
294+
return this.formatSmartDiff(repo, analysis);
309295
}
310296

311297
/**
@@ -525,8 +511,7 @@ export class GitService {
525511
/**
526512
* 将提交信息设置到 SCM 输入框。
527513
*/
528-
setCommitMessage(message: string) {
529-
const repo = this.repository;
514+
setCommitMessage(message: string, repo: GitRepository) {
530515
if (repo) {
531516
repo.inputBox.value = message;
532517
}

src/features/aiCommit/index.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { GitService } from "./git";
44
import { AiService } from "./ai";
55
import { AiCommitViewProvider, CommitItem } from "./treeProvider";
66
import { FAST_PROMPT } from "./prompts";
7+
import {
8+
GitRepository,
9+
resolveRepository,
10+
} from "../git/shared/repositoryResolver";
711
import * as path from "path";
812
import * as fs from "fs";
913

@@ -24,6 +28,7 @@ export function activate(context: vscode.ExtensionContext) {
2428
let cachedDiff: string | null = null;
2529
// 缓存上下文信息,供 "预览 Prompt" 使用
2630
let lastContext: { diff: string; projectMeta: string } | null = null;
31+
let lastRepository: GitRepository | null = null;
2732

2833
// 1. 注册 TreeDataProvider
2934
context.subscriptions.push(
@@ -54,11 +59,9 @@ export function activate(context: vscode.ExtensionContext) {
5459
// ---------------------------------------------------------
5560
// 辅助函数:获取 Project Meta
5661
// ---------------------------------------------------------
57-
const getProjectMeta = async (): Promise<string> => {
62+
const getProjectMeta = async (repoRoot: string): Promise<string> => {
5863
try {
59-
if (!vscode.workspace.workspaceFolders) return "";
60-
const rootPath = vscode.workspace.workspaceFolders[0].uri.fsPath;
61-
const pkgPath = path.join(rootPath, "package.json");
64+
const pkgPath = path.join(repoRoot, "package.json");
6265
if (fs.existsSync(pkgPath)) {
6366
const raw = await fs.promises.readFile(pkgPath, "utf-8");
6467
const pkg = JSON.parse(raw);
@@ -70,10 +73,22 @@ export function activate(context: vscode.ExtensionContext) {
7073
return "";
7174
};
7275

76+
const resolveTargetRepository = async (): Promise<GitRepository> => {
77+
const resolved = await resolveRepository({
78+
quickPickTitle: "AI Commit: 请选择目标 Git 仓库",
79+
});
80+
lastRepository = resolved.repository;
81+
Logger.info(
82+
`[RepoResolver] source=${resolved.source}, repo=${resolved.repository.rootUri.fsPath}`,
83+
);
84+
return resolved.repository;
85+
};
86+
7387
// ---------------------------------------------------------
7488
// 辅助函数:执行 AI 生成
7589
// ---------------------------------------------------------
7690
const runAiGeneration = async (
91+
repository: GitRepository,
7792
diff: string,
7893
changeSummary: string = "(None)",
7994
) => {
@@ -86,7 +101,7 @@ export function activate(context: vscode.ExtensionContext) {
86101
async () => {
87102
try {
88103
// 获取项目元数据
89-
const projectMeta = await getProjectMeta();
104+
const projectMeta = await getProjectMeta(repository.rootUri.fsPath);
90105

91106
// 保存上下文供预览
92107
lastContext = { diff, projectMeta };
@@ -123,6 +138,8 @@ export function activate(context: vscode.ExtensionContext) {
123138
context.subscriptions.push(
124139
vscode.commands.registerCommand("fusi-tools.previewSmartDiff", async () => {
125140
try {
141+
const repository = await resolveTargetRepository();
142+
126143
// 1. 显示加载中
127144
provider.showLoading();
128145

@@ -131,18 +148,18 @@ export function activate(context: vscode.ExtensionContext) {
131148

132149
// 2. 分析
133150
Logger.info("正在分析文件变更 (智能预处理)...");
134-
const analysis = await gitService.analyzeChanges();
151+
const analysis = await gitService.analyzeChanges(repository);
135152
if (!analysis || analysis.length === 0) {
136153
provider.clear(); // 清除加载态
137154
vscode.window.showWarningMessage("未发现暂存更改,请先暂存文件。");
138155
return;
139156
}
140157

141158
// 3. 格式化并缓存
142-
cachedDiff = await gitService.formatSmartDiff(analysis);
159+
cachedDiff = await gitService.formatSmartDiff(repository, analysis);
143160

144161
// [New] 预加载上下文,以便用户可以在“生成前”查看 Prompt
145-
const projectMeta = await getProjectMeta();
162+
const projectMeta = await getProjectMeta(repository.rootUri.fsPath);
146163
lastContext = { diff: cachedDiff, projectMeta };
147164

148165
// 4. 更新 UI 展示文件列表
@@ -170,7 +187,8 @@ export function activate(context: vscode.ExtensionContext) {
170187
if (!cachedDiff) return; // 如果还是没有 (e.g. 无更改),终止
171188
}
172189

173-
await runAiGeneration(cachedDiff);
190+
const repository = lastRepository || (await resolveTargetRepository());
191+
await runAiGeneration(repository, cachedDiff);
174192
}),
175193
);
176194

@@ -180,25 +198,27 @@ export function activate(context: vscode.ExtensionContext) {
180198
context.subscriptions.push(
181199
vscode.commands.registerCommand("fusi-tools.generateDirect", async () => {
182200
try {
201+
const repository = await resolveTargetRepository();
202+
183203
// 1. 显示加载
184204
provider.showLoading();
185205

186206
// 2. 获取 diff (不做 UI 预览,直接获取字符串)
187-
const analysis = await gitService.analyzeChanges();
207+
const analysis = await gitService.analyzeChanges(repository);
188208
if (!analysis || analysis.length === 0) {
189209
provider.clear();
190210
vscode.window.showWarningMessage("未发现暂存更改,请先暂存文件。");
191211
return;
192212
}
193213

194-
const diff = await gitService.formatSmartDiff(analysis);
214+
const diff = await gitService.formatSmartDiff(repository, analysis);
195215
cachedDiff = diff; // 顺便缓存
196216

197217
// 更新 UI (后台更新,不聚焦,作为副产品)
198218
provider.updatePreProcess(analysis);
199219

200220
// 3. 直接执行生成
201-
await runAiGeneration(diff);
221+
await runAiGeneration(repository, diff);
202222
} catch (error: any) {
203223
provider.clear();
204224
vscode.window.showErrorMessage(`生成失败: ${error.message}`);
@@ -212,7 +232,7 @@ export function activate(context: vscode.ExtensionContext) {
212232
context.subscriptions.push(
213233
vscode.commands.registerCommand(
214234
"fusi-tools.applyCommit",
215-
(arg: string | CommitItem) => {
235+
async (arg: string | CommitItem) => {
216236
let message = "";
217237

218238
if (typeof arg === "string") {
@@ -228,8 +248,15 @@ export function activate(context: vscode.ExtensionContext) {
228248
}
229249

230250
if (message) {
231-
Logger.info("尝试应用提交信息到 Git 输入框");
232-
gitService.setCommitMessage(message);
251+
try {
252+
const repository = lastRepository || (await resolveTargetRepository());
253+
Logger.info(
254+
`尝试应用提交信息到 Git 输入框: ${repository.rootUri.fsPath}`,
255+
);
256+
gitService.setCommitMessage(message, repository);
257+
} catch (error: any) {
258+
vscode.window.showErrorMessage(`应用提交信息失败: ${error.message}`);
259+
}
233260
}
234261
},
235262
),

0 commit comments

Comments
 (0)