From ace4c437ebc80c3eb68982529ba802d959cf46d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=B9=BF?= Date: Wed, 26 Feb 2025 12:40:52 +0800 Subject: [PATCH] fix diff and git run --- package.json | 3 + src/GitService.ts | 208 +++++++++++++++++++++++-------------------- src/extension.ts | 22 ++++- src/view.provider.ts | 131 +++++++++++++++++++++++++-- 4 files changed, 257 insertions(+), 107 deletions(-) diff --git a/package.json b/package.json index a706261..7bf45a3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,9 @@ "ui", "workspace" ], + "extensionDependencies": [ + "vscode.git" +], "activationEvents": [], "main": "./dist/extension.js", "contributes": { diff --git a/src/GitService.ts b/src/GitService.ts index 501f794..f1f55b4 100644 --- a/src/GitService.ts +++ b/src/GitService.ts @@ -1,109 +1,121 @@ -import * as vscode from 'vscode'; -import * as child_process from 'child_process'; +import * as vscode from "vscode"; +import * as child_process from "child_process"; -const GitFilter:{[key: string]: string} = { - s: 'since', - u: 'until', - a: 'author', - g: 'grep', +const GitFilter: { [key: string]: string } = { + s: "since", + u: "until", + a: "author", + g: "grep", }; export interface GitCommit { - hash?: string; - author?: string; - date?: string; - msg?: string; - files?: string[]; + hash?: string; + author?: string; + date?: string; + msg?: string; + files?: string[]; } export class GitService { - private _repoPath: string; + private _repoPath: string; - constructor() { - const workspaceFolders = vscode.workspace.workspaceFolders; - if (!workspaceFolders || workspaceFolders.length === 0) { - throw new Error('No workspace folder opened'); - } - this._repoPath = workspaceFolders[0].uri.fsPath; + constructor(private gitPath: string) { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + throw new Error("No workspace folder opened"); } + this._repoPath = workspaceFolders[0].uri.fsPath; + } - public async getCommits(filter: string = ''): Promise { - const filterParts = filter.split(' '); - const nFilter: string[] = []; - filterParts.forEach((part) => { - const t = part.split(':'); - const key = t[0]; - const v = t[1]; - if (key in GitFilter) { - nFilter.push(`--${GitFilter[key]}=${v}`); + public async getCommits(filter: string = ""): Promise { + const filterParts = filter.split(" "); + const nFilter: string[] = []; + filterParts.forEach((part) => { + const t = part.split(":"); + const key = t[0]; + const v = t[1]; + if (key in GitFilter) { + nFilter.push(`--${GitFilter[key]}=${v}`); + } + }); + const command = `git log --oneline ${nFilter.join( + " " + )} --pretty=format:"{\\\"hash\\\":\\\"%h\\\",\\\"author\\\":\\\"%an\\\",\\\"date\\\":\\\"%ad\\\",\\\"msg\\\":\\\"%s\\\"}%n---" --date=format:'%Y-%m-%d' --name-only`; + const gitArgs = [ + "log", + "--oneline", + ...nFilter, + '--pretty=format:"{\\"hash\\":\\"%h\\",\\"author\\":\\"%an\\",\\"date\\":\\"%ad\\",\\"msg\\":\\"%s\\"}"%n---', + "--date=format:%Y-%m-%d", + "--name-only", + ]; + const gitProcess = child_process.spawn(this.gitPath, gitArgs, { + cwd: this._repoPath, + }); + + return new Promise((resolve, reject) => { + let lines: string[] = []; + let commits: GitCommit[] = []; + function makeCommit(lines: any[]): GitCommit | undefined { + try { + const commitLine = lines[0]; + console.log(commitLine); + const commit: GitCommit = JSON.parse(lines[0]); + commit.files = []; + for (let i = 1; i < lines.length; i++) { + if (lines[i].trim() !== "") { + commit.files?.push(lines[i]); } - }); - const command = `git log --oneline ${nFilter.join(' ')} --pretty=format:"{\\\"hash\\\":\\\"%h\\\",\\\"author\\\":\\\"%an\\\",\\\"date\\\":\\\"%ad\\\",\\\"msg\\\":\\\"%s\\\"}%n---" --date=format:'%Y-%m-%d' --name-only`; - return new Promise((resolve, reject) => { - child_process.exec(command, { cwd: this._repoPath }, (error, stdout, stderr) => { - if (error) { - reject(error); - } else { - // const commits = stdout.split('\n').map(line => { - // try { - // const commit = JSON.parse(line); - // return commit; - // } catch (e) { - // return null; - // } - // }).filter(commit => commit.hash); - const lines = stdout.split('\n') - let commit:GitCommit|undefined = {}; - let commits:GitCommit[] = []; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.startsWith('{')) { - try{ - commit = JSON.parse(line); - if(!commit){ - continue - } - commit.files = []; - }catch(e){ - commit = undefined - console.log(e); - } - - } else if (line === '---') { - if(commit){ - commits.push(commit); - } - - } else if (line.trim() !== '') { - if(!commit){ - continue; - } - if(!commit.files){ - commit.files = []; - } - commit.files.push(line); - } else { - // do nothing - console.log('end'); - } - - } - resolve(commits); - } - }); - }); - } + } + return commit; + } catch (e) { + console.log(e); + } - public async getCommitFiles(commit: string): Promise { - const command = `git show --name-only --pretty='' ${commit}`; - return new Promise((resolve, reject) => { - child_process.exec(command, { cwd: this._repoPath }, (error, stdout, stderr) => { - if (error) { - reject(error); - } else { - const files = stdout.split('\n').filter(file => file); - resolve(files); - } - }); - }); - } -} \ No newline at end of file + return undefined; + } + gitProcess.stdout.setEncoding("utf8"); + gitProcess.stdout.on("data", (data) => { + const nlines = data.split("\n"); + for (let nline of nlines) { + if (nline.startsWith("\"{")) { + lines = []; + lines.push(JSON.parse(nline)); + } else if (nline.startsWith("---") ) { + continue + }else if( nline.trim() === ""){ + const commit = makeCommit(lines); + if(commit){ + commits.push(commit); + } + } else if (nline.trim() !== "") { + lines.push(nline); + } + } + }); + gitProcess.on("exit",(code,signal)=>{ + if(code === 0){ + resolve(commits); + }else{ + reject(new Error(`git log exited with code ${code}`)); + } + }); + }); + } + + public async getPreviousCommit(hash: string): Promise { + const command = `git log --pretty=format:"%h" -n 1 ${hash}^`; + return new Promise((resolve, reject) => { + child_process.exec( + command, + { cwd: this._repoPath }, + (error, stdout, stderr) => { + if (error) { + reject(error); + } else { + resolve(stdout.trim()); + } + } + ); + }); + } +} diff --git a/src/extension.ts b/src/extension.ts index eace552..8a991d9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,9 +3,25 @@ import * as vscode from 'vscode'; import { GitService } from './GitService'; import { GViewProvider } from './view.provider'; -export function activate(context: vscode.ExtensionContext) { - console.log('Congratulations, your extension "commit-panel" is now active!'); - const gitService = new GitService(); +function getGitApi(){ + const gitExtension = vscode.extensions.getExtension<'vscode.git'>('vscode.git'); + if(gitExtension && gitExtension.isActive){ + const api = (gitExtension.exports as any).getAPI(1); + return api.git.path; + }else{ + throw new Error('Git extension is not active'); + } + +} + +export async function activate(context: vscode.ExtensionContext) { + const gitPath = getGitApi(); + if(!gitPath){ + vscode.window.showErrorMessage('No Git avaliable'); + return; + } + + const gitService = new GitService(gitPath); const viewProvider = new GViewProvider(context, gitService); diff --git a/src/view.provider.ts b/src/view.provider.ts index 4920a9b..ef90543 100644 --- a/src/view.provider.ts +++ b/src/view.provider.ts @@ -29,12 +29,88 @@ export class GViewProvider implements vscode.WebviewViewProvider { const filterText = message.text; this.filterCommits(filterText); return; + case "openFile": + const filePath = message.filePath; + const commit = message.hash; + // should open git change for file and commit + // vscode.commands.executeCommand('git.showCommit',commit) + // this.openFileInCommit(commit, filePath); + this.openDiff(commit, filePath); + return; } }, undefined, this.context.subscriptions ); } +private makeUri(repoPath:string, filePath:string, commit:string) { + const repoUri = vscode.Uri.file(repoPath) + const fUri = vscode.Uri.joinPath(repoUri, filePath); + const params = { + path: fUri.fsPath, + ref: commit + } + return fUri.with({ scheme: 'git',path:fUri.path, query: JSON.stringify(params) }); +} +private openFileInCommit(commitHash: any, filePath: any) { + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + if(!workspaceFolder){ + throw new Error('Please open a workspace folder first'); + } + const repoPath = workspaceFolder.replace(/\\/g, '/') + .replace(/'/g, "'") + .replace(/ /g, " "); + const fileUri = vscode.Uri.parse(`git://${repoPath}?${commitHash}#${filePath}`); + vscode.workspace.openTextDocument(fileUri).then(doc => { + vscode.window.showTextDocument(doc); + }) +} + +private async openDiff(commitId: string, filePath: string) { + const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + if (!workspaceFolder) { + vscode.window.showErrorMessage('Please open a workspace folder first'); + return; + } + + try { + const rawPrevCommit = await this._gitService.getPreviousCommit(commitId); + const previousCommitId = rawPrevCommit?.trim(); // 去除可能的换行符 + if (!previousCommitId) { + vscode.window.showErrorMessage('Cannot find previous commit'); + return; + } + + // 编码处理 + const encodedCommit = encodeURIComponent(commitId.trim()); + const encodedPrevCommit = encodeURIComponent(previousCommitId); + const sanitizedWorkPath = workspaceFolder.replace(/\\/g, '/') + .replace(/'/g, "'") + .replace(/ /g, " "); + const sanitizedFilePath = filePath.replace(/\\/g, '/') + .replace(/'/g, "'") + .replace(/ /g, " "); + // 构造 URI + const currentUri = this.makeUri(sanitizedWorkPath, sanitizedFilePath, encodedCommit); + + const previousUri = this.makeUri(sanitizedWorkPath, sanitizedFilePath, encodedPrevCommit); + + // 调试输出 + console.log('Current URI:', currentUri.toString()); + console.log('Previous URI:', previousUri.toString()); + + await vscode.commands.executeCommand( + "vscode.diff", + previousUri, + currentUri, + `Changes: ${path.basename(filePath)} (${previousCommitId.slice(0,7)} → ${commitId.slice(0,7)})` + ); + } catch (error) { + vscode.window.showErrorMessage(`Failed to open diff: ${error instanceof Error ? error.message : error}`); + } +} + + generateHtml(webviewView: vscode.WebviewView) { return ` @@ -95,7 +171,7 @@ export class GViewProvider implements vscode.WebviewViewProvider {
过滤 - +