diff --git a/src/commands.ts b/src/commands.ts index 0b781c6..1cb77d8 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -62,9 +62,7 @@ export class DiffWithPreviousCommand extends EditorCommand { return window.showInformationMessage(`Commit ${sha} has no previous commit`); } - // TODO: Moving doesn't always seem to work -- or more accurately it seems like it moves down that number of lines from the current line - // which for a diff could be the first difference - return Promise.all([this.git.getVersionedFile(uri.fsPath, sha), this.git.getVersionedFile(uri.fsPath, compareWithSha)]) + return Promise.all([this.git.getVersionedFile(shaUri.fsPath, sha), this.git.getVersionedFile(compareWithUri.fsPath, compareWithSha)]) .catch(ex => console.error('[GitLens.DiffWithPreviousCommand]', 'getVersionedFile', ex)) .then(values => commands.executeCommand(BuiltInCommands.Diff, Uri.file(values[1]), Uri.file(values[0]), `${path.basename(compareWithUri.fsPath)} (${compareWithSha}) ↔ ${path.basename(shaUri.fsPath)} (${sha})`) .then(() => commands.executeCommand(BuiltInCommands.RevealLine, {lineNumber: line, at: 'center'}))); @@ -91,8 +89,6 @@ export class DiffWithWorkingCommand extends EditorCommand { }); }; - // TODO: Moving doesn't always seem to work -- or more accurately it seems like it moves down that number of lines from the current line - // which for a diff could be the first difference return this.git.getVersionedFile(shaUri.fsPath, sha) .catch(ex => console.error('[GitLens.DiffWithWorkingCommand]', 'getVersionedFile', ex)) .then(compare => commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), uri, `${path.basename(shaUri.fsPath)} (${sha}) ↔ ${path.basename(uri.fsPath)} (index)`) diff --git a/src/git/enrichers/blameParserEnricher.ts b/src/git/enrichers/blameParserEnricher.ts new file mode 100644 index 0000000..f823376 --- /dev/null +++ b/src/git/enrichers/blameParserEnricher.ts @@ -0,0 +1,202 @@ +'use strict' +import {GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitCommit, IGitCommitLine, IGitEnricher} from './../git'; +import * as moment from 'moment'; +import * as path from 'path'; + +interface IBlameEntry { + sha: string; + line: number; + originalLine: number; + lineCount: number; + + author?: string; + authorEmail?: string; + authorDate?: string; + authorTimeZone?: string; + + committer?: string; + committerEmail?: string; + committerDate?: string; + committerTimeZone?: string; + + previousSha?: string; + previousFileName?: string; + + fileName?: string; + + summary?: string; +} + +export class GitBlameParserEnricher implements IGitEnricher { + constructor(public format: GitBlameFormat) { + if (format !== GitBlameFormat.incremental) { + throw new Error(`Invalid blame format=${format}`); + } + } + + private _parseEntries(data: string): IBlameEntry[] { + if (!data) return null; + + const lines = data.split('\n'); + if (!lines.length) return null; + + const entries: IBlameEntry[] = []; + + let entry: IBlameEntry; + let position = -1; + while (++position < lines.length) { + let lineParts = lines[position].split(" "); + if (lineParts.length < 2) { + continue; + } + + if (!entry) { + entry = { + sha: lineParts[0].substring(0, 8), + originalLine: parseInt(lineParts[1], 10) - 1, + line: parseInt(lineParts[2], 10) - 1, + lineCount: parseInt(lineParts[3], 10) + }; + + continue; + } + + switch (lineParts[0]) { + case "author": + entry.author = lineParts.slice(1).join(" ").trim(); + break; + + // case "author-mail": + // entry.authorEmail = lineParts[1].trim(); + // break; + + case "author-time": + entry.authorDate = lineParts[1]; + break; + + case "author-tz": + entry.authorTimeZone = lineParts[1]; + break; + + // case "committer": + // entry.committer = lineParts.slice(1).join(" ").trim(); + // break; + + // case "committer-mail": + // entry.committerEmail = lineParts[1].trim(); + // break; + + // case "committer-time": + // entry.committerDate = lineParts[1]; + // break; + + // case "committer-tz": + // entry.committerTimeZone = lineParts[1]; + // break; + + case "summary": + entry.summary = lineParts.slice(1).join(" ").trim(); + break; + + case "previous": + entry.previousSha = lineParts[1].substring(0, 8); + entry.previousFileName = lineParts.slice(2).join(" "); + break; + + case "filename": + entry.fileName = lineParts.slice(1).join(" "); + + entries.push(entry); + entry = null; + break; + + default: + break; + } + } + + return entries; + } + + enrich(data: string, fileName: string): IGitBlame { + const entries = this._parseEntries(data); + if (!entries) return null; + + const authors: Map = new Map(); + const commits: Map = new Map(); + const lines: Array = []; + + let repoPath: string; + let relativeFileName: string; + + for (let i = 0, len = entries.length; i < len; i++) { + const entry = entries[i]; + + if (i === 0) { + // Try to get the repoPath from the most recent commit + repoPath = fileName.replace(`/${entry.fileName}`, ''); + relativeFileName = path.relative(repoPath, fileName).replace(/\\/g, '/'); + } + + let commit = commits.get(entry.sha); + if (!commit) { + let author = authors.get(entry.author); + if (!author) { + author = { + name: entry.author, + lineCount: 0 + }; + authors.set(entry.author, author); + } + + commit = new GitCommit(repoPath, entry.sha, relativeFileName, entry.author, moment(`${entry.authorDate} ${entry.authorTimeZone}`, 'X Z').toDate(), entry.summary); + + if (relativeFileName !== entry.fileName) { + commit.originalFileName = entry.fileName; + } + + if (entry.previousSha) { + commit.previousSha = entry.previousSha; + commit.previousFileName = entry.previousFileName; + } + + commits.set(entry.sha, commit); + } + + for (let j = 0, len = entry.lineCount; j < len; j++) { + const line: IGitCommitLine = { + sha: entry.sha, + line: entry.line + j, + originalLine: entry.originalLine + j + } + + if (commit.previousSha) { + line.previousSha = commit.previousSha; + } + + commit.lines.push(line); + lines[line.line] = 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: Map = new Map(); + // Array.from(commits.values()) + // .sort((a, b) => b.date.getTime() - a.date.getTime()) + // .forEach(c => sortedCommits.set(c.sha, c)); + + return { + repoPath: repoPath, + authors: sortedAuthors, + // commits: sortedCommits, + commits: commits, + lines: lines + }; + } +} \ No newline at end of file diff --git a/src/gitEnrichment.ts b/src/git/enrichers/blameRegExpEnricher.ts similarity index 63% rename from src/gitEnrichment.ts rename to src/git/enrichers/blameRegExpEnricher.ts index a1fa647..8543a92 100644 --- a/src/gitEnrichment.ts +++ b/src/git/enrichers/blameRegExpEnricher.ts @@ -1,17 +1,11 @@ 'use strict' -import {Uri} from 'vscode'; -import {GitBlameFormat} from './git' +import {GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitCommit, IGitCommitLine, IGitEnricher} from './../git'; import * as moment from 'moment'; -import * as path from 'path'; 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; -interface IGitEnricher { - enrich(data: string, ...args): T; -} - -export class GitBlameEnricher implements IGitEnricher { +export class GitBlameRegExpEnricher implements IGitEnricher { private _matcher: RegExp; constructor(public format: GitBlameFormat, private repoPath: string) { @@ -95,77 +89,4 @@ export class GitBlameEnricher implements IGitEnricher { lines: lines }; } -} - -export interface IGitBlame { - authors: Map; - commits: Map; - lines: IGitCommitLine[]; -} - -export interface IGitBlameLine { - author: IGitAuthor; - commit: IGitCommit; - line: IGitCommitLine; -} - -export interface IGitBlameLines extends IGitBlame { - allLines: IGitCommitLine[]; -} - -export interface IGitBlameCommitLines { - author: IGitAuthor; - commit: IGitCommit; - lines: IGitCommitLine[]; -} - -export interface IGitAuthor { - name: string; - lineCount: number; -} - -export interface IGitCommit { - sha: string; - fileName: string; - author: string; - date: Date; - message: string; - lines: IGitCommitLine[]; - originalFileName?: string; - previousSha?: string; - previousFileName?: string; - - previousUri: Uri; - uri: Uri; -} - -export class GitCommit implements IGitCommit { - lines: IGitCommitLine[]; - originalFileName?: string; - previousSha?: string; - previousFileName?: string; - - constructor(private repoPath: string, public sha: string, public fileName: string, public author: string, public date: Date, public message: string, - lines?: IGitCommitLine[], originalFileName?: string, previousSha?: string, previousFileName?: string) { - this.lines = lines || []; - this.originalFileName = originalFileName; - this.previousSha = previousSha; - this.previousFileName = previousFileName; - } - - get previousUri(): Uri { - return this.previousFileName ? Uri.file(path.join(this.repoPath, this.previousFileName)) : this.uri; - } - - get uri(): Uri { - return Uri.file(path.join(this.repoPath, this.originalFileName || this.fileName)); - } -} - -export interface IGitCommitLine { - sha: string; - previousSha?: string; - line: number; - originalLine: number; - code?: string; } \ No newline at end of file diff --git a/src/git.ts b/src/git/git.ts similarity index 57% rename from src/git.ts rename to src/git/git.ts index 5d1bd22..0429351 100644 --- a/src/git.ts +++ b/src/git/git.ts @@ -5,6 +5,8 @@ import * as tmp from 'tmp'; import {spawnPromise} from 'spawn-rx'; export * from './gitEnrichment'; +//export * from './enrichers/blameRegExpEnricher'; +export * from './enrichers/blameParserEnricher'; function gitCommand(cwd: string, ...args) { return spawnPromise('git', args, { cwd: cwd }) @@ -32,30 +34,33 @@ export const GitBlameFormat = { export default class Git { static normalizePath(fileName: string, repoPath?: string) { - fileName = fileName.replace(/\\/g, '/'); - repoPath = repoPath.replace(/\\/g, '/'); - if (path.isAbsolute(fileName) && fileName.startsWith(repoPath)) { - fileName = path.relative(repoPath, fileName).replace(/\\/g, '/'); - } - return fileName; + return fileName.replace(/\\/g, '/'); + } + + static splitPath(fileName: string) { + // if (!path.isAbsolute(fileName)) { + // console.error('[GitLens]', `Git.splitPath(${fileName}) is not an absolute path!`); + // debugger; + // } + return [path.basename(fileName).replace(/\\/g, '/'), path.dirname(fileName).replace(/\\/g, '/')]; } static repoPath(cwd: string) { return gitCommand(cwd, 'rev-parse', '--show-toplevel').then(data => data.replace(/\r?\n|\r/g, '').replace(/\\/g, '/')); } - static blame(format: GitBlameFormat, fileName: string, repoPath: string, sha?: string) { - fileName = Git.normalizePath(fileName, repoPath); + static blame(format: GitBlameFormat, fileName: string, sha?: string) { + const [file, root] = Git.splitPath(Git.normalizePath(fileName)); if (sha) { - return gitCommand(repoPath, 'blame', format, '--root', `${sha}^`, '--', fileName); + return gitCommand(root, 'blame', format, '--root', `${sha}^`, '--', file); } - return gitCommand(repoPath, 'blame', format, '--root', '--', fileName); + return gitCommand(root, 'blame', format, '--root', '--', file); } - static getVersionedFile(fileName: string, repoPath: string, sha: string) { + static getVersionedFile(fileName: string, sha: string) { return new Promise((resolve, reject) => { - Git.getVersionedFileText(fileName, repoPath, sha).then(data => { + Git.getVersionedFileText(fileName, sha).then(data => { const ext = path.extname(fileName); tmp.file({ prefix: `${path.basename(fileName, ext)}-${sha}_`, postfix: ext }, (err, destination, fd, cleanupCallback) => { if (err) { @@ -76,27 +81,10 @@ export default class Git { }); } - static getVersionedFileText(fileName: string, repoPath: string, sha: string) { - fileName = Git.normalizePath(fileName, repoPath); + static getVersionedFileText(fileName: string, sha: string) { + const [file, root] = Git.splitPath(Git.normalizePath(fileName)); sha = sha.replace('^', ''); - return gitCommand(repoPath, 'show', `${sha}:./${fileName}`); + return gitCommand(root, 'show', `${sha}:./${file}`); } - - // static getCommitMessage(sha: string, repoPath: string) { - // sha = sha.replace('^', ''); - - // return gitCommand(repoPath, 'show', '-s', '--format=%B', sha); - // // .then(s => { console.log(s); return s; }) - // // .catch(ex => console.error(ex)); - // } - - // static getCommitMessages(fileName: string, repoPath: string) { - // fileName = Git.normalizePath(fileName, repoPath); - - // // git log --format="%h (%aN %x09 %ai) %s" -- - // return gitCommand(repoPath, 'log', '--oneline', '--', fileName); - // // .then(s => { console.log(s); return s; }) - // // .catch(ex => console.error(ex)); - // } } \ No newline at end of file diff --git a/src/git/gitEnrichment.ts b/src/git/gitEnrichment.ts new file mode 100644 index 0000000..24171f1 --- /dev/null +++ b/src/git/gitEnrichment.ts @@ -0,0 +1,82 @@ +'use strict' +import {Uri} from 'vscode'; +import * as path from 'path'; + +export interface IGitEnricher { + enrich(data: string, ...args): T; +} + +export interface IGitBlame { + repoPath: string; + authors: Map; + commits: Map; + lines: IGitCommitLine[]; +} + +export interface IGitBlameLine { + author: IGitAuthor; + commit: IGitCommit; + line: IGitCommitLine; +} + +export interface IGitBlameLines extends IGitBlame { + allLines: IGitCommitLine[]; +} + +export interface IGitBlameCommitLines { + author: IGitAuthor; + commit: IGitCommit; + lines: IGitCommitLine[]; +} + +export interface IGitAuthor { + name: string; + lineCount: number; +} + +export interface IGitCommit { + repoPath: string; + sha: string; + fileName: string; + author: string; + date: Date; + message: string; + lines: IGitCommitLine[]; + originalFileName?: string; + previousSha?: string; + previousFileName?: string; + + previousUri: Uri; + uri: Uri; +} + +export class GitCommit implements IGitCommit { + lines: IGitCommitLine[]; + originalFileName?: string; + previousSha?: string; + previousFileName?: string; + + constructor(public repoPath: string, public sha: string, public fileName: string, public author: string, public date: Date, public message: string, + lines?: IGitCommitLine[], originalFileName?: string, previousSha?: string, previousFileName?: string) { + this.lines = lines || []; + this.originalFileName = originalFileName; + this.previousSha = previousSha; + this.previousFileName = previousFileName; + } + + get previousUri(): Uri { + return this.previousFileName ? Uri.file(path.join(this.repoPath, this.previousFileName)) : this.uri; + } + + get uri(): Uri { + return Uri.file(path.join(this.repoPath, this.originalFileName || this.fileName)); + } +} + +export interface IGitCommitLine { + sha: string; + previousSha?: string; + line: number; + originalLine: number; + code?: string; +} \ No newline at end of file diff --git a/src/gitBlameCodeLensProvider.ts b/src/gitBlameCodeLensProvider.ts index 2369dc6..81eb77c 100644 --- a/src/gitBlameCodeLensProvider.ts +++ b/src/gitBlameCodeLensProvider.ts @@ -6,13 +6,13 @@ import * as moment from 'moment'; import * as path from 'path'; export class GitDiffWithWorkingTreeCodeLens extends CodeLens { - constructor(private git: GitProvider, public fileName: string, public sha: string, range: Range) { + constructor(private git: GitProvider, public fileName: string, public commit: IGitCommit, range: Range) { super(range); } } export class GitDiffWithPreviousCodeLens extends CodeLens { - constructor(private git: GitProvider, public fileName: string, public sha: string, public compareWithSha: string, range: Range) { + constructor(private git: GitProvider, public fileName: string, public commit: IGitCommit, range: Range) { super(range); } } @@ -31,22 +31,17 @@ export default class GitBlameCodeLensProvider implements CodeLensProvider { const lenses: CodeLens[] = []; if (!blame) return lenses; - const commits = Array.from(blame.commits.values()); - let index = commits.findIndex(c => c.sha === sha) + 1; - - let previousCommit: IGitCommit; - if (index < commits.length) { - previousCommit = commits[index]; - } + const commit = blame.commits.get(sha); + const absoluteFileName = path.join(commit.repoPath, fileName); // Add codelens to each "group" of blame lines const lines = blame.lines.filter(l => l.sha === sha && l.originalLine >= data.range.start.line && l.originalLine <= data.range.end.line); let lastLine = lines[0].originalLine; lines.forEach(l => { if (l.originalLine !== lastLine + 1) { - lenses.push(new GitDiffWithWorkingTreeCodeLens(this.git, fileName, sha, new Range(l.originalLine, 0, l.originalLine, 1))); - if (previousCommit) { - lenses.push(new GitDiffWithPreviousCodeLens(this.git, fileName, sha, previousCommit.sha, new Range(l.originalLine, 1, l.originalLine, 2))); + lenses.push(new GitDiffWithWorkingTreeCodeLens(this.git, absoluteFileName, commit, new Range(l.originalLine, 0, l.originalLine, 1))); + if (commit.previousSha) { + lenses.push(new GitDiffWithPreviousCodeLens(this.git, absoluteFileName, commit, new Range(l.originalLine, 1, l.originalLine, 2))); } } lastLine = l.originalLine; @@ -54,9 +49,9 @@ export default class GitBlameCodeLensProvider implements CodeLensProvider { // Check if we have a lens for the whole document -- if not add one if (!lenses.find(l => l.range.start.line === 0 && l.range.end.line === 0)) { - lenses.push(new GitDiffWithWorkingTreeCodeLens(this.git, fileName, sha, new Range(0, 0, 0, 1))); - if (previousCommit) { - lenses.push(new GitDiffWithPreviousCodeLens(this.git, fileName, sha, previousCommit.sha, new Range(0, 1, 0, 2))); + lenses.push(new GitDiffWithWorkingTreeCodeLens(this.git, absoluteFileName, commit, new Range(0, 0, 0, 1))); + if (commit.previousSha) { + lenses.push(new GitDiffWithPreviousCodeLens(this.git, absoluteFileName, commit, new Range(0, 1, 0, 2))); } } @@ -73,16 +68,16 @@ export default class GitBlameCodeLensProvider implements CodeLensProvider { lens.command = { title: `Compare with Working Tree`, command: Commands.DiffWithWorking, - arguments: [Uri.file(path.join(this.git.repoPath, lens.fileName)), lens.sha] + arguments: [Uri.file(lens.fileName), lens.commit.sha, lens.commit.uri, lens.range.start.line] }; return Promise.resolve(lens); } _resolveGitDiffWithPreviousCodeLens(lens: GitDiffWithPreviousCodeLens, token: CancellationToken): Thenable { lens.command = { - title: `Compare with Previous (${lens.compareWithSha})`, + title: `Compare with Previous (${lens.commit.previousSha})`, command: Commands.DiffWithPrevious, - arguments: [Uri.file(path.join(this.git.repoPath, lens.fileName)), lens.sha, lens.compareWithSha] + arguments: [Uri.file(lens.fileName), lens.commit.sha, lens.commit.uri, lens.commit.previousSha, lens.commit.previousUri, lens.range.start.line] }; return Promise.resolve(lens); } diff --git a/src/gitBlameController.ts b/src/gitBlameController.ts index 6478a4b..cd5c92a 100644 --- a/src/gitBlameController.ts +++ b/src/gitBlameController.ts @@ -1,5 +1,5 @@ 'use strict' -import {commands, DecorationInstanceRenderOptions, DecorationOptions, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Disposable, ExtensionContext, languages, OverviewRulerLane, Position, Range, TextEditor, TextEditorDecorationType, Uri, window, workspace} from 'vscode'; +import {commands, DecorationInstanceRenderOptions, DecorationOptions, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Disposable, ExtensionContext, languages, OverviewRulerLane, Position, Range, TextDocument, TextEditor, TextEditorDecorationType, Uri, window, workspace} from 'vscode'; import {BuiltInCommands, Commands, DocumentSchemes} from './constants'; import {BlameAnnotationStyle, IBlameConfig} from './configuration'; import GitProvider, {IGitBlame, IGitCommit} from './gitProvider'; @@ -96,13 +96,16 @@ class GitBlameEditorController extends Disposable { private _config: IBlameConfig; private _diagnostics: DiagnosticCollection; private _disposable: Disposable; + private _document: TextDocument; private _toggleWhitespace: boolean; constructor(private context: ExtensionContext, private git: GitProvider, public editor: TextEditor) { super(() => this.dispose()); - this.uri = this.editor.document.uri; + this._document = this.editor.document; + this.uri = this._document.uri; const fileName = this.uri.fsPath; + this._blame = this.git.getBlameForFile(fileName); this._config = workspace.getConfiguration('gitlens').get('blame'); @@ -205,24 +208,33 @@ class GitBlameEditorController extends Disposable { } let gutter = ''; - if (lastSha === l.sha) { - count++; - if (count === 1) { - gutter = `\\00a6\\00a0 ${this._getAuthor(commit, 17, true)}`; - } else if (count === 2) { - gutter = `\\00a6\\00a0 ${this._getDate(commit, true)}`; - } else { - gutter = '\\00a6\\00a0'; + if (lastSha !== l.sha) { + count = -1; + } + + const isEmptyOrWhitespace = this._document.lineAt(l.line).isEmptyOrWhitespace; + if (!isEmptyOrWhitespace) { + switch (++count) { + case 0: + gutter = commit.sha.substring(0, 8); + break; + case 1: + gutter = `\\00a6\\00a0 ${this._getAuthor(commit, 17, true)}`; + break; + case 2: + gutter = `\\00a6\\00a0 ${this._getDate(commit, true)}`; + break; + default: + gutter = '\\00a6\\00a0'; + break; } - } else { - count = 0; - gutter = commit.sha.substring(0, 8); } + lastSha = l.sha; return { range: this.editor.document.validateRange(new Range(l.line, 0, l.line, 0)), - hoverMessage: [commit.message, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY hh:MM a')}`], + hoverMessage: [`_${l.sha}_: ${commit.message}`, `${commit.author}, ${moment(commit.date).format('MMMM Do, YYYY hh:MM a')}`], renderOptions: { before: { color: color, contentText: gutter, width: '11em' } } }; }); @@ -258,18 +270,6 @@ class GitBlameEditorController extends Disposable { if (l.sha.startsWith('00000000')) { color = 'rgba(0, 188, 242, 0.6)'; hoverMessage = ''; - // if (l.previousSha) { - // let previousCommit = blame.commits.get(l.previousSha); - // if (previousCommit) {//} && previousCommit.lines.find(_ => _.line === l.originalLine)) { - // commit = previousCommit; - // color = 'rgba(0, 188, 242, 0.6)'; - // } - // else { - // color = 'rgba(127, 186, 0, 0.6)'; - // } - // } else { - // color = 'rgba(127, 186, 0, 0.6)'; - // } } const gutter = this._getGutter(commit); diff --git a/src/gitProvider.ts b/src/gitProvider.ts index 95884a6..9d7fc49 100644 --- a/src/gitProvider.ts +++ b/src/gitProvider.ts @@ -3,7 +3,7 @@ import {Disposable, ExtensionContext, languages, Location, Position, Range, Uri, import {DocumentSchemes, WorkspaceState} from './constants'; import {IConfig} from './configuration'; import GitCodeLensProvider from './gitCodeLensProvider'; -import Git, {GitBlameEnricher, GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitBlameCommitLines, IGitBlameLine, IGitBlameLines, IGitCommit} from './git'; +import Git, {GitBlameParserEnricher, GitBlameFormat, GitCommit, IGitAuthor, IGitBlame, IGitBlameCommitLines, IGitBlameLine, IGitBlameLines, IGitCommit} from './git/git'; import * as fs from 'fs' import * as ignore from 'ignore'; import * as _ from 'lodash'; @@ -11,7 +11,7 @@ import * as moment from 'moment'; import * as path from 'path'; export { Git }; -export * from './git'; +export * from './git/git'; interface IBlameCacheEntry { //date: Date; @@ -26,8 +26,6 @@ enum RemoveCacheReason { } export default class GitProvider extends Disposable { - public repoPath: string; - private _blameCache: Map; private _blameCacheDisposable: Disposable; @@ -37,17 +35,17 @@ export default class GitProvider extends Disposable { private _gitignore: Promise; static BlameEmptyPromise = Promise.resolve(null); - static BlameFormat = GitBlameFormat.porcelain; + static BlameFormat = GitBlameFormat.incremental; constructor(private context: ExtensionContext) { super(() => this.dispose()); - this.repoPath = context.workspaceState.get(WorkspaceState.RepoPath) as string; + const repoPath = context.workspaceState.get(WorkspaceState.RepoPath) as string; this._onConfigure(); this._gitignore = new Promise((resolve, reject) => { - const gitignorePath = path.join(this.repoPath, '.gitignore'); + const gitignorePath = path.join(repoPath, '.gitignore'); fs.exists(gitignorePath, e => { if (e) { fs.readFile(gitignorePath, 'utf8', (err, data) => { @@ -126,7 +124,7 @@ export default class GitProvider extends Disposable { private _removeCachedBlame(fileName: string, reason: RemoveCacheReason) { if (!this.UseCaching) return; - fileName = Git.normalizePath(fileName, this.repoPath); + fileName = Git.normalizePath(fileName); const cacheKey = this._getBlameCacheKey(fileName); if (reason === RemoveCacheReason.DocumentClosed) { @@ -150,7 +148,7 @@ export default class GitProvider extends Disposable { } getBlameForFile(fileName: string) { - fileName = Git.normalizePath(fileName, this.repoPath); + fileName = Git.normalizePath(fileName); const cacheKey = this._getBlameCacheKey(fileName); if (this.UseCaching) { @@ -164,15 +162,14 @@ export default class GitProvider extends Disposable { console.log('[GitLens]', `Skipping blame; ${fileName} is gitignored`); blame = GitProvider.BlameEmptyPromise; } else { - const enricher = new GitBlameEnricher(GitProvider.BlameFormat, this.repoPath); - blame = Git.blame(GitProvider.BlameFormat, fileName, this.repoPath) - .then(data => enricher.enrich(data, fileName)); - - if (this.UseCaching) { - // Trap and cache expected blame errors - blame.catch(ex => { - const msg = ex && ex.toString(); - if (msg && (msg.includes('is outside repository') || msg.includes('no such path'))) { + const enricher = new GitBlameParserEnricher(GitProvider.BlameFormat); + + blame = Git.blame(GitProvider.BlameFormat, fileName) + .then(data => enricher.enrich(data, fileName)) + .catch(ex => { + // Trap and cache expected blame errors + if (this.UseCaching) { + const msg = ex && ex.toString(); console.log('[GitLens]', `Replace blame cache: cacheKey=${cacheKey}`); this._blameCache.set(cacheKey, { //date: new Date(), @@ -181,16 +178,7 @@ export default class GitProvider extends Disposable { }); return GitProvider.BlameEmptyPromise; } - - const brokenBlame = this._blameCache.get(cacheKey); - if (brokenBlame) { - brokenBlame.errorMessage = msg; - this._blameCache.set(cacheKey, brokenBlame); - } - - throw ex; }); - } } if (this.UseCaching) { @@ -238,7 +226,7 @@ export default class GitProvider extends Disposable { blame.commits.forEach(c => { if (!shas.has(c.sha)) return; - const commit: IGitCommit = new GitCommit(this.repoPath, c.sha, c.fileName, c.author, c.date, c.message, + const commit: IGitCommit = new GitCommit(c.repoPath, c.sha, c.fileName, c.author, c.date, c.message, c.lines.filter(l => l.line >= range.start.line && l.line <= range.end.line), c.originalFileName, c.previousSha, c.previousFileName); commits.set(c.sha, commit); @@ -274,7 +262,7 @@ export default class GitProvider extends Disposable { const lines = blame.lines.slice(range.start.line, range.end.line + 1).filter(l => l.sha === sha); let commit = blame.commits.get(sha); - commit = new GitCommit(this.repoPath, commit.sha, commit.fileName, commit.author, commit.date, commit.message, + commit = new GitCommit(commit.repoPath, commit.sha, commit.fileName, commit.author, commit.date, commit.message, lines, commit.originalFileName, commit.previousSha, commit.previousFileName); return { author: Object.assign({}, blame.authors.get(commit.author), { lineCount: commit.lines.length }), @@ -304,50 +292,12 @@ export default class GitProvider extends Disposable { }); } - // getHistoryLocations(fileName: string, range: Range) { - // return this.getBlameForRange(fileName, range).then(blame => { - // if (!blame) return null; - - // 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; - // }); - // } - - // const commitMessageMatcher = /^([\^0-9a-fA-F]{7})\s(.*)$/gm; - - // getCommitMessage(sha: string) { - // return Git.getCommitMessage(sha, this.repoPath); - // } - - // getCommitMessages(fileName: string) { - // return Git.getCommitMessages(fileName, this.repoPath).then(data => { - // const commits: Map = new Map(); - // let m: Array; - // while ((m = commitMessageMatcher.exec(data)) != null) { - // commits.set(m[1], m[2]); - // } - - // return commits; - // }); - // } - getVersionedFile(fileName: string, sha: string) { - return Git.getVersionedFile(fileName, this.repoPath, sha); + return Git.getVersionedFile(fileName, sha); } getVersionedFileText(fileName: string, sha: string) { - return Git.getVersionedFileText(fileName, this.repoPath, sha); + return Git.getVersionedFileText(fileName, sha); } static fromBlameUri(uri: Uri): IGitBlameUriData { @@ -385,10 +335,10 @@ export default class GitProvider extends Disposable { } private static _toGitUriData(commit: IGitCommit, index: number, originalFileName?: string): T { - const fileName = originalFileName || commit.fileName; - const data = { fileName: commit.fileName, sha: commit.sha, index: index } as T; + const fileName = Git.normalizePath(path.join(commit.repoPath, commit.fileName)); + const data = { fileName: fileName, sha: commit.sha, index: index } as T; if (originalFileName) { - data.originalFileName = originalFileName; + data.originalFileName = Git.normalizePath(path.join(commit.repoPath, originalFileName)); } return data; }