Browse Source

Combines blame file vs contents

Providers can now choose how to handle editor contents blaming
Simplies "commit" line model to just have line & originalLine
  - Not really accurate for log parsing, but close enough
Adds forces line blame to improve performance
main
Eric Amodio 2 years ago
parent
commit
e04a58db9c
23 changed files with 184 additions and 146 deletions
  1. +3
    -5
      src/annotations/blameAnnotationProvider.ts
  2. +2
    -4
      src/annotations/gutterBlameAnnotationProvider.ts
  3. +1
    -6
      src/annotations/gutterChangesAnnotationProvider.ts
  4. +1
    -1
      src/annotations/gutterHeatmapBlameAnnotationProvider.ts
  5. +3
    -7
      src/codelens/codeLensProvider.ts
  6. +1
    -7
      src/commands/copyMessageToClipboard.ts
  7. +1
    -3
      src/commands/copyShaToClipboard.ts
  8. +2
    -4
      src/commands/diffLineWithWorking.ts
  9. +1
    -3
      src/commands/openCommitOnRemote.ts
  10. +20
    -14
      src/env/node/git/localGitProvider.ts
  11. +6
    -3
      src/git/gitProvider.ts
  12. +10
    -6
      src/git/gitProviderService.ts
  13. +2
    -8
      src/git/models/commit.ts
  14. +3
    -3
      src/git/parsers/blameParser.ts
  15. +4
    -8
      src/git/parsers/logParser.ts
  16. +2
    -2
      src/hovers/hovers.ts
  17. +2
    -2
      src/hovers/lineHoverController.ts
  18. +105
    -41
      src/premium/github/githubGitProvider.ts
  19. +1
    -1
      src/statusbar/statusBarController.ts
  20. +9
    -13
      src/trackers/gitLineTracker.ts
  21. +1
    -1
      src/views/nodes/commitFileNode.ts
  22. +1
    -1
      src/views/nodes/fileRevisionAsCommitNode.ts
  23. +3
    -3
      src/views/nodes/lineHistoryNode.ts

+ 3
- 5
src/annotations/blameAnnotationProvider.ts View File

@ -21,9 +21,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
) { ) {
super(annotationType, editor, trackedDocument); super(annotationType, editor, trackedDocument);
this.blame = editor.document.isDirty
? this.container.git.getBlameForFileContents(this.trackedDocument.uri, editor.document.getText())
: this.container.git.getBlameForFile(this.trackedDocument.uri);
this.blame = this.container.git.getBlame(this.trackedDocument.uri, editor.document);
if (editor.document.isDirty) { if (editor.document.isDirty) {
trackedDocument.setForceDirtyStateChangeOnNextDocumentChange(); trackedDocument.setForceDirtyStateChangeOnNextDocumentChange();
@ -174,8 +172,8 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
private async getDetailsHoverMessage(commit: GitCommit, document: TextDocument) { private async getDetailsHoverMessage(commit: GitCommit, document: TextDocument) {
let editorLine = this.editor.selection.active.line; let editorLine = this.editor.selection.active.line;
const line = editorLine + 1; const line = editorLine + 1;
const commitLine = commit.lines.find(l => l.to.line === line) ?? commit.lines[0];
editorLine = commitLine.from.line - 1;
const commitLine = commit.lines.find(l => l.line === line) ?? commit.lines[0];
editorLine = commitLine.originalLine - 1;
return Hovers.detailsMessage( return Hovers.detailsMessage(
commit, commit,

+ 2
- 4
src/annotations/gutterBlameAnnotationProvider.ts View File

@ -89,7 +89,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
for (const l of blame.lines) { for (const l of blame.lines) {
// editor lines are 0-based // editor lines are 0-based
const editorLine = l.to.line - 1;
const editorLine = l.line - 1;
if (previousSha === l.sha) { if (previousSha === l.sha) {
if (gutter == null) continue; if (gutter == null) continue;
@ -202,9 +202,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const highlightDecorationRanges = Arrays.filterMap(blame.lines, l => const highlightDecorationRanges = Arrays.filterMap(blame.lines, l =>
l.sha === sha l.sha === sha
? // editor lines are 0-based ? // editor lines are 0-based
this.editor.document.validateRange(
new Range(l.to.line - 1, 0, l.to.line - 1, Number.MAX_SAFE_INTEGER),
)
this.editor.document.validateRange(new Range(l.line - 1, 0, l.line - 1, Number.MAX_SAFE_INTEGER))
: undefined, : undefined,
); );

+ 1
- 6
src/annotations/gutterChangesAnnotationProvider.ts View File

@ -166,12 +166,7 @@ export class GutterChangesAnnotationProvider extends AnnotationProviderBase
// If we want to only show changes from the specified sha, get the blame so we can compare with "visible" shas // If we want to only show changes from the specified sha, get the blame so we can compare with "visible" shas
const blame = const blame =
context?.sha != null && context?.only context?.sha != null && context?.only
? this.editor?.document.isDirty
? await this.container.git.getBlameForFileContents(
this.trackedDocument.uri,
this.editor.document.getText(),
)
: await this.container.git.getBlameForFile(this.trackedDocument.uri)
? await this.container.git.getBlame(this.trackedDocument.uri, this.editor?.document)
: undefined; : undefined;
let selection: Selection | undefined; let selection: Selection | undefined;

+ 1
- 1
src/annotations/gutterHeatmapBlameAnnotationProvider.ts View File

@ -36,7 +36,7 @@ export class GutterHeatmapBlameAnnotationProvider extends BlameAnnotationProvide
let commit: GitCommit | undefined; let commit: GitCommit | undefined;
for (const l of blame.lines) { for (const l of blame.lines) {
// editor lines are 0-based // editor lines are 0-based
const editorLine = l.to.line - 1;
const editorLine = l.line - 1;
commit = blame.commits.get(l.sha); commit = blame.commits.get(l.sha);
if (commit == null) continue; if (commit == null) continue;

+ 3
- 7
src/codelens/codeLensProvider.ts View File

@ -154,14 +154,10 @@ export class GitCodeLensProvider implements CodeLensProvider {
if (token.isCancellationRequested) return lenses; if (token.isCancellationRequested) return lenses;
if (languageScope.scopes.length === 1 && languageScope.scopes.includes(CodeLensScopes.Document)) { if (languageScope.scopes.length === 1 && languageScope.scopes.includes(CodeLensScopes.Document)) {
blame = document.isDirty
? await this.container.git.getBlameForFileContents(gitUri, document.getText())
: await this.container.git.getBlameForFile(gitUri);
blame = await this.container.git.getBlame(gitUri, document);
} else { } else {
[blame, symbols] = await Promise.all([ [blame, symbols] = await Promise.all([
document.isDirty
? this.container.git.getBlameForFileContents(gitUri, document.getText())
: this.container.git.getBlameForFile(gitUri),
this.container.git.getBlame(gitUri, document),
commands.executeCommand<SymbolInformation[]>( commands.executeCommand<SymbolInformation[]>(
BuiltInCommands.ExecuteDocumentSymbolProvider, BuiltInCommands.ExecuteDocumentSymbolProvider,
document.uri, document.uri,
@ -169,7 +165,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
]); ]);
} }
if (blame === undefined || blame.lines.length === 0) return lenses;
if (blame == null || blame?.lines.length === 0) return lenses;
} else if (languageScope.scopes.length !== 1 || !languageScope.scopes.includes(CodeLensScopes.Document)) { } else if (languageScope.scopes.length !== 1 || !languageScope.scopes.includes(CodeLensScopes.Document)) {
symbols = await commands.executeCommand<SymbolInformation[]>( symbols = await commands.executeCommand<SymbolInformation[]>(
BuiltInCommands.ExecuteDocumentSymbolProvider, BuiltInCommands.ExecuteDocumentSymbolProvider,

+ 1
- 7
src/commands/copyMessageToClipboard.ts View File

@ -73,13 +73,7 @@ export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
if (blameline < 0) return; if (blameline < 0) return;
try { try {
const blame = editor?.document.isDirty
? await this.container.git.getBlameForLineContents(
gitUri,
blameline,
editor.document.getText(),
)
: await this.container.git.getBlameForLine(gitUri, blameline);
const blame = await this.container.git.getBlameForLine(gitUri, blameline, editor?.document);
if (blame == null || blame.commit.isUncommitted) return; if (blame == null || blame.commit.isUncommitted) return;
void (await GitActions.Commit.copyMessageToClipboard(blame.commit)); void (await GitActions.Commit.copyMessageToClipboard(blame.commit));

+ 1
- 3
src/commands/copyShaToClipboard.ts View File

@ -65,9 +65,7 @@ export class CopyShaToClipboardCommand extends ActiveEditorCommand {
try { try {
const gitUri = await GitUri.fromUri(uri); const gitUri = await GitUri.fromUri(uri);
const blame = editor?.document.isDirty
? await this.container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await this.container.git.getBlameForLine(gitUri, blameline);
const blame = await this.container.git.getBlameForLine(gitUri, blameline, editor?.document);
if (blame == null) return; if (blame == null) return;
args.sha = blame.commit.sha; args.sha = blame.commit.sha;

+ 2
- 4
src/commands/diffLineWithWorking.ts View File

@ -39,9 +39,7 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
if (blameline < 0) return; if (blameline < 0) return;
try { try {
const blame = editor?.document.isDirty
? await this.container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await this.container.git.getBlameForLine(gitUri, blameline);
const blame = await this.container.git.getBlameForLine(gitUri, blameline, editor?.document);
if (blame == null) { if (blame == null) {
void Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare'); void Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
@ -68,7 +66,7 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
lhsUri = args.commit.file!.uri; lhsUri = args.commit.file!.uri;
} }
// editor lines are 0-based // editor lines are 0-based
args.line = blame.line.to.line - 1;
args.line = blame.line.line - 1;
} catch (ex) { } catch (ex) {
Logger.error(ex, 'DiffLineWithWorkingCommand', `getBlameForLine(${blameline})`); Logger.error(ex, 'DiffLineWithWorkingCommand', `getBlameForLine(${blameline})`);
void Messages.showGenericErrorMessage('Unable to open compare'); void Messages.showGenericErrorMessage('Unable to open compare');

+ 1
- 3
src/commands/openCommitOnRemote.ts View File

@ -71,9 +71,7 @@ export class OpenCommitOnRemoteCommand extends ActiveEditorCommand {
const blameline = editor == null ? 0 : editor.selection.active.line; const blameline = editor == null ? 0 : editor.selection.active.line;
if (blameline < 0) return; if (blameline < 0) return;
const blame = editor?.document.isDirty
? await this.container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await this.container.git.getBlameForLine(gitUri, blameline);
const blame = await this.container.git.getBlameForLine(gitUri, blameline, editor?.document);
if (blame == null) { if (blame == null) {
void Messages.showFileNotUnderSourceControlWarningMessage( void Messages.showFileNotUnderSourceControlWarningMessage(
'Unable to open commit on remote provider', 'Unable to open commit on remote provider',

+ 20
- 14
src/env/node/git/localGitProvider.ts View File

@ -9,6 +9,7 @@ import {
extensions, extensions,
FileType, FileType,
Range, Range,
TextDocument,
Uri, Uri,
window, window,
workspace, workspace,
@ -905,9 +906,11 @@ export class LocalGitProvider implements GitProvider, Disposable {
@gate() @gate()
@log() @log()
async getBlameForFile(uri: GitUri): Promise<GitBlame | undefined> {
async getBlame(uri: GitUri, document?: TextDocument | undefined): Promise<GitBlame | undefined> {
const cc = Logger.getCorrelationContext(); const cc = Logger.getCorrelationContext();
if (document?.isDirty) return this.getBlameContents(uri, document.getText());
let key = 'blame'; let key = 'blame';
if (uri.sha != null) { if (uri.sha != null) {
key += `:${uri.sha}`; key += `:${uri.sha}`;
@ -930,7 +933,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
} }
} }
const promise = this.getBlameForFileCore(uri, doc, key, cc);
const promise = this.getBlameCore(uri, doc, key, cc);
if (doc.state != null) { if (doc.state != null) {
Logger.debug(cc, `Cache add: '${key}'`); Logger.debug(cc, `Cache add: '${key}'`);
@ -944,7 +947,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
return promise; return promise;
} }
private async getBlameForFileCore(
private async getBlameCore(
uri: GitUri, uri: GitUri,
document: TrackedDocument<GitDocumentState>, document: TrackedDocument<GitDocumentState>,
key: string, key: string,
@ -986,8 +989,8 @@ export class LocalGitProvider implements GitProvider, Disposable {
} }
} }
@log<LocalGitProvider['getBlameForFileContents']>({ args: { 1: '<contents>' } })
async getBlameForFileContents(uri: GitUri, contents: string): Promise<GitBlame | undefined> {
@log<LocalGitProvider['getBlameContents']>({ args: { 1: '<contents>' } })
async getBlameContents(uri: GitUri, contents: string): Promise<GitBlame | undefined> {
const cc = Logger.getCorrelationContext(); const cc = Logger.getCorrelationContext();
const key = `blame:${md5(contents)}`; const key = `blame:${md5(contents)}`;
@ -1009,7 +1012,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
} }
} }
const promise = this.getBlameForFileContentsCore(uri, contents, doc, key, cc);
const promise = this.getBlameContentsCore(uri, contents, doc, key, cc);
if (doc.state != null) { if (doc.state != null) {
Logger.debug(cc, `Cache add: '${key}'`); Logger.debug(cc, `Cache add: '${key}'`);
@ -1023,7 +1026,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
return promise; return promise;
} }
private async getBlameForFileContentsCore(
private async getBlameContentsCore(
uri: GitUri, uri: GitUri,
contents: string, contents: string,
document: TrackedDocument<GitDocumentState>, document: TrackedDocument<GitDocumentState>,
@ -1071,10 +1074,13 @@ export class LocalGitProvider implements GitProvider, Disposable {
async getBlameForLine( async getBlameForLine(
uri: GitUri, uri: GitUri,
editorLine: number, editorLine: number,
document?: TextDocument | undefined,
options?: { forceSingleLine?: boolean }, options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined> { ): Promise<GitBlameLine | undefined> {
if (document?.isDirty) return this.getBlameForLineContents(uri, editorLine, document.getText(), options);
if (!options?.forceSingleLine && this.useCaching) { if (!options?.forceSingleLine && this.useCaching) {
const blame = await this.getBlameForFile(uri);
const blame = await this.getBlame(uri);
if (blame == null) return undefined; if (blame == null) return undefined;
let blameLine = blame.lines[editorLine]; let blameLine = blame.lines[editorLine];
@ -1125,7 +1131,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
options?: { forceSingleLine?: boolean }, options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined> { ): Promise<GitBlameLine | undefined> {
if (!options?.forceSingleLine && this.useCaching) { if (!options?.forceSingleLine && this.useCaching) {
const blame = await this.getBlameForFileContents(uri, contents);
const blame = await this.getBlameContents(uri, contents);
if (blame == null) return undefined; if (blame == null) return undefined;
let blameLine = blame.lines[editorLine]; let blameLine = blame.lines[editorLine];
@ -1170,7 +1176,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log() @log()
async getBlameForRange(uri: GitUri, range: Range): Promise<GitBlameLines | undefined> { async getBlameForRange(uri: GitUri, range: Range): Promise<GitBlameLines | undefined> {
const blame = await this.getBlameForFile(uri);
const blame = await this.getBlame(uri);
if (blame == null) return undefined; if (blame == null) return undefined;
return this.getBlameRange(blame, uri, range); return this.getBlameRange(blame, uri, range);
@ -1178,7 +1184,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log<LocalGitProvider['getBlameForRangeContents']>({ args: { 2: '<contents>' } }) @log<LocalGitProvider['getBlameForRangeContents']>({ args: { 2: '<contents>' } })
async getBlameForRangeContents(uri: GitUri, range: Range, contents: string): Promise<GitBlameLines | undefined> { async getBlameForRangeContents(uri: GitUri, range: Range, contents: string): Promise<GitBlameLines | undefined> {
const blame = await this.getBlameForFileContents(uri, contents);
const blame = await this.getBlameContents(uri, contents);
if (blame == null) return undefined; if (blame == null) return undefined;
return this.getBlameRange(blame, uri, range); return this.getBlameRange(blame, uri, range);
@ -1205,7 +1211,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
if (!shas.has(c.sha)) continue; if (!shas.has(c.sha)) continue;
const commit = c.with({ const commit = c.with({
lines: c.lines.filter(l => l.to.line >= startLine && l.to.line <= endLine),
lines: c.lines.filter(l => l.line >= startLine && l.line <= endLine),
}); });
commits.set(c.sha, commit); commits.set(c.sha, commit);
@ -2915,7 +2921,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
ref = blameLine.commit.sha; ref = blameLine.commit.sha;
relativePath = blameLine.commit.file?.path ?? blameLine.commit.file?.originalPath ?? relativePath; relativePath = blameLine.commit.file?.path ?? blameLine.commit.file?.originalPath ?? relativePath;
uri = this.getAbsoluteUri(relativePath, repoPath); uri = this.getAbsoluteUri(relativePath, repoPath);
editorLine = blameLine.line.from.line - 1;
editorLine = blameLine.line.originalLine - 1;
if (skip === 0 && blameLine.commit.file?.previousSha) { if (skip === 0 && blameLine.commit.file?.previousSha) {
previous = GitUri.fromFile(relativePath, repoPath, blameLine.commit.file.previousSha); previous = GitUri.fromFile(relativePath, repoPath, blameLine.commit.file.previousSha);
@ -2944,7 +2950,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
ref = blameLine.commit.sha; ref = blameLine.commit.sha;
relativePath = blameLine.commit.file?.path ?? blameLine.commit.file?.originalPath ?? relativePath; relativePath = blameLine.commit.file?.path ?? blameLine.commit.file?.originalPath ?? relativePath;
uri = this.getAbsoluteUri(relativePath, repoPath); uri = this.getAbsoluteUri(relativePath, repoPath);
editorLine = blameLine.line.from.line - 1;
editorLine = blameLine.line.originalLine - 1;
if (skip === 0 && blameLine.commit.file?.previousSha) { if (skip === 0 && blameLine.commit.file?.previousSha) {
previous = GitUri.fromFile(relativePath, repoPath, blameLine.commit.file.previousSha); previous = GitUri.fromFile(relativePath, repoPath, blameLine.commit.file.previousSha);

+ 6
- 3
src/git/gitProvider.ts View File

@ -1,4 +1,4 @@
import { Disposable, Event, Range, Uri, WorkspaceFolder } from 'vscode';
import { Disposable, Event, Range, TextDocument, Uri, WorkspaceFolder } from 'vscode';
import { Commit, InputBox } from '../@types/vscode.git'; import { Commit, InputBox } from '../@types/vscode.git';
import { GitUri } from './gitUri'; import { GitUri } from './gitUri';
import { import {
@ -129,23 +129,26 @@ export interface GitProvider extends Disposable {
/** /**
* Returns the blame of a file * Returns the blame of a file
* @param uri Uri of the file to blame * @param uri Uri of the file to blame
* @param document Optional TextDocument to blame the contents of if dirty
*/ */
getBlameForFile(uri: GitUri): Promise<GitBlame | undefined>;
getBlame(uri: GitUri, document?: TextDocument | undefined): Promise<GitBlame | undefined>;
/** /**
* Returns the blame of a file, using the editor contents (for dirty editors) * Returns the blame of a file, using the editor contents (for dirty editors)
* @param uri Uri of the file to blame * @param uri Uri of the file to blame
* @param contents Contents from the editor to use * @param contents Contents from the editor to use
*/ */
getBlameForFileContents(uri: GitUri, contents: string): Promise<GitBlame | undefined>;
getBlameContents(uri: GitUri, contents: string): Promise<GitBlame | undefined>;
/** /**
* Returns the blame of a single line * Returns the blame of a single line
* @param uri Uri of the file to blame * @param uri Uri of the file to blame
* @param editorLine Editor line number (0-based) to blame (Git is 1-based) * @param editorLine Editor line number (0-based) to blame (Git is 1-based)
* @param document Optional TextDocument to blame the contents of if dirty
* @param options.forceSingleLine Forces blame to be for the single line (rather than the whole file) * @param options.forceSingleLine Forces blame to be for the single line (rather than the whole file)
*/ */
getBlameForLine( getBlameForLine(
uri: GitUri, uri: GitUri,
editorLine: number, editorLine: number,
document?: TextDocument | undefined,
options?: { forceSingleLine?: boolean }, options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined>; ): Promise<GitBlameLine | undefined>;
/** /**

+ 10
- 6
src/git/gitProviderService.ts View File

@ -6,6 +6,7 @@ import {
EventEmitter, EventEmitter,
ProgressLocation, ProgressLocation,
Range, Range,
TextDocument,
TextEditor, TextEditor,
Uri, Uri,
window, window,
@ -849,21 +850,22 @@ export class GitProviderService implements Disposable {
/** /**
* Returns the blame of a file * Returns the blame of a file
* @param uri Uri of the file to blame * @param uri Uri of the file to blame
* @param document Optional TextDocument to blame the contents of if dirty
*/ */
async getBlameForFile(uri: GitUri): Promise<GitBlame | undefined> {
async getBlame(uri: GitUri, document?: TextDocument | undefined): Promise<GitBlame | undefined> {
const { provider } = this.getProvider(uri); const { provider } = this.getProvider(uri);
return provider.getBlameForFile(uri);
return provider.getBlame(uri, document);
} }
@log<GitProviderService['getBlameForFileContents']>({ args: { 1: '<contents>' } })
@log<GitProviderService['getBlameContents']>({ args: { 1: '<contents>' } })
/** /**
* Returns the blame of a file, using the editor contents (for dirty editors) * Returns the blame of a file, using the editor contents (for dirty editors)
* @param uri Uri of the file to blame * @param uri Uri of the file to blame
* @param contents Contents from the editor to use * @param contents Contents from the editor to use
*/ */
async getBlameForFileContents(uri: GitUri, contents: string): Promise<GitBlame | undefined> {
async getBlameContents(uri: GitUri, contents: string): Promise<GitBlame | undefined> {
const { provider } = this.getProvider(uri); const { provider } = this.getProvider(uri);
return provider.getBlameForFileContents(uri, contents);
return provider.getBlameContents(uri, contents);
} }
@log() @log()
@ -871,15 +873,17 @@ export class GitProviderService implements Disposable {
* Returns the blame of a single line * Returns the blame of a single line
* @param uri Uri of the file to blame * @param uri Uri of the file to blame
* @param editorLine Editor line number (0-based) to blame (Git is 1-based) * @param editorLine Editor line number (0-based) to blame (Git is 1-based)
* @param document Optional TextDocument to blame the contents of if dirty
* @param options.forceSingleLine Forces blame to be for the single line (rather than the whole file) * @param options.forceSingleLine Forces blame to be for the single line (rather than the whole file)
*/ */
async getBlameForLine( async getBlameForLine(
uri: GitUri, uri: GitUri,
editorLine: number, editorLine: number,
document?: TextDocument | undefined,
options?: { forceSingleLine?: boolean }, options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined> { ): Promise<GitBlameLine | undefined> {
const { provider } = this.getProvider(uri); const { provider } = this.getProvider(uri);
return provider.getBlameForLine(uri, editorLine, options);
return provider.getBlameForLine(uri, editorLine, document, options);
} }
@log<GitProviderService['getBlameForLineContents']>({ args: { 2: '<contents>' } }) @log<GitProviderService['getBlameForLineContents']>({ args: { 2: '<contents>' } })

+ 2
- 8
src/git/models/commit.ts View File

@ -494,14 +494,8 @@ export class GitCommitIdentity {
export interface GitCommitLine { export interface GitCommitLine {
sha: string; sha: string;
previousSha?: string | undefined; previousSha?: string | undefined;
from: {
line: number;
count: number;
};
to: {
line: number;
count: number;
};
originalLine: number;
line: number;
} }
export interface GitCommitStats { export interface GitCommitStats {

+ 3
- 3
src/git/parsers/blameParser.ts View File

@ -254,12 +254,12 @@ export class GitBlameParser {
const line: GitCommitLine = { const line: GitCommitLine = {
sha: entry.sha, sha: entry.sha,
previousSha: commit.file!.previousSha, previousSha: commit.file!.previousSha,
from: { line: entry.originalLine + i, count: 1 },
to: { line: entry.line + i, count: 1 },
originalLine: entry.originalLine + i,
line: entry.line + i,
}; };
commit.lines.push(line); commit.lines.push(line);
lines[line.to.line - 1] = line;
lines[line.line - 1] = line;
} }
} }
} }

+ 4
- 8
src/git/parsers/logParser.ts View File

@ -451,14 +451,10 @@ export class GitLogParser {
if (match !== null) { if (match !== null) {
entry.line = { entry.line = {
sha: entry.sha!, sha: entry.sha!,
from: {
line: parseInt(match[1], 10),
count: parseInt(match[2], 10),
},
to: {
line: parseInt(match[3], 10),
count: parseInt(match[4], 10),
},
originalLine: parseInt(match[1], 10),
// count: parseInt(match[2], 10),
line: parseInt(match[3], 10),
// count: parseInt(match[4], 10),
}; };
} }

+ 2
- 2
src/hovers/hovers.ts View File

@ -43,7 +43,7 @@ export namespace Hovers {
} }
const line = editorLine + 1; const line = editorLine + 1;
const commitLine = commit.lines.find(l => l.to.line === line) ?? commit.lines[0];
const commitLine = commit.lines.find(l => l.line === line) ?? commit.lines[0];
let originalPath = commit.file.originalPath; let originalPath = commit.file.originalPath;
if (originalPath == null) { if (originalPath == null) {
@ -52,7 +52,7 @@ export namespace Hovers {
} }
} }
editorLine = commitLine.to.line - 1;
editorLine = commitLine.line - 1;
// TODO: Doesn't work with dirty files -- pass in editor? or contents? // TODO: Doesn't work with dirty files -- pass in editor? or contents?
let hunkLine = await Container.instance.git.getDiffForLine(uri, editorLine, ref, ref2); let hunkLine = await Container.instance.git.getDiffForLine(uri, editorLine, ref, ref2);

+ 2
- 2
src/hovers/lineHoverController.ts View File

@ -135,8 +135,8 @@ export class LineHoverController implements Disposable {
let editorLine = position.line; let editorLine = position.line;
const line = editorLine + 1; const line = editorLine + 1;
const commitLine = commit.lines.find(l => l.to.line === line) ?? commit.lines[0];
editorLine = commitLine.from.line - 1;
const commitLine = commit.lines.find(l => l.line === line) ?? commit.lines[0];
editorLine = commitLine.originalLine - 1;
const trackedDocument = await this.container.tracker.get(document); const trackedDocument = await this.container.tracker.get(document);
if (trackedDocument == null) return undefined; if (trackedDocument == null) return undefined;

+ 105
- 41
src/premium/github/githubGitProvider.ts View File

@ -7,6 +7,7 @@ import {
EventEmitter, EventEmitter,
FileType, FileType,
Range, Range,
TextDocument,
Uri, Uri,
window, window,
workspace, workspace,
@ -344,9 +345,12 @@ export class GitHubGitProvider implements GitProvider, Disposable {
@gate() @gate()
@log() @log()
async getBlameForFile(uri: GitUri): Promise<GitBlame | undefined> {
async getBlame(uri: GitUri, document?: TextDocument | undefined): Promise<GitBlame | undefined> {
const cc = Logger.getCorrelationContext(); const cc = Logger.getCorrelationContext();
// TODO@eamodio we need to figure out when to do this, since dirty isn't enough, we need to know if there are any uncommitted changes
if (document?.isDirty) return this.getBlameContents(uri, document.getText());
let key = 'blame'; let key = 'blame';
if (uri.sha != null) { if (uri.sha != null) {
key += `:${uri.sha}`; key += `:${uri.sha}`;
@ -367,7 +371,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
doc.state = new GitDocumentState(doc.key); doc.state = new GitDocumentState(doc.key);
} }
const promise = this.getBlameForFileCore(uri, doc, key, cc);
const promise = this.getBlameCore(uri, doc, key, cc);
if (doc.state != null) { if (doc.state != null) {
Logger.debug(cc, `Cache add: '${key}'`); Logger.debug(cc, `Cache add: '${key}'`);
@ -381,7 +385,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
return promise; return promise;
} }
private async getBlameForFileCore(
private async getBlameCore(
uri: GitUri, uri: GitUri,
document: TrackedDocument<GitDocumentState>, document: TrackedDocument<GitDocumentState>,
key: string, key: string,
@ -393,7 +397,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
const { metadata, github, remotehub, session } = context; const { metadata, github, remotehub, session } = context;
const root = remotehub.getVirtualUri(remotehub.getProviderRootUri(uri)); const root = remotehub.getVirtualUri(remotehub.getProviderRootUri(uri));
const file = this.getRelativePath(uri, root);
const relativePath = this.getRelativePath(uri, root);
// const sha = await this.resolveReferenceCore(uri.repoPath!, metadata, uri.sha); // const sha = await this.resolveReferenceCore(uri.repoPath!, metadata, uri.sha);
// if (sha == null) return undefined; // if (sha == null) return undefined;
@ -404,7 +408,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
metadata.repo.owner, metadata.repo.owner,
metadata.repo.name, metadata.repo.name,
ref, ref,
file,
relativePath,
); );
const authors = new Map<string, GitBlameAuthor>(); const authors = new Map<string, GitBlameAuthor>();
@ -440,7 +444,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
c.message.split('\n', 1)[0], c.message.split('\n', 1)[0],
c.parents.nodes[0]?.oid ? [c.parents.nodes[0]?.oid] : [], c.parents.nodes[0]?.oid ? [c.parents.nodes[0]?.oid] : [],
c.message, c.message,
new GitFileChange(root.toString(), file, GitFileIndexStatus.Modified),
new GitFileChange(root.toString(), relativePath, GitFileIndexStatus.Modified),
{ changedFiles: c.changedFiles ?? 0, additions: c.additions ?? 0, deletions: c.deletions ?? 0 }, { changedFiles: c.changedFiles ?? 0, additions: c.additions ?? 0, deletions: c.deletions ?? 0 },
[], [],
); );
@ -449,17 +453,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
} }
for (let i = range.startingLine; i <= range.endingLine; i++) { for (let i = range.startingLine; i <= range.endingLine; i++) {
const line: GitCommitLine = {
sha: c.oid,
from: {
line: i,
count: 1,
},
to: {
line: i,
count: 1,
},
};
const line: GitCommitLine = { sha: c.oid, originalLine: i, line: i };
commit.lines.push(line); commit.lines.push(line);
lines[i - 1] = line; lines[i - 1] = line;
@ -496,9 +490,10 @@ export class GitHubGitProvider implements GitProvider, Disposable {
} }
} }
@log<GitHubGitProvider['getBlameForFileContents']>({ args: { 1: '<contents>' } })
async getBlameForFileContents(uri: GitUri, _contents: string): Promise<GitBlame | undefined> {
return this.getBlameForFile(uri);
@log<GitHubGitProvider['getBlameContents']>({ args: { 1: '<contents>' } })
async getBlameContents(_uri: GitUri, _contents: string): Promise<GitBlame | undefined> {
// TODO@eamodio figure out how to actually generate a blame given the contents (need to generate a diff)
return undefined; //this.getBlame(uri);
} }
@gate() @gate()
@ -506,41 +501,110 @@ export class GitHubGitProvider implements GitProvider, Disposable {
async getBlameForLine( async getBlameForLine(
uri: GitUri, uri: GitUri,
editorLine: number, editorLine: number,
_options?: { forceSingleLine?: boolean },
document?: TextDocument | undefined,
options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined> { ): Promise<GitBlameLine | undefined> {
const blame = await this.getBlameForFile(uri);
if (blame == null) return undefined;
const cc = Logger.getCorrelationContext();
// TODO@eamodio we need to figure out when to do this, since dirty isn't enough, we need to know if there are any uncommitted changes
if (document?.isDirty) return this.getBlameForLineContents(uri, editorLine, document.getText(), options);
if (!options?.forceSingleLine) {
const blame = await this.getBlame(uri);
if (blame == null) return undefined;
let blameLine = blame.lines[editorLine];
if (blameLine == null) {
if (blame.lines.length !== editorLine) return undefined;
blameLine = blame.lines[editorLine - 1];
}
const commit = blame.commits.get(blameLine.sha);
if (commit == null) return undefined;
let blameLine = blame.lines[editorLine];
if (blameLine == null) {
if (blame.lines.length !== editorLine) return undefined;
blameLine = blame.lines[editorLine - 1];
const author = blame.authors.get(commit.author.name)!;
return {
author: { ...author, lineCount: commit.lines.length },
commit: commit,
line: blameLine,
};
} }
const commit = blame.commits.get(blameLine.sha);
if (commit == null) return undefined;
try {
const context = await this.ensureRepositoryContext(uri.repoPath!);
if (context == null) return undefined;
const { metadata, github, remotehub, session } = context;
const author = blame.authors.get(commit.author.name)!;
return {
author: { ...author, lineCount: commit.lines.length },
commit: commit,
line: blameLine,
};
const root = remotehub.getVirtualUri(remotehub.getProviderRootUri(uri));
const relativePath = this.getRelativePath(uri, root);
const ref = uri.sha ?? (await metadata.getRevision()).revision;
const blame = await github.getBlame(
session?.accessToken,
metadata.repo.owner,
metadata.repo.name,
ref,
relativePath,
);
const range = blame.ranges.find(r => r.startingLine === editorLine);
if (range == null) return undefined;
const c = range.commit;
const { viewer = session.account.label } = blame;
const authorName = viewer != null && c.author.name === viewer ? 'You' : c.author.name;
const committerName = viewer != null && c.committer.name === viewer ? 'You' : c.committer.name;
const commit = new GitCommit(
this.container,
uri.repoPath!,
c.oid,
new GitCommitIdentity(authorName, c.author.email, new Date(c.author.date), c.author.avatarUrl),
new GitCommitIdentity(committerName, c.committer.email, new Date(c.author.date)),
c.message.split('\n', 1)[0],
c.parents.nodes[0]?.oid ? [c.parents.nodes[0]?.oid] : [],
c.message,
new GitFileChange(root.toString(), relativePath, GitFileIndexStatus.Modified),
{ changedFiles: c.changedFiles ?? 0, additions: c.additions ?? 0, deletions: c.deletions ?? 0 },
[],
);
for (let i = range.startingLine; i <= range.endingLine; i++) {
const line: GitCommitLine = { sha: c.oid, originalLine: i, line: i };
commit.lines.push(line);
}
return {
author: {
name: authorName,
lineCount: range.endingLine - range.startingLine + 1,
},
commit: commit,
line: { sha: c.oid, originalLine: range.startingLine, line: editorLine },
};
} catch (ex) {
debugger;
Logger.error(cc, ex);
return undefined;
}
} }
@log<GitHubGitProvider['getBlameForLineContents']>({ args: { 2: '<contents>' } }) @log<GitHubGitProvider['getBlameForLineContents']>({ args: { 2: '<contents>' } })
async getBlameForLineContents( async getBlameForLineContents(
uri: GitUri,
editorLine: number,
_uri: GitUri,
_editorLine: number,
_contents: string, _contents: string,
_options?: { forceSingleLine?: boolean }, _options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined> { ): Promise<GitBlameLine | undefined> {
return this.getBlameForLine(uri, editorLine);
// TODO@eamodio figure out how to actually generate a blame given the contents (need to generate a diff)
return undefined; //this.getBlameForLine(uri, editorLine);
} }
@log() @log()
async getBlameForRange(uri: GitUri, range: Range): Promise<GitBlameLines | undefined> { async getBlameForRange(uri: GitUri, range: Range): Promise<GitBlameLines | undefined> {
const blame = await this.getBlameForFile(uri);
const blame = await this.getBlame(uri);
if (blame == null) return undefined; if (blame == null) return undefined;
return this.getBlameRange(blame, uri, range); return this.getBlameRange(blame, uri, range);
@ -548,7 +612,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
@log<GitHubGitProvider['getBlameForRangeContents']>({ args: { 2: '<contents>' } }) @log<GitHubGitProvider['getBlameForRangeContents']>({ args: { 2: '<contents>' } })
async getBlameForRangeContents(uri: GitUri, range: Range, contents: string): Promise<GitBlameLines | undefined> { async getBlameForRangeContents(uri: GitUri, range: Range, contents: string): Promise<GitBlameLines | undefined> {
const blame = await this.getBlameForFileContents(uri, contents);
const blame = await this.getBlameContents(uri, contents);
if (blame == null) return undefined; if (blame == null) return undefined;
return this.getBlameRange(blame, uri, range); return this.getBlameRange(blame, uri, range);
@ -575,7 +639,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
if (!shas.has(c.sha)) continue; if (!shas.has(c.sha)) continue;
const commit = c.with({ const commit = c.with({
lines: c.lines.filter(l => l.to.line >= startLine && l.to.line <= endLine),
lines: c.lines.filter(l => l.line >= startLine && l.line <= endLine),
}); });
commits.set(c.sha, commit); commits.set(c.sha, commit);

+ 1
- 1
src/statusbar/statusBarController.ts View File

@ -371,7 +371,7 @@ export class StatusBarController implements Disposable {
const tooltip = await Hovers.detailsMessage( const tooltip = await Hovers.detailsMessage(
commit, commit,
commit.getGitUri(), commit.getGitUri(),
commit.lines[0].to.line,
commit.lines[0].line,
this.container.config.statusBar.tooltipFormat, this.container.config.statusBar.tooltipFormat,
this.container.config.defaultDateFormat, this.container.config.defaultDateFormat,
{ {

+ 9
- 13
src/trackers/gitLineTracker.ts View File

@ -148,14 +148,12 @@ export class GitLineTracker extends LineTracker {
} }
if (selections.length === 1) { if (selections.length === 1) {
const blameLine = editor.document.isDirty
? await this.container.git.getBlameForLineContents(
trackedDocument.uri,
selections[0].active,
editor.document.getText(),
)
: await this.container.git.getBlameForLine(trackedDocument.uri, selections[0].active);
if (blameLine === undefined) {
const blameLine = await this.container.git.getBlameForLine(
trackedDocument.uri,
selections[0].active,
editor?.document,
);
if (blameLine == null) {
if (cc != null) { if (cc != null) {
cc.exitDetails = ` ${GlyphChars.Dot} blame failed`; cc.exitDetails = ` ${GlyphChars.Dot} blame failed`;
} }
@ -163,12 +161,10 @@ export class GitLineTracker extends LineTracker {
return false; return false;
} }
this.setState(blameLine.line.to.line - 1, new GitLineState(blameLine.commit));
this.setState(blameLine.line.line - 1, new GitLineState(blameLine.commit));
} else { } else {
const blame = editor.document.isDirty
? await this.container.git.getBlameForFileContents(trackedDocument.uri, editor.document.getText())
: await this.container.git.getBlameForFile(trackedDocument.uri);
if (blame === undefined) {
const blame = await this.container.git.getBlame(trackedDocument.uri, editor.document);
if (blame == null) {
if (cc != null) { if (cc != null) {
cc.exitDetails = ` ${GlyphChars.Dot} blame failed`; cc.exitDetails = ` ${GlyphChars.Dot} blame failed`;
} }

+ 1
- 1
src/views/nodes/commitFileNode.ts View File

@ -139,7 +139,7 @@ export class CommitFileNode
override getCommand(): Command | undefined { override getCommand(): Command | undefined {
let line; let line;
if (this.commit.lines.length) { if (this.commit.lines.length) {
line = this.commit.lines[0].to.line - 1;
line = this.commit.lines[0].line - 1;
} else { } else {
line = this._options.selection?.active.line ?? 0; line = this._options.selection?.active.line ?? 0;
} }

+ 1
- 1
src/views/nodes/fileRevisionAsCommitNode.ts View File

@ -140,7 +140,7 @@ export class FileRevisionAsCommitNode extends ViewRefFileNode
override getCommand(): Command | undefined { override getCommand(): Command | undefined {
let line; let line;
if (this.commit.lines.length) { if (this.commit.lines.length) {
line = this.commit.lines[0].to.line - 1;
line = this.commit.lines[0].line - 1;
} else { } else {
line = this._options.selection?.active.line ?? 0; line = this._options.selection?.active.line ?? 0;
} }

+ 3
- 3
src/views/nodes/lineHistoryNode.ts View File

@ -94,11 +94,11 @@ export class LineHistoryNode
const lastLine = blame.lines[blame.lines.length - 1]; const lastLine = blame.lines[blame.lines.length - 1];
// Since there could be a change in the line numbers, update the selection // Since there could be a change in the line numbers, update the selection
const firstActive = selection.active.line === firstLine.to.line - 1;
const firstActive = selection.active.line === firstLine.line - 1;
selection = new Selection( selection = new Selection(
(firstActive ? lastLine : firstLine).from.line - 1,
(firstActive ? lastLine : firstLine).originalLine - 1,
selection.anchor.character, selection.anchor.character,
(firstActive ? firstLine : lastLine).from.line - 1,
(firstActive ? firstLine : lastLine).originalLine - 1,
selection.active.character, selection.active.character,
); );

Loading…
Cancel
Save