From cb0f54ca3f96919c756de55438cfa57d987c0914 Mon Sep 17 00:00:00 2001 From: ThreeFish Date: Sat, 27 Jun 2026 23:17:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(Parity-Batch1):=20=E5=BC=95=E5=85=A5=20exe?= =?UTF-8?q?cGit=20CLI=20=E9=80=9A=E9=81=93=E8=A1=A5=E9=BD=90=20cherry-pick?= =?UTF-8?q?/revert/reset/rename/stash-list/ignore/compare/reword;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang --- CHANGELOG.md | 10 ++ package.json | 33 ++++- src/adapter/git-cli-commands.ts | 201 ++++++++++++++++++++++++++ src/adapter/git-repository-service.ts | 22 +++ src/adapter/stash-commands.ts | 22 +-- src/adapter/tree/stash-tree.ts | 54 +++++-- src/extension.ts | 4 +- tests/suite/extension.test.js | 7 + 8 files changed, 322 insertions(+), 31 deletions(-) create mode 100644 src/adapter/git-cli-commands.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b4571..a2f9e8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ ## [Unreleased] +### Added — Parity Batch 1(CLI 功能补齐,0.0.2) + +> 关键转向:引入 `GitRepositoryService.execGit`(复用 vscode.git 的同一 git 二进制 `api.git.path`),补齐稳定 API 未暴露的操作——修正此前"API 限制延后"的过度自我设限。 + +- Cherry-pick(Log 右键)、Revert commit(Log 右键)——经 `git cherry-pick` / `git revert`。 +- Reset HEAD(soft/mixed/hard/keep,命令面板,hard 二次确认)。 +- 分支重命名(Branches 右键 `git branch -m`)、比较分支(`git diff --stat a...b`)。 +- Ignore(写 .gitignore)、改写最新提交(amend)。 +- **Stash 多栈列表**:用 `git stash list`(CLI)修复——apply/pop/drop 按真实 `stash@{n}` 索引(此前 `log({refNames:['stash']})` 仅返回最新 stash 内部提交、语义错误,已弃用)。 + ## [0.0.1-rc.1] - 2026-06-27 — 首发候选(Pre-release) 首个公开预发布候选,整合 M0-M4 里程碑交付 + 两轮独立 code review 修复。 diff --git a/package.json b/package.json index 82d9681..32fd278 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Hyper Git", "icon": "media/icon.png", "description": "在 VS Code 上完整复刻 IntelliJ IDEA 的 Git 工具窗口与 Commit 提交窗口,并为未来 AI Agent 自主代理预留架构接缝。", - "version": "0.0.1", + "version": "0.0.2", "publisher": "threefish-ai", "license": "MIT", "preview": true, @@ -123,7 +123,14 @@ { "command": "hyperGit.discardChanges", "title": "丢弃改动", "category": "Hyper Git" }, { "command": "hyperGit.pull", "title": "Pull", "category": "Hyper Git", "icon": "$(arrow-down)" }, { "command": "hyperGit.push", "title": "Push", "category": "Hyper Git", "icon": "$(arrow-up)" }, - { "command": "hyperGit.fetch", "title": "Fetch", "category": "Hyper Git" } + { "command": "hyperGit.fetch", "title": "Fetch", "category": "Hyper Git" }, + { "command": "hyperGit.cherryPick", "title": "Cherry-Pick", "category": "Hyper Git" }, + { "command": "hyperGit.revertCommit", "title": "Revert Commit", "category": "Hyper Git" }, + { "command": "hyperGit.resetHead", "title": "Reset HEAD…", "category": "Hyper Git" }, + { "command": "hyperGit.branchRename", "title": "重命名分支", "category": "Hyper Git" }, + { "command": "hyperGit.ignorePath", "title": "添加到 .gitignore", "category": "Hyper Git" }, + { "command": "hyperGit.compareBranches", "title": "比较分支", "category": "Hyper Git" }, + { "command": "hyperGit.rewordCommit", "title": "改写最新提交信息", "category": "Hyper Git" } ], "menus": { "view/title": [ @@ -137,10 +144,7 @@ { "command": "hyperGit.branchCreate", "when": "view == hyperGit.branches", "group": "navigation" }, { "command": "hyperGit.pull", "when": "view == hyperGit.branches", "group": "navigation" }, { "command": "hyperGit.push", "when": "view == hyperGit.branches", "group": "navigation" }, - { "command": "hyperGit.stashCreate", "when": "view == hyperGit.stash", "group": "navigation" }, - { "command": "hyperGit.stashApply", "when": "view == hyperGit.stash" }, - { "command": "hyperGit.stashPop", "when": "view == hyperGit.stash" }, - { "command": "hyperGit.stashDrop", "when": "view == hyperGit.stash" } + { "command": "hyperGit.stashCreate", "when": "view == hyperGit.stash", "group": "navigation" } ], "view/item/context": [ { "command": "hyperGit.setActiveChangelist", "when": "view == hyperGit.changes && viewItem == hyperGit.changelist", "group": "1_changelist@1" }, @@ -154,7 +158,15 @@ { "command": "hyperGit.branchCheckout", "when": "view == hyperGit.branches && viewItem =~ /hyperGit.branch|hyperGit.remoteBranch/", "group": "1_branch@1" }, { "command": "hyperGit.branchDelete", "when": "view == hyperGit.branches && viewItem == hyperGit.branch", "group": "1_branch@2" }, { "command": "hyperGit.mergeBranch", "when": "view == hyperGit.branches && viewItem =~ /hyperGit.branch|hyperGit.remoteBranch/", "group": "1_branch@3" }, - { "command": "hyperGit.rebaseBranch", "when": "view == hyperGit.branches && viewItem =~ /hyperGit.branch|hyperGit.remoteBranch/", "group": "1_branch@4" } + { "command": "hyperGit.rebaseBranch", "when": "view == hyperGit.branches && viewItem =~ /hyperGit.branch|hyperGit.remoteBranch/", "group": "1_branch@4" }, + { "command": "hyperGit.branchRename", "when": "view == hyperGit.branches && viewItem == hyperGit.branch", "group": "1_branch@5" }, + { "command": "hyperGit.compareBranches", "when": "view == hyperGit.branches && viewItem =~ /hyperGit.branch|hyperGit.remoteBranch/", "group": "1_branch@6" }, + { "command": "hyperGit.cherryPick", "when": "view == hyperGit.log && viewItem == hyperGit.commit", "group": "1_commit@1" }, + { "command": "hyperGit.revertCommit", "when": "view == hyperGit.log && viewItem == hyperGit.commit", "group": "1_commit@2" }, + { "command": "hyperGit.ignorePath", "when": "view == hyperGit.changes && viewItem == hyperGit.fileChange", "group": "1_file@4" }, + { "command": "hyperGit.stashApply", "when": "view == hyperGit.stash && viewItem == hyperGit.stash", "group": "1_stash@1" }, + { "command": "hyperGit.stashPop", "when": "view == hyperGit.stash && viewItem == hyperGit.stash", "group": "1_stash@2" }, + { "command": "hyperGit.stashDrop", "when": "view == hyperGit.stash && viewItem == hyperGit.stash", "group": "1_stash@3" } ], "commandPalette": [ { "command": "hyperGit.openDiff", "when": "false" }, @@ -171,7 +183,12 @@ { "command": "hyperGit.stashApply", "when": "false" }, { "command": "hyperGit.stashPop", "when": "false" }, { "command": "hyperGit.stashDrop", "when": "false" }, - { "command": "hyperGit.discardChanges", "when": "false" } + { "command": "hyperGit.discardChanges", "when": "false" }, + { "command": "hyperGit.cherryPick", "when": "false" }, + { "command": "hyperGit.revertCommit", "when": "false" }, + { "command": "hyperGit.branchRename", "when": "false" }, + { "command": "hyperGit.compareBranches", "when": "false" }, + { "command": "hyperGit.ignorePath", "when": "false" } ] }, "configuration": { diff --git a/src/adapter/git-cli-commands.ts b/src/adapter/git-cli-commands.ts new file mode 100644 index 0000000..c63c6d7 --- /dev/null +++ b/src/adapter/git-cli-commands.ts @@ -0,0 +1,201 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import type { BranchNode, BranchesTreeProvider } from './tree/branches-tree'; +import type { ChangeItem, GitRepositoryService } from './git-repository-service'; +import type { LogNode } from './tree/log-tree'; + +const errMsg = (e: unknown): string => (e instanceof Error ? e.message : String(e)); + +/** + * 注册经 git CLI 补齐的操作(M5' batch 1):cherry-pick / revert / reset / branch rename / + * ignore / compare branches / reword。均经 `service.execGit`(复用 vscode.git 的同一 git 二进制)。 + */ +export function registerGitCliCommands(service: GitRepositoryService, branchesTree: BranchesTreeProvider): vscode.Disposable[] { + const subs: vscode.Disposable[] = []; + + subs.push( + vscode.commands.registerCommand('hyperGit.cherryPick', async (node?: LogNode) => { + const repo = service.repo; + if (!repo) { + return; + } + const hash = node?.kind === 'commit' ? node.commit.hash : await pickCommitHash(service); + if (!hash) { + return; + } + try { + await service.execGit(['cherry-pick', hash]); + void vscode.window.showInformationMessage(`Cherry-pick ${hash.slice(0, 7)} 完成`); + } catch (e) { + void vscode.window.showErrorMessage(`Cherry-pick 失败:${errMsg(e)}`); + } + }), + ); + + subs.push( + vscode.commands.registerCommand('hyperGit.revertCommit', async (node?: LogNode) => { + const repo = service.repo; + if (!repo) { + return; + } + const hash = node?.kind === 'commit' ? node.commit.hash : await pickCommitHash(service); + if (!hash) { + return; + } + try { + await service.execGit(['revert', '--no-edit', hash]); + void vscode.window.showInformationMessage(`Revert ${hash.slice(0, 7)} 完成`); + } catch (e) { + void vscode.window.showErrorMessage(`Revert 失败:${errMsg(e)}`); + } + }), + ); + + subs.push( + vscode.commands.registerCommand('hyperGit.resetHead', async () => { + const repo = service.repo; + if (!repo) { + return; + } + const items = [ + { label: 'soft', description: '保留工作区与暂存区改动' }, + { label: 'mixed', description: '保留工作区改动,取消暂存(默认)' }, + { label: 'hard', description: '⚠ 丢弃所有改动(不可撤销)' }, + { label: 'keep', description: '保留工作区已修改文件' }, + ]; + const pick = await vscode.window.showQuickPick(items, { placeHolder: '选择 reset 模式(作用于 HEAD)' }); + if (!pick) { + return; + } + if (pick.label === 'hard') { + const ok = await vscode.window.showWarningMessage('hard reset 将丢弃所有改动,确认?', { modal: true }, '确认 hard reset'); + if (!ok) { + return; + } + } + try { + await service.execGit(['reset', `--${pick.label}`, 'HEAD~0']); + branchesTree.refresh(); + void vscode.window.showInformationMessage(`Reset (--${pick.label}) 完成`); + } catch (e) { + void vscode.window.showErrorMessage(`Reset 失败:${errMsg(e)}`); + } + }), + ); + + subs.push( + vscode.commands.registerCommand('hyperGit.branchRename', async (node?: BranchNode) => { + const repo = service.repo; + if (!repo || node?.kind !== 'branch') { + return; + } + const oldName = node.ref.name ?? ''; + const newName = await vscode.window.showInputBox({ prompt: `重命名分支「${oldName}」`, value: oldName }); + if (!newName || !newName.trim() || newName === oldName) { + return; + } + try { + await service.execGit(['branch', '-m', oldName, newName.trim()]); + branchesTree.refresh(); + } catch (e) { + void vscode.window.showErrorMessage(`重命名失败:${errMsg(e)}`); + } + }), + ); + + subs.push( + vscode.commands.registerCommand('hyperGit.ignorePath', async (change?: ChangeItem) => { + const repo = service.repo; + if (!repo) { + return; + } + const rel = change?.relativePath ?? (await pickRelativePath(service)); + if (!rel) { + return; + } + const ignoreFile = path.join(repo.rootUri.fsPath, '.gitignore'); + try { + await new Promise((resolve, reject) => { + fs.appendFile(ignoreFile, `${rel}\n`, (err) => (err ? reject(err) : resolve())); + }); + void vscode.window.showInformationMessage(`已添加到 .gitignore:${rel}`); + } catch (e) { + void vscode.window.showErrorMessage(`Ignore 失败:${errMsg(e)}`); + } + }), + ); + + subs.push( + vscode.commands.registerCommand('hyperGit.compareBranches', async (node?: BranchNode) => { + const repo = service.repo; + if (!repo) { + return; + } + const refs = repo.state.refs.filter((r) => r.name && (r.type === 0 || r.type === 1)); + const base = node?.kind === 'branch' ? node.ref.name : await vscode.window.showQuickPick(refs.map((r) => r.name!), { placeHolder: '选择 base 分支' }); + if (!base) { + return; + } + const target = await vscode.window.showQuickPick( + refs.filter((r) => r.name !== base).map((r) => r.name!), + { placeHolder: `比较 ${base} 与...` }, + ); + if (!target) { + return; + } + try { + const out = await service.execGit(['diff', '--stat', `${base}...${target}`]); + const doc = await vscode.workspace.openTextDocument({ content: `$ git diff --stat ${base}...${target}\n\n${out}`, language: 'plaintext' }); + await vscode.window.showTextDocument(doc, { preview: true }); + } catch (e) { + void vscode.window.showErrorMessage(`比较失败:${errMsg(e)}`); + } + }), + ); + + subs.push( + vscode.commands.registerCommand('hyperGit.rewordCommit', async () => { + const repo = service.repo; + if (!repo) { + return; + } + const message = await vscode.window.showInputBox({ prompt: '改写最新提交信息(amend)', value: (await repo.log({ maxEntries: 1 }))[0]?.message ?? '' }); + if (!message || !message.trim()) { + return; + } + try { + await repo.commit(message.trim(), { amend: true }); + void vscode.window.showInformationMessage('已改写最新提交'); + } catch (e) { + void vscode.window.showErrorMessage(`改写失败:${errMsg(e)}`); + } + }), + ); + + return subs; +} + +async function pickCommitHash(service: GitRepositoryService): Promise { + const repo = service.repo; + if (!repo) { + return undefined; + } + const commits = await repo.log({ maxEntries: 30 }); + const items = commits.map((c) => ({ + label: (c.message.split('\n', 1)[0] ?? c.hash).slice(0, 50), + description: `${c.authorName ?? ''} · ${c.hash.slice(0, 7)}`, + hash: c.hash, + })); + const pick = await vscode.window.showQuickPick(items, { placeHolder: '选择 commit' }); + return pick?.hash; +} + +async function pickRelativePath(service: GitRepositoryService): Promise { + const changes = service.getChanges(); + if (changes.length === 0) { + return undefined; + } + const pick = await vscode.window.showQuickPick(changes.map((c) => ({ label: c.relativePath })), { placeHolder: '选择要忽略的文件' }); + return pick?.label; +} diff --git a/src/adapter/git-repository-service.ts b/src/adapter/git-repository-service.ts index b7b339d..1c8a81b 100644 --- a/src/adapter/git-repository-service.ts +++ b/src/adapter/git-repository-service.ts @@ -1,3 +1,4 @@ +import { execFile } from 'child_process'; import * as path from 'path'; import * as vscode from 'vscode'; import type { API, Change, Repository } from '../types/git'; @@ -100,6 +101,27 @@ export class GitRepositoryService implements vscode.Disposable { return this.api.toGitUri(uri, ref); } + /** + * 受控 git CLI 通道:复用 vscode.git 的同一 git 二进制(`api.git.path`),补齐稳定 API 未暴露的操作 + * (cherry-pick / revert / reset / branch rename / stash list / compare 等)。仓库根为工作目录。 + * 仅作为 API 缺口的补充,不重造 vscode.git 已覆盖的能力。 + */ + async execGit(args: string[]): Promise { + const repo = this._repo; + if (!repo) { + throw new Error('未找到 Git 仓库'); + } + return new Promise((resolve, reject) => { + execFile(this.api.git.path, args, { cwd: repo.rootUri.fsPath, maxBuffer: 20 * 1024 * 1024, encoding: 'utf8' }, (err, stdout) => { + if (err) { + reject(err); + } else { + resolve(stdout); + } + }); + }); + } + dispose(): void { this.repoSub?.dispose(); this.disposables.forEach((d) => d.dispose()); diff --git a/src/adapter/stash-commands.ts b/src/adapter/stash-commands.ts index 42dba78..17ef521 100644 --- a/src/adapter/stash-commands.ts +++ b/src/adapter/stash-commands.ts @@ -1,13 +1,12 @@ import * as vscode from 'vscode'; import type { GitRepositoryService } from './git-repository-service'; -import type { StashTreeProvider } from './tree/stash-tree'; +import type { StashNode, StashTreeProvider } from './tree/stash-tree'; /** * 注册 Stash 相关命令(M4)。 * * 经 vscode.git 稳定 API:createStash / applyStash / popStash / dropStash。 - * 因 API 无 stash 列表枚举(见 StashTreeProvider),apply/pop/drop 作用于 stash@{0}(最新)。 - * 「Shelve Changes」以 stash 近似(MVP,见工程方案 §4 P2)。 + * stash 列表由 `StashTreeProvider`(execGit `git stash list`)枚举,apply/pop/drop 按 stash@{n} 真实索引。 */ export function registerStashCommands(service: GitRepositoryService, stashTree: StashTreeProvider): vscode.Disposable[] { const subs: vscode.Disposable[] = []; @@ -30,13 +29,14 @@ export function registerStashCommands(service: GitRepositoryService, stashTree: ); subs.push( - vscode.commands.registerCommand('hyperGit.stashApply', async () => { + vscode.commands.registerCommand('hyperGit.stashApply', async (node?: StashNode) => { const repo = service.repo; if (!repo) { return; } + const index = node?.kind === 'stash' ? node.index : 0; try { - await repo.applyStash(0); + await repo.applyStash(index); stashTree.refresh(); } catch (e) { void vscode.window.showErrorMessage(`Stash 应用失败:${errMsg(e)}`); @@ -45,13 +45,14 @@ export function registerStashCommands(service: GitRepositoryService, stashTree: ); subs.push( - vscode.commands.registerCommand('hyperGit.stashPop', async () => { + vscode.commands.registerCommand('hyperGit.stashPop', async (node?: StashNode) => { const repo = service.repo; if (!repo) { return; } + const index = node?.kind === 'stash' ? node.index : 0; try { - await repo.popStash(0); + await repo.popStash(index); stashTree.refresh(); } catch (e) { void vscode.window.showErrorMessage(`Stash pop 失败:${errMsg(e)}`); @@ -60,15 +61,16 @@ export function registerStashCommands(service: GitRepositoryService, stashTree: ); subs.push( - vscode.commands.registerCommand('hyperGit.stashDrop', async () => { + vscode.commands.registerCommand('hyperGit.stashDrop', async (node?: StashNode) => { const repo = service.repo; if (!repo) { return; } - const choice = await vscode.window.showWarningMessage('删除最新的 stash(stash@{0})?', { modal: true }, '删除'); + const index = node?.kind === 'stash' ? node.index : 0; + const choice = await vscode.window.showWarningMessage(`删除 stash@{${index}}?`, { modal: true }, '删除'); if (choice === '删除') { try { - await repo.dropStash(0); + await repo.dropStash(index); stashTree.refresh(); } catch (e) { void vscode.window.showErrorMessage(`Stash 删除失败:${errMsg(e)}`); diff --git a/src/adapter/tree/stash-tree.ts b/src/adapter/tree/stash-tree.ts index 73631e2..2543bb7 100644 --- a/src/adapter/tree/stash-tree.ts +++ b/src/adapter/tree/stash-tree.ts @@ -1,29 +1,59 @@ import * as vscode from 'vscode'; +import type { GitRepositoryService } from '../git-repository-service'; + +export interface StashEntryNode { + readonly kind: 'stash'; + readonly index: number; + readonly message: string; +} + +export type StashNode = StashEntryNode; /** * Stash 视图 TreeDataProvider。 * - * ⚠️ vscode.git 稳定 API 不暴露 `git stash list`。曾尝试 `log({ refNames: ['stash'] })`, - * 但经实测其仅返回**最新一个 stash 的内部提交**(WIP commit / parent / index commit), - * 非 stash 栈列表(语义错误)——故弃用。 - * - * 现视图保持空(由 viewsWelcome 引导),stash 操作(create/apply/pop/drop)作用于 stash@{0}(最新)。 - * 多 stash 列表枚举为 API 限制,未来经 git CLI 兜底或 proposed API 评估(见 implementation-status §3)。 + * vscode.git 稳定 API 不暴露 `git stash list`(`log({ refNames: ['stash'] })` 仅返回最新 stash 的 + * 内部提交,语义错误)。故改用受控 CLI 通道 `service.execGit(['stash','list'])` 枚举真实 stash 栈, + * index 与 `stash@{n}` 严格对应(供 apply/pop/drop)。 */ -export class StashTreeProvider implements vscode.TreeDataProvider, vscode.Disposable { - private readonly _onDidChange = new vscode.EventEmitter(); +export class StashTreeProvider implements vscode.TreeDataProvider, vscode.Disposable { + private readonly _onDidChange = new vscode.EventEmitter(); readonly onDidChangeTreeData = this._onDidChange.event; + constructor(private readonly service: GitRepositoryService) {} + refresh(): void { this._onDidChange.fire(undefined); } - getTreeItem(): vscode.TreeItem { - return new vscode.TreeItem(''); + async getChildren(): Promise { + const repo = this.service.repo; + if (!repo) { + return []; + } + try { + const out = await this.service.execGit(['stash', 'list']); + return out + .split('\n') + .filter((line) => line.trim().length > 0) + .map((line): StashEntryNode => { + const m = line.match(/^stash@\{(\d+)\}:\s*(.*)$/); + return { kind: 'stash', index: m ? Number(m[1]) : 0, message: m ? m[2] : line }; + }); + } catch { + return []; + } } - getChildren(): never[] { - return []; + getTreeItem(node: StashNode): vscode.TreeItem { + const subject = node.message.split(':').slice(1).join(':').trim() || node.message; + const item = new vscode.TreeItem(subject.slice(0, 60), vscode.TreeItemCollapsibleState.None); + item.id = `stash:${node.index}`; + item.description = `stash@{${node.index}}`; + item.tooltip = `stash@{${node.index}}\n${node.message}`; + item.contextValue = 'hyperGit.stash'; + item.iconPath = new vscode.ThemeIcon('archive'); + return item; } dispose(): void { diff --git a/src/extension.ts b/src/extension.ts index a878c6e..1260353 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,6 +12,7 @@ import { ChangesTreeProvider, EmptyChangesProvider } from './adapter/tree/change import { LogTreeProvider } from './adapter/tree/log-tree'; import { registerHistoryCommands } from './adapter/history-commands'; import { registerStashCommands } from './adapter/stash-commands'; +import { registerGitCliCommands } from './adapter/git-cli-commands'; import { StashTreeProvider } from './adapter/tree/stash-tree'; import { CommitWebviewProvider } from './adapter/webview/commit-webview'; import { getGitApi } from './adapter/git-api'; @@ -57,7 +58,7 @@ export async function activate(context: vscode.ExtensionContext): Promise const commitView = new CommitWebviewProvider(service, registry, commit); const logTree = new LogTreeProvider(service); const branchesTree = new BranchesTreeProvider(service); - const stashTree = new StashTreeProvider(); + const stashTree = new StashTreeProvider(service); const focusCommitView = (): void => { void vscode.commands.executeCommand('hyperGit.commit.focus'); }; @@ -76,6 +77,7 @@ export async function activate(context: vscode.ExtensionContext): Promise vscode.window.registerTreeDataProvider('hyperGit.stash', stashTree), ...registerChangesCommands(service, registry, tree), ...registerHistoryCommands(service, logTree, branchesTree), + ...registerGitCliCommands(service, branchesTree), ...registerStashCommands(service, stashTree), vscode.commands.registerCommand('hyperGit.commit', focusCommitView), vscode.commands.registerCommand('hyperGit.commitAndPush', focusCommitView), diff --git a/tests/suite/extension.test.js b/tests/suite/extension.test.js index 3af7245..65facbf 100644 --- a/tests/suite/extension.test.js +++ b/tests/suite/extension.test.js @@ -45,6 +45,13 @@ suite('扩展冒烟测试', function () { 'hyperGit.pull', 'hyperGit.push', 'hyperGit.fetch', + 'hyperGit.cherryPick', + 'hyperGit.revertCommit', + 'hyperGit.resetHead', + 'hyperGit.branchRename', + 'hyperGit.ignorePath', + 'hyperGit.compareBranches', + 'hyperGit.rewordCommit', ]) { assert.ok(commands.includes(cmd), `命令 ${cmd} 未注册`); }