From ebb1085562c40ffe71286f4ea634fa3e0d44a8af Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Sun, 4 Sep 2016 03:43:35 -0400 Subject: [PATCH] Switches to porcelain blame format Provides more data (commit message, previous commit, etc) --- src/git.ts | 17 +++- src/gitBlameController.ts | 16 ++-- src/gitProvider.ts | 164 +++++++++++++++++++++++++++++++------- 3 files changed, 159 insertions(+), 38 deletions(-) diff --git a/src/git.ts b/src/git.ts index 29c6de9..1b4b959 100644 --- a/src/git.ts +++ b/src/git.ts @@ -23,7 +23,7 @@ export default class Git { if (sha) { 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); @@ -32,6 +32,20 @@ export default class Git { // .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) { return new Promise((resolve, reject) => { Git.getVersionedFileText(fileName, repoPath, sha).then(data => { @@ -79,6 +93,7 @@ export default class Git { static getCommitMessages(fileName: string, repoPath: string) { fileName = Git.normalizePath(fileName, repoPath); + // git log --format="%h (%aN %x09 %ai) %s" -- console.log('git', 'log', '--oneline', '--', fileName); return gitCommand(repoPath, 'log', '--oneline', '--', fileName); // .then(s => { console.log(s); return s; }) diff --git a/src/gitBlameController.ts b/src/gitBlameController.ts index 03f7135..519d3ae 100644 --- a/src/gitBlameController.ts +++ b/src/gitBlameController.ts @@ -76,14 +76,14 @@ export default class GitBlameController extends Disposable { class GitBlameEditorController extends Disposable { private _subscription: Disposable; private _blame: Promise; - private _commits: Promise>; + //private _commits: Promise>; constructor(private git: GitProvider, private blameDecoration: TextEditorDecorationType, private highlightDecoration: TextEditorDecorationType, public editor: TextEditor, public sha: string) { super(() => this.dispose()); const fileName = this.editor.document.uri.path; 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 => { const activeLine = e.selections[0].active.line; @@ -106,22 +106,22 @@ class GitBlameEditorController extends Disposable { return this._blame.then(blame => { if (!blame.lines.length) return; - return this._commits.then(msgs => { - const commits = Array.from(blame.commits.values()); - commits.forEach(c => c.message = msgs.get(c.sha.substring(0, c.sha.length - 1))); + // return this._commits.then(msgs => { + // const commits = Array.from(blame.commits.values()); + // commits.forEach(c => c.message = msgs.get(c.sha.substring(0, c.sha.length - 1))); const blameDecorationOptions: DecorationOptions[] = blame.lines.map(l => { const c = blame.commits.get(l.sha); return { 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')}`, - renderOptions: { before: { contentText: `${l.sha}`, } } + renderOptions: { before: { contentText: `${l.sha.substring(0, 8)}`, } } }; }); this.editor.setDecorations(this.blameDecoration, blameDecorationOptions); - return this.applyHighlight(sha || commits[0].sha); - }); + return this.applyHighlight(sha || blame.commits.values().next().value.sha); + // }); }); } diff --git a/src/gitProvider.ts b/src/gitProvider.ts index 6c570f7..03b832c 100644 --- a/src/gitProvider.ts +++ b/src/gitProvider.ts @@ -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 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 { public repoPath: string; @@ -43,53 +45,138 @@ export default class GitProvider extends Disposable { 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 = new Map(); + // const commits: Map = new Map(); + // const lines: Array = []; + + // let m: Array; + // 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 = 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) { fileName = Git.normalizePath(fileName, this.repoPath); let blame = this._blames.get(fileName); if (blame !== undefined) return blame; - blame = Git.blame(fileName, this.repoPath) + blame = Git.blamePorcelain(fileName, this.repoPath) .then(data => { const authors: Map = new Map(); const commits: Map = new Map(); const lines: Array = []; let m: Array; - 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]; + while ((m = blamePorcelainMatcher.exec(data)) != null) { + const sha = m[1].substring(0, 8); let commit = commits.get(sha); if (!commit) { + const authorName = m[5].trim(); + let author = authors.get(authorName); + if (!author) { + author = { + name: authorName, + lineCount: 0 + }; + authors.set(authorName, author); + } + commit = { sha, fileName: fileName, - author: m[4].trim(), - date: new Date(m[5]), + author: authorName, + date: moment(`${m[7]} ${m[8]}`, 'X Z').toDate(), + message: m[13], 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); } const line: IGitCommitLine = { sha, - line: parseInt(m[6], 10) - 1, - originalLine: parseInt(m[3], 10) - 1 - //code: m[7] - } - - const file = m[2].trim(); - if (!fileName.toLowerCase().endsWith(file.toLowerCase())) { - line.originalFileName = file; + line: parseInt(m[3], 10) - 1, + originalLine: parseInt(m[2], 10) - 1 + //code: m[17] } commit.lines.push(line); @@ -116,8 +203,7 @@ export default class GitProvider extends Disposable { }); this._blames.set(fileName, blame); - return blame; - } + return blame; } getBlameForLine(fileName: string, line: number): Promise { return this.getBlameForFile(fileName).then(blame => { @@ -192,8 +278,8 @@ export default class GitProvider extends Disposable { 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(l.originalFileName - ? this.toBlameUri(c, i + 1, commitCount, range, l.originalFileName) + 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)))); }); @@ -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 = []; + // 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) { return Git.getCommitMessage(sha, this.repoPath); } @@ -303,15 +407,17 @@ export interface IGitCommit { fileName: string; author: string; date: Date; + message: string; lines: IGitCommitLine[]; - message?: string; + originalFileName?: string; + previousSha?: string; + previousFileName?: string; } export interface IGitCommitLine { sha: string; line: number; originalLine: number; - originalFileName?: string; code?: string; }