fix diff and git run
This commit is contained in:
parent
0038a12534
commit
ace4c437eb
|
@ -13,6 +13,9 @@
|
|||
"ui",
|
||||
"workspace"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"vscode.git"
|
||||
],
|
||||
"activationEvents": [],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue