mirror of
https://github.com/ckaczor/vscode-gitlens.git
synced 2026-02-16 10:58:34 -05:00
Switches to porcelain blame format
Provides more data (commit message, previous commit, etc)
This commit is contained in:
17
src/git.ts
17
src/git.ts
@@ -23,7 +23,7 @@ export default class Git {
|
|||||||
|
|
||||||
if (sha) {
|
if (sha) {
|
||||||
console.log('git', 'blame', '-fn', '--root', `${sha}^`, '--', fileName);
|
console.log('git', 'blame', '-fn', '--root', `${sha}^`, '--', fileName);
|
||||||
return gitCommand(repoPath, 'blame', '-fnw', '--root', `${sha}^`, '--', fileName);
|
return gitCommand(repoPath, 'blame', '-fn', '--root', `${sha}^`, '--', fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('git', 'blame', '-fn', '--root', '--', fileName);
|
console.log('git', 'blame', '-fn', '--root', '--', fileName);
|
||||||
@@ -32,6 +32,20 @@ export default class Git {
|
|||||||
// .catch(ex => console.error(ex));
|
// .catch(ex => console.error(ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static blamePorcelain(fileName: string, repoPath: string, sha?: string) {
|
||||||
|
fileName = Git.normalizePath(fileName, repoPath);
|
||||||
|
|
||||||
|
if (sha) {
|
||||||
|
console.log('git', 'blame', '--porcelain', '--root', `${sha}^`, '--', fileName);
|
||||||
|
return gitCommand(repoPath, 'blame', '--porcelain', '--root', `${sha}^`, '--', fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('git', 'blame', '--porcelain', '--root', '--', fileName);
|
||||||
|
return gitCommand(repoPath, 'blame', '--porcelain', '--root', '--', fileName);
|
||||||
|
// .then(s => { console.log(s); return s; })
|
||||||
|
// .catch(ex => console.error(ex));
|
||||||
|
}
|
||||||
|
|
||||||
static getVersionedFile(fileName: string, repoPath: string, sha: string) {
|
static getVersionedFile(fileName: string, repoPath: string, sha: string) {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
Git.getVersionedFileText(fileName, repoPath, sha).then(data => {
|
Git.getVersionedFileText(fileName, repoPath, sha).then(data => {
|
||||||
@@ -79,6 +93,7 @@ export default class Git {
|
|||||||
static getCommitMessages(fileName: string, repoPath: string) {
|
static getCommitMessages(fileName: string, repoPath: string) {
|
||||||
fileName = Git.normalizePath(fileName, repoPath);
|
fileName = Git.normalizePath(fileName, repoPath);
|
||||||
|
|
||||||
|
// git log --format="%h (%aN %x09 %ai) %s" --
|
||||||
console.log('git', 'log', '--oneline', '--', fileName);
|
console.log('git', 'log', '--oneline', '--', fileName);
|
||||||
return gitCommand(repoPath, 'log', '--oneline', '--', fileName);
|
return gitCommand(repoPath, 'log', '--oneline', '--', fileName);
|
||||||
// .then(s => { console.log(s); return s; })
|
// .then(s => { console.log(s); return s; })
|
||||||
|
|||||||
@@ -76,14 +76,14 @@ export default class GitBlameController extends Disposable {
|
|||||||
class GitBlameEditorController extends Disposable {
|
class GitBlameEditorController extends Disposable {
|
||||||
private _subscription: Disposable;
|
private _subscription: Disposable;
|
||||||
private _blame: Promise<IGitBlame>;
|
private _blame: Promise<IGitBlame>;
|
||||||
private _commits: Promise<Map<string, string>>;
|
//private _commits: Promise<Map<string, string>>;
|
||||||
|
|
||||||
constructor(private git: GitProvider, private blameDecoration: TextEditorDecorationType, private highlightDecoration: TextEditorDecorationType, public editor: TextEditor, public sha: string) {
|
constructor(private git: GitProvider, private blameDecoration: TextEditorDecorationType, private highlightDecoration: TextEditorDecorationType, public editor: TextEditor, public sha: string) {
|
||||||
super(() => this.dispose());
|
super(() => this.dispose());
|
||||||
|
|
||||||
const fileName = this.editor.document.uri.path;
|
const fileName = this.editor.document.uri.path;
|
||||||
this._blame = this.git.getBlameForFile(fileName);
|
this._blame = this.git.getBlameForFile(fileName);
|
||||||
this._commits = this.git.getCommitMessages(fileName);
|
//this._commits = this.git.getCommitMessages(fileName);
|
||||||
|
|
||||||
this._subscription = Disposable.from(window.onDidChangeTextEditorSelection(e => {
|
this._subscription = Disposable.from(window.onDidChangeTextEditorSelection(e => {
|
||||||
const activeLine = e.selections[0].active.line;
|
const activeLine = e.selections[0].active.line;
|
||||||
@@ -106,22 +106,22 @@ class GitBlameEditorController extends Disposable {
|
|||||||
return this._blame.then(blame => {
|
return this._blame.then(blame => {
|
||||||
if (!blame.lines.length) return;
|
if (!blame.lines.length) return;
|
||||||
|
|
||||||
return this._commits.then(msgs => {
|
// return this._commits.then(msgs => {
|
||||||
const commits = Array.from(blame.commits.values());
|
// const commits = Array.from(blame.commits.values());
|
||||||
commits.forEach(c => c.message = msgs.get(c.sha.substring(0, c.sha.length - 1)));
|
// commits.forEach(c => c.message = msgs.get(c.sha.substring(0, c.sha.length - 1)));
|
||||||
|
|
||||||
const blameDecorationOptions: DecorationOptions[] = blame.lines.map(l => {
|
const blameDecorationOptions: DecorationOptions[] = blame.lines.map(l => {
|
||||||
const c = blame.commits.get(l.sha);
|
const c = blame.commits.get(l.sha);
|
||||||
return {
|
return {
|
||||||
range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)),
|
range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)),
|
||||||
hoverMessage: `${c.message}\n${c.author}, ${moment(c.date).format('MMMM Do, YYYY hh:MM a')}`,
|
hoverMessage: `${c.message}\n${c.author}, ${moment(c.date).format('MMMM Do, YYYY hh:MM a')}`,
|
||||||
renderOptions: { before: { contentText: `${l.sha}`, } }
|
renderOptions: { before: { contentText: `${l.sha.substring(0, 8)}`, } }
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.editor.setDecorations(this.blameDecoration, blameDecorationOptions);
|
this.editor.setDecorations(this.blameDecoration, blameDecorationOptions);
|
||||||
return this.applyHighlight(sha || commits[0].sha);
|
return this.applyHighlight(sha || blame.commits.values().next().value.sha);
|
||||||
});
|
// });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import * as _ from 'lodash';
|
|||||||
|
|
||||||
const blameMatcher = /^([\^0-9a-fA-F]{8})\s([\S]*)\s+([0-9\S]+)\s\((.*)\s([0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\s[-|+][0-9]{4})\s+([0-9]+)\)(.*)$/gm;
|
const blameMatcher = /^([\^0-9a-fA-F]{8})\s([\S]*)\s+([0-9\S]+)\s\((.*)\s([0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\s[-|+][0-9]{4})\s+([0-9]+)\)(.*)$/gm;
|
||||||
const commitMessageMatcher = /^([\^0-9a-fA-F]{7})\s(.*)$/gm;
|
const commitMessageMatcher = /^([\^0-9a-fA-F]{7})\s(.*)$/gm;
|
||||||
|
const blamePorcelainMatcher = /^([\^0-9a-fA-F]{40})\s([0-9]+)\s([0-9]+)(?:\s([0-9]+))?$\n(?:^author\s(.*)$\n^author-mail\s(.*)$\n^author-time\s(.*)$\n^author-tz\s(.*)$\n^committer\s(.*)$\n^committer-mail\s(.*)$\n^committer-time\s(.*)$\n^committer-tz\s(.*)$\n^summary\s(.*)$\n(?:^previous\s(.*)?\s(.*)$\n)?^filename\s(.*)$\n)?^(.*)$/gm;
|
||||||
|
const blameLinePorcelainMatcher = /^([\^0-9a-fA-F]{40})\s([0-9]+)\s([0-9]+)(?:\s([0-9]+))?$\n^author\s(.*)$\n^author-mail\s(.*)$\n^author-time\s(.*)$\n^author-tz\s(.*)$\n^committer\s(.*)$\n^committer-mail\s(.*)$\n^committer-time\s(.*)$\n^committer-tz\s(.*)$\n^summary\s(.*)$\n(?:^previous\s(.*)?\s(.*)$\n)?^filename\s(.*)$\n^(.*)$/gm;
|
||||||
|
|
||||||
export default class GitProvider extends Disposable {
|
export default class GitProvider extends Disposable {
|
||||||
public repoPath: string;
|
public repoPath: string;
|
||||||
@@ -43,53 +45,138 @@ export default class GitProvider extends Disposable {
|
|||||||
return Git.repoPath(cwd);
|
return Git.repoPath(cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getBlameForFile(fileName: string) {
|
||||||
|
// fileName = Git.normalizePath(fileName, this.repoPath);
|
||||||
|
|
||||||
|
// let blame = this._blames.get(fileName);
|
||||||
|
// if (blame !== undefined) return blame;
|
||||||
|
|
||||||
|
// blame = Git.blame(fileName, this.repoPath)
|
||||||
|
// .then(data => {
|
||||||
|
// const authors: Map<string, IGitAuthor> = new Map();
|
||||||
|
// const commits: Map<string, IGitCommit> = new Map();
|
||||||
|
// const lines: Array<IGitCommitLine> = [];
|
||||||
|
|
||||||
|
// let m: Array<string>;
|
||||||
|
// while ((m = blameMatcher.exec(data)) != null) {
|
||||||
|
// const authorName = m[4].trim();
|
||||||
|
// let author = authors.get(authorName);
|
||||||
|
// if (!author) {
|
||||||
|
// author = {
|
||||||
|
// name: authorName,
|
||||||
|
// lineCount: 0
|
||||||
|
// };
|
||||||
|
// authors.set(authorName, author);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const sha = m[1];
|
||||||
|
// let commit = commits.get(sha);
|
||||||
|
// if (!commit) {
|
||||||
|
// commit = {
|
||||||
|
// sha,
|
||||||
|
// fileName: fileName,
|
||||||
|
// author: authorName,
|
||||||
|
// date: new Date(m[5]),
|
||||||
|
// lines: []
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const file = m[2].trim();
|
||||||
|
// if (!fileName.toLowerCase().endsWith(file.toLowerCase())) {
|
||||||
|
// commit.originalFileName = file;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// commits.set(sha, commit);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const line: IGitCommitLine = {
|
||||||
|
// sha,
|
||||||
|
// line: parseInt(m[6], 10) - 1,
|
||||||
|
// originalLine: parseInt(m[3], 10) - 1
|
||||||
|
// //code: m[7]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// commit.lines.push(line);
|
||||||
|
// lines.push(line);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// commits.forEach(c => authors.get(c.author).lineCount += c.lines.length);
|
||||||
|
|
||||||
|
// const sortedAuthors: Map<string, IGitAuthor> = new Map();
|
||||||
|
// const values = Array.from(authors.values())
|
||||||
|
// .sort((a, b) => b.lineCount - a.lineCount)
|
||||||
|
// .forEach(a => sortedAuthors.set(a.name, a));
|
||||||
|
|
||||||
|
// const sortedCommits = new Map();
|
||||||
|
// Array.from(commits.values())
|
||||||
|
// .sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||||
|
// .forEach(c => sortedCommits.set(c.sha, c));
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// authors: sortedAuthors,
|
||||||
|
// commits: sortedCommits,
|
||||||
|
// lines: lines
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
|
||||||
|
// this._blames.set(fileName, blame);
|
||||||
|
// return blame;
|
||||||
|
// }
|
||||||
|
|
||||||
getBlameForFile(fileName: string) {
|
getBlameForFile(fileName: string) {
|
||||||
fileName = Git.normalizePath(fileName, this.repoPath);
|
fileName = Git.normalizePath(fileName, this.repoPath);
|
||||||
|
|
||||||
let blame = this._blames.get(fileName);
|
let blame = this._blames.get(fileName);
|
||||||
if (blame !== undefined) return blame;
|
if (blame !== undefined) return blame;
|
||||||
|
|
||||||
blame = Git.blame(fileName, this.repoPath)
|
blame = Git.blamePorcelain(fileName, this.repoPath)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const authors: Map<string, IGitAuthor> = new Map();
|
const authors: Map<string, IGitAuthor> = new Map();
|
||||||
const commits: Map<string, IGitCommit> = new Map();
|
const commits: Map<string, IGitCommit> = new Map();
|
||||||
const lines: Array<IGitCommitLine> = [];
|
const lines: Array<IGitCommitLine> = [];
|
||||||
|
|
||||||
let m: Array<string>;
|
let m: Array<string>;
|
||||||
while ((m = blameMatcher.exec(data)) != null) {
|
while ((m = blamePorcelainMatcher.exec(data)) != null) {
|
||||||
const authorName = m[4].trim();
|
const sha = m[1].substring(0, 8);
|
||||||
let author = authors.get(authorName);
|
|
||||||
if (!author) {
|
|
||||||
author = {
|
|
||||||
name: authorName,
|
|
||||||
lineCount: 0
|
|
||||||
};
|
|
||||||
authors.set(authorName, author);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sha = m[1];
|
|
||||||
let commit = commits.get(sha);
|
let commit = commits.get(sha);
|
||||||
if (!commit) {
|
if (!commit) {
|
||||||
|
const authorName = m[5].trim();
|
||||||
|
let author = authors.get(authorName);
|
||||||
|
if (!author) {
|
||||||
|
author = {
|
||||||
|
name: authorName,
|
||||||
|
lineCount: 0
|
||||||
|
};
|
||||||
|
authors.set(authorName, author);
|
||||||
|
}
|
||||||
|
|
||||||
commit = {
|
commit = {
|
||||||
sha,
|
sha,
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
author: m[4].trim(),
|
author: authorName,
|
||||||
date: new Date(m[5]),
|
date: moment(`${m[7]} ${m[8]}`, 'X Z').toDate(),
|
||||||
|
message: m[13],
|
||||||
lines: []
|
lines: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const originalFileName = m[16];
|
||||||
|
if (!fileName.toLowerCase().endsWith(originalFileName.toLowerCase())) {
|
||||||
|
commit.originalFileName = originalFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousSha = m[14];
|
||||||
|
if (previousSha) {
|
||||||
|
commit.previousSha = previousSha.substring(0, 8);
|
||||||
|
commit.previousFileName = m[15];
|
||||||
|
}
|
||||||
|
|
||||||
commits.set(sha, commit);
|
commits.set(sha, commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
const line: IGitCommitLine = {
|
const line: IGitCommitLine = {
|
||||||
sha,
|
sha,
|
||||||
line: parseInt(m[6], 10) - 1,
|
line: parseInt(m[3], 10) - 1,
|
||||||
originalLine: parseInt(m[3], 10) - 1
|
originalLine: parseInt(m[2], 10) - 1
|
||||||
//code: m[7]
|
//code: m[17]
|
||||||
}
|
|
||||||
|
|
||||||
const file = m[2].trim();
|
|
||||||
if (!fileName.toLowerCase().endsWith(file.toLowerCase())) {
|
|
||||||
line.originalFileName = file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commit.lines.push(line);
|
commit.lines.push(line);
|
||||||
@@ -116,8 +203,7 @@ export default class GitProvider extends Disposable {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._blames.set(fileName, blame);
|
this._blames.set(fileName, blame);
|
||||||
return blame;
|
return blame; }
|
||||||
}
|
|
||||||
|
|
||||||
getBlameForLine(fileName: string, line: number): Promise<IGitBlameLine> {
|
getBlameForLine(fileName: string, line: number): Promise<IGitBlameLine> {
|
||||||
return this.getBlameForFile(fileName).then(blame => {
|
return this.getBlameForFile(fileName).then(blame => {
|
||||||
@@ -192,8 +278,8 @@ export default class GitProvider extends Disposable {
|
|||||||
Array.from(blame.commits.values())
|
Array.from(blame.commits.values())
|
||||||
.forEach((c, i) => {
|
.forEach((c, i) => {
|
||||||
const uri = this.toBlameUri(c, i + 1, commitCount, range);
|
const uri = this.toBlameUri(c, i + 1, commitCount, range);
|
||||||
c.lines.forEach(l => locations.push(new Location(l.originalFileName
|
c.lines.forEach(l => locations.push(new Location(c.originalFileName
|
||||||
? this.toBlameUri(c, i + 1, commitCount, range, l.originalFileName)
|
? this.toBlameUri(c, i + 1, commitCount, range, c.originalFileName)
|
||||||
: uri,
|
: uri,
|
||||||
new Position(l.originalLine, 0))));
|
new Position(l.originalLine, 0))));
|
||||||
});
|
});
|
||||||
@@ -202,6 +288,24 @@ export default class GitProvider extends Disposable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getHistoryLocations(fileName: string, range: Range) {
|
||||||
|
// return this.getBlameForRange(fileName, range).then(blame => {
|
||||||
|
// const commitCount = blame.commits.size;
|
||||||
|
|
||||||
|
// const locations: Array<Location> = [];
|
||||||
|
// Array.from(blame.commits.values())
|
||||||
|
// .forEach((c, i) => {
|
||||||
|
// const uri = this.toBlameUri(c, i + 1, commitCount, range);
|
||||||
|
// c.lines.forEach(l => locations.push(new Location(c.originalFileName
|
||||||
|
// ? this.toBlameUri(c, i + 1, commitCount, range, c.originalFileName)
|
||||||
|
// : uri,
|
||||||
|
// new Position(l.originalLine, 0))));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return locations;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
getCommitMessage(sha: string) {
|
getCommitMessage(sha: string) {
|
||||||
return Git.getCommitMessage(sha, this.repoPath);
|
return Git.getCommitMessage(sha, this.repoPath);
|
||||||
}
|
}
|
||||||
@@ -303,15 +407,17 @@ export interface IGitCommit {
|
|||||||
fileName: string;
|
fileName: string;
|
||||||
author: string;
|
author: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
|
message: string;
|
||||||
lines: IGitCommitLine[];
|
lines: IGitCommitLine[];
|
||||||
message?: string;
|
originalFileName?: string;
|
||||||
|
previousSha?: string;
|
||||||
|
previousFileName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGitCommitLine {
|
export interface IGitCommitLine {
|
||||||
sha: string;
|
sha: string;
|
||||||
line: number;
|
line: number;
|
||||||
originalLine: number;
|
originalLine: number;
|
||||||
originalFileName?: string;
|
|
||||||
code?: string;
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user