Skip to content

Commit 3f558af

Browse files
committed
feat(Parity-Batch2): Git 提交图 Webview(graph 着色拓扑)+ Console(git 命令输出面板);
🤖 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<threefish.ai@gmail.com>
1 parent 142fb47 commit 3f558af

7 files changed

Lines changed: 125 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
## [Unreleased]
66

7+
### Added — Parity Batch 2(UI 丰富度,0.0.3)
8+
9+
- **Git 提交图(WebviewPanel)**`git log --graph --oneline --decorate --all`(CLI)获取拓扑,语义着色渲染(graph 连线 / refs / hash)——补齐 IDEA Log 提交图的可视化拓扑。命令面板 + Log 视图标题按钮。
10+
- **Console**:Hyper Git Console(OutputChannel)记录所有 `execGit` 命令与输出(对齐 IDEA Console 标签页)。
11+
712
### Added — Parity Batch 1(CLI 功能补齐,0.0.2)
813

914
> 关键转向:引入 `GitRepositoryService.execGit`(复用 vscode.git 的同一 git 二进制 `api.git.path`),补齐稳定 API 未暴露的操作——修正此前"API 限制延后"的过度自我设限。

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Hyper Git",
44
"icon": "media/icon.png",
55
"description": "在 VS Code 上完整复刻 IntelliJ IDEA 的 Git 工具窗口与 Commit 提交窗口,并为未来 AI Agent 自主代理预留架构接缝。",
6-
"version": "0.0.2",
6+
"version": "0.0.3",
77
"publisher": "threefish-ai",
88
"license": "MIT",
99
"preview": true,
@@ -130,13 +130,16 @@
130130
{ "command": "hyperGit.branchRename", "title": "重命名分支", "category": "Hyper Git" },
131131
{ "command": "hyperGit.ignorePath", "title": "添加到 .gitignore", "category": "Hyper Git" },
132132
{ "command": "hyperGit.compareBranches", "title": "比较分支", "category": "Hyper Git" },
133-
{ "command": "hyperGit.rewordCommit", "title": "改写最新提交信息", "category": "Hyper Git" }
133+
{ "command": "hyperGit.rewordCommit", "title": "改写最新提交信息", "category": "Hyper Git" },
134+
{ "command": "hyperGit.showGraph", "title": "查看提交图(Graph)", "category": "Hyper Git", "icon": "$(git-commit)" },
135+
{ "command": "hyperGit.showConsole", "title": "打开 Console", "category": "Hyper Git" }
134136
],
135137
"menus": {
136138
"view/title": [
137139
{ "command": "hyperGit.refresh", "when": "view == hyperGit.changes", "group": "navigation" },
138140
{ "command": "hyperGit.newChangelist", "when": "view == hyperGit.changes", "group": "navigation" },
139141
{ "command": "hyperGit.refreshLog", "when": "view == hyperGit.log", "group": "navigation" },
142+
{ "command": "hyperGit.showGraph", "when": "view == hyperGit.log", "group": "navigation" },
140143
{ "command": "hyperGit.logFilterAuthor", "when": "view == hyperGit.log" },
141144
{ "command": "hyperGit.logFilterPath", "when": "view == hyperGit.log" },
142145
{ "command": "hyperGit.logClearFilter", "when": "view == hyperGit.log" },

src/adapter/git-repository-service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { execFile } from 'child_process';
22
import * as path from 'path';
33
import * as vscode from 'vscode';
4+
import { logGit } from '../infra/git-console';
45
import type { API, Change, Repository } from '../types/git';
56
import { FileStatus } from '../engine/model';
67
import { mapGitStatus } from './git-status-map';
@@ -114,8 +115,10 @@ export class GitRepositoryService implements vscode.Disposable {
114115
return new Promise((resolve, reject) => {
115116
execFile(this.api.git.path, args, { cwd: repo.rootUri.fsPath, maxBuffer: 20 * 1024 * 1024, encoding: 'utf8' }, (err, stdout) => {
116117
if (err) {
118+
logGit(args, undefined, err.message);
117119
reject(err);
118120
} else {
121+
logGit(args, stdout);
119122
resolve(stdout);
120123
}
121124
});
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as vscode from 'vscode';
2+
import type { GitRepositoryService } from '../git-repository-service';
3+
4+
/**
5+
* Git 提交图(WebviewPanel)。
6+
*
7+
* 用 `git log --graph --oneline --decorate --all`(受控 CLI 通道)获取拓扑文本,在 Webview 内以
8+
* 等宽字体 + 语义着色渲染(graph 连线、refs、hash)——补齐 IDEA Log 提交图的可视化拓扑。
9+
* 完整像素级 lane-SVG 渲染作为后续增强(batch 2.x)。
10+
*/
11+
export class GraphWebview {
12+
private static readonly viewType = 'hyperGit.graph';
13+
14+
static async open(service: GitRepositoryService): Promise<void> {
15+
const repo = service.repo;
16+
if (!repo) {
17+
void vscode.window.showWarningMessage('未找到 Git 仓库');
18+
return;
19+
}
20+
let graph = '';
21+
try {
22+
graph = await service.execGit(['log', '--graph', '--oneline', '--decorate', '--all', '-n', '300']);
23+
} catch (e) {
24+
void vscode.window.showErrorMessage(`获取提交图失败:${e instanceof Error ? e.message : String(e)}`);
25+
return;
26+
}
27+
28+
const panel = vscode.window.createWebviewPanel(GraphWebview.viewType, 'Git Graph — Hyper Git', vscode.ViewColumn.Active, {
29+
enableScripts: false,
30+
retainContextWhenHidden: false,
31+
});
32+
panel.webview.html = GraphWebview.renderHtml(graph, repo.rootUri.fsPath);
33+
}
34+
35+
private static renderHtml(graph: string, repoRoot: string): string {
36+
const lines = graph.split('\n').map(GraphWebview.renderLine).join('\n');
37+
return `<!DOCTYPE html>
38+
<html lang="zh-CN">
39+
<head>
40+
<meta charset="UTF-8">
41+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'">
42+
<style>
43+
body { margin: 0; padding: 12px 16px; font-family: var(--vscode-editor-font-family), ui-monospace, Menlo, Consolas, monospace; font-size: var(--vscode-editor-font-size); color: var(--vscode-foreground); background: var(--vscode-editor-background); }
44+
h3 { margin: 0 0 8px; font-weight: 600; font-size: 13px; opacity: 0.85; }
45+
.repo { opacity: 0.6; font-size: 11px; margin-bottom: 10px; word-break: break-all; }
46+
pre { margin: 0; white-space: pre; line-height: 1.5; overflow-x: auto; }
47+
.graph { color: var(--vscode-gitDecoration-addedResourceForeground, #3fb950); }
48+
.hash { color: var(--vscode-editorWarning-foreground, #d29922); }
49+
.ref { color: var(--vscode-textLink-foreground, #4dabf7); font-weight: 600; }
50+
</style>
51+
</head>
52+
<body>
53+
<h3>Git 提交图(最近 300 条)</h3>
54+
<div class="repo">${escapeHtml(repoRoot)}</div>
55+
<pre>${lines}</pre>
56+
</body>
57+
</html>`;
58+
}
59+
60+
private static renderLine(line: string): string {
61+
const m = line.match(/^([*|/\\_. ]+)(.*)$/);
62+
if (!m) {
63+
return escapeHtml(line);
64+
}
65+
const graphPart = escapeHtml(m[1]);
66+
let rest = escapeHtml(m[2]);
67+
rest = rest.replace(/(\([^)]*\))/g, '<span class="ref">$1</span>');
68+
rest = rest.replace(/\b([0-9a-f]{7,40})\b/g, '<span class="hash">$1</span>');
69+
return `<span class="graph">${graphPart}</span>${rest}`;
70+
}
71+
}
72+
73+
function escapeHtml(s: string): string {
74+
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
75+
}

src/extension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { registerStashCommands } from './adapter/stash-commands';
1515
import { registerGitCliCommands } from './adapter/git-cli-commands';
1616
import { StashTreeProvider } from './adapter/tree/stash-tree';
1717
import { CommitWebviewProvider } from './adapter/webview/commit-webview';
18+
import { GraphWebview } from './adapter/webview/graph-webview';
19+
import { showGitConsole } from './infra/git-console';
1820
import { getGitApi } from './adapter/git-api';
1921
import { GitRepositoryService } from './adapter/git-repository-service';
2022
import { createLogger } from './infra/logger';
@@ -81,6 +83,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
8183
...registerStashCommands(service, stashTree),
8284
vscode.commands.registerCommand('hyperGit.commit', focusCommitView),
8385
vscode.commands.registerCommand('hyperGit.commitAndPush', focusCommitView),
86+
vscode.commands.registerCommand('hyperGit.showGraph', () => GraphWebview.open(service)),
87+
vscode.commands.registerCommand('hyperGit.showConsole', () => showGitConsole()),
8488
);
8589

8690
// git 状态变化频繁(add/checkout/diff 缓存失效均触发),防抖合并避免 log/stash 高频重拉。

src/infra/git-console.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as vscode from 'vscode';
2+
3+
/**
4+
* Hyper Git Console:对齐 IDEA Console 标签页,记录所有经 execGit 执行的 git 命令及其输出。
5+
* 复用单一 OutputChannel(懒构造)。
6+
*/
7+
let channel: vscode.OutputChannel | undefined;
8+
9+
function getChannel(): vscode.OutputChannel {
10+
if (!channel) {
11+
channel = vscode.window.createOutputChannel('Hyper Git Console');
12+
}
13+
return channel;
14+
}
15+
16+
/** 记录一条 git 命令(及其输出/错误)到 Console。 */
17+
export function logGit(args: readonly string[], output?: string, error?: string): void {
18+
const c = getChannel();
19+
c.appendLine(`$ git ${args.join(' ')}`);
20+
if (output) {
21+
c.appendLine(output);
22+
}
23+
if (error) {
24+
c.appendLine(`[error] ${error}`);
25+
}
26+
}
27+
28+
/** 显示 Console 面板。 */
29+
export function showGitConsole(): void {
30+
getChannel().show(true);
31+
}

tests/suite/extension.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ suite('扩展冒烟测试', function () {
5252
'hyperGit.ignorePath',
5353
'hyperGit.compareBranches',
5454
'hyperGit.rewordCommit',
55+
'hyperGit.showGraph',
56+
'hyperGit.showConsole',
5557
]) {
5658
assert.ok(commands.includes(cmd), `命令 ${cmd} 未注册`);
5759
}

0 commit comments

Comments
 (0)