fix diff and git run

This commit is contained in:
程广 2025-02-26 12:40:52 +08:00
parent 0038a12534
commit ace4c437eb
4 changed files with 257 additions and 107 deletions

View File

@ -13,6 +13,9 @@
"ui",
"workspace"
],
"extensionDependencies": [
"vscode.git"
],
"activationEvents": [],
"main": "./dist/extension.js",
"contributes": {

View File

@ -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<any[]> {
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<any[]> {
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<string[]> {
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);
}
});
});
}
}
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<string> {
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());
}
}
);
});
}
}

View File

@ -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);

View File

@ -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 {
<div id="app">
<el-input v-model="filterText" placeholder="Enter commit message to filter"></el-input>
<el-button @click="filterCommits"></el-button>
<el-tree :data="commits" :props="defaultProps" ></el-tree>
<el-tree :data="commits" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</div>
<script src="${webviewView.webview.asWebviewUri(
vscode.Uri.file(
@ -117,20 +193,63 @@ export class GViewProvider implements vscode.WebviewViewProvider {
filterText: '',
commits: [],
defaultProps: {
children: 'files',
children: 'children',
label: 'message'
}
};
},
methods: {
handleNodeClick(data, node, component) {
if(data.type === 'file'){
vscode.postMessage({ command: 'openFile', filePath: data.fullPath, hash: data.hash });
}
},
filterCommits() {
vscode.postMessage({ command: 'filterCommits', text: this.filterText });
},
updateCommits(commits) {
this.commits = commits.map(commit => ({
message: commit.msg,
files: commit.files.map(file => ({ message: file }))
}));
// this.commits = commits.map(commit => ({
// message: commit.msg,
// files: commit.files.map(file => ({ message: file }))
// }));
const nodes = [];
for(let commit of commits){
const node = {
message: commit.msg + ' -- ' + commit.author + '@' + commit.date,
type: 'commit',
children:[]
}
if(commit.files) {
//group path to tree
for(let file of commit.files){
const path = file.split('/');
let currentNode = node;
for(let i = 0; i < path.length; i++){
const pathItem = path[i];
let childNode = currentNode.children.find(child => child.message === pathItem);
if(!childNode){
childNode= {}
currentNode.children.push(childNode);
}
if(i === path.length - 1){
childNode.message = pathItem;
childNode.type = 'file';
childNode.fullPath = file;
childNode.hash = commit.hash;
} else {
childNode.message = childNode.message||pathItem;
childNode.type = 'folder';
childNode.children = childNode.children||[];
}
currentNode = childNode;
}
}
}
nodes.push(node);
}
this.commits = nodes;
}
},
mounted() {