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);
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) {
trackedDocument.setForceDirtyStateChangeOnNextDocumentChange();
@ -174,8 +172,8 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
private async getDetailsHoverMessage(commit: GitCommit, document: TextDocument) {
let editorLine = this.editor.selection.active.line;
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(
commit,

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

@ -89,7 +89,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
for (const l of blame.lines) {
// editor lines are 0-based
const editorLine = l.to.line - 1;
const editorLine = l.line - 1;
if (previousSha === l.sha) {
if (gutter == null) continue;
@ -202,9 +202,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const highlightDecorationRanges = Arrays.filterMap(blame.lines, l =>
l.sha === sha
? // 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,
);

+ 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
const blame =
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;
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;
for (const l of blame.lines) {
// editor lines are 0-based
const editorLine = l.to.line - 1;
const editorLine = l.line - 1;
commit = blame.commits.get(l.sha);
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 (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 {
[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[]>(
BuiltInCommands.ExecuteDocumentSymbolProvider,
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)) {
symbols = await commands.executeCommand<SymbolInformation[]>(
BuiltInCommands.ExecuteDocumentSymbolProvider,

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

@ -73,13 +73,7 @@ export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
if (blameline < 0) return;
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;
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 {
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;
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;
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) {
void Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
@ -68,7 +66,7 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
lhsUri = args.commit.file!.uri;
}
// editor lines are 0-based
args.line = blame.line.to.line - 1;
args.line = blame.line.line - 1;
} catch (ex) {
Logger.error(ex, 'DiffLineWithWorkingCommand', `getBlameForLine(${blameline})`);
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;
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) {
void Messages.showFileNotUnderSourceControlWarningMessage(
'Unable to open commit on remote provider',

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

@ -9,6 +9,7 @@ import {
extensions,
FileType,
Range,
TextDocument,
Uri,
window,
workspace,
@ -905,9 +906,11 @@ export class LocalGitProvider implements GitProvider, Disposable {
@gate()
@log()
async getBlameForFile(uri: GitUri): Promise<GitBlame | undefined> {
async getBlame(uri: GitUri, document?: TextDocument | undefined): Promise<GitBlame | undefined> {
const cc = Logger.getCorrelationContext();
if (document?.isDirty) return this.getBlameContents(uri, document.getText());
let key = 'blame';
if (uri.sha != null) {
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) {
Logger.debug(cc, `Cache add: '${key}'`);
@ -944,7 +947,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
return promise;
}
private async getBlameForFileCore(
private async getBlameCore(
uri: GitUri,
document: TrackedDocument<GitDocumentState>,
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 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) {
Logger.debug(cc, `Cache add: '${key}'`);
@ -1023,7 +1026,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
return promise;
}
private async getBlameForFileContentsCore(
private async getBlameContentsCore(
uri: GitUri,
contents: string,
document: TrackedDocument<GitDocumentState>,
@ -1071,10 +1074,13 @@ export class LocalGitProvider implements GitProvider, Disposable {
async getBlameForLine(
uri: GitUri,
editorLine: number,
document?: TextDocument | undefined,
options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined> {
if (document?.isDirty) return this.getBlameForLineContents(uri, editorLine, document.getText(), options);
if (!options?.forceSingleLine && this.useCaching) {
const blame = await this.getBlameForFile(uri);
const blame = await this.getBlame(uri);
if (blame == null) return undefined;
let blameLine = blame.lines[editorLine];
@ -1125,7 +1131,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined> {
if (!options?.forceSingleLine && this.useCaching) {
const blame = await this.getBlameForFileContents(uri, contents);
const blame = await this.getBlameContents(uri, contents);
if (blame == null) return undefined;
let blameLine = blame.lines[editorLine];
@ -1170,7 +1176,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log()
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;
return this.getBlameRange(blame, uri, range);
@ -1178,7 +1184,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log<LocalGitProvider['getBlameForRangeContents']>({ args: { 2: '<contents>' } })
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;
return this.getBlameRange(blame, uri, range);
@ -1205,7 +1211,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
if (!shas.has(c.sha)) continue;
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);
@ -2915,7 +2921,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
ref = blameLine.commit.sha;
relativePath = blameLine.commit.file?.path ?? blameLine.commit.file?.originalPath ?? relativePath;
uri = this.getAbsoluteUri(relativePath, repoPath);
editorLine = blameLine.line.from.line - 1;
editorLine = blameLine.line.originalLine - 1;
if (skip === 0 && 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;
relativePath = blameLine.commit.file?.path ?? blameLine.commit.file?.originalPath ?? relativePath;
uri = this.getAbsoluteUri(relativePath, repoPath);
editorLine = blameLine.line.from.line - 1;
editorLine = blameLine.line.originalLine - 1;
if (skip === 0 && 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 { GitUri } from './gitUri';
import {
@ -129,23 +129,26 @@ export interface GitProvider extends Disposable {
/**
* Returns the blame of a file
* @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)
* @param uri Uri of the file to blame
* @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
* @param uri Uri of the file to blame
* @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)
*/
getBlameForLine(
uri: GitUri,
editorLine: number,
document?: TextDocument | undefined,
options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined>;
/**

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

@ -6,6 +6,7 @@ import {
EventEmitter,
ProgressLocation,
Range,
TextDocument,
TextEditor,
Uri,
window,
@ -849,21 +850,22 @@ export class GitProviderService implements Disposable {
/**
* Returns the blame of a file
* @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);
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)
* @param uri Uri of the file to blame
* @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);
return provider.getBlameForFileContents(uri, contents);
return provider.getBlameContents(uri, contents);
}
@log()
@ -871,15 +873,17 @@ export class GitProviderService implements Disposable {
* Returns the blame of a single line
* @param uri Uri of the file to blame
* @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)
*/
async getBlameForLine(
uri: GitUri,
editorLine: number,
document?: TextDocument | undefined,
options?: { forceSingleLine?: boolean },
): Promise<GitBlameLine | undefined> {
const { provider } = this.getProvider(uri);
return provider.getBlameForLine(uri, editorLine, options);
return provider.getBlameForLine(uri, editorLine, document, options);
}
@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 {
sha: string;
previousSha?: string | undefined;
from: {
line: number;
count: number;
};
to: {
line: number;
count: number;
};
originalLine: number;
line: number;
}
export interface GitCommitStats {

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

@ -254,12 +254,12 @@ export class GitBlameParser {
const line: GitCommitLine = {
sha: entry.sha,
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);
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) {
entry.line = {
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 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;
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?
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;
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);
if (trackedDocument == null) return undefined;

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

@ -7,6 +7,7 @@ import {
EventEmitter,
FileType,
Range,
TextDocument,
Uri,
window,
workspace,
@ -344,9 +345,12 @@ export class GitHubGitProvider implements GitProvider, Disposable {
@gate()
@log()
async getBlameForFile(uri: GitUri): Promise<GitBlame | undefined> {
async getBlame(uri: GitUri, document?: TextDocument | undefined): Promise<GitBlame | 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.getBlameContents(uri, document.getText());
let key = 'blame';
if (uri.sha != null) {
key += `:${uri.sha}`;
@ -367,7 +371,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
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) {
Logger.debug(cc, `Cache add: '${key}'`);
@ -381,7 +385,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
return promise;
}
private async getBlameForFileCore(
private async getBlameCore(
uri: GitUri,
document: TrackedDocument<GitDocumentState>,
key: string,
@ -393,7 +397,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
const { metadata, github, remotehub, session } = context;
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);
// if (sha == null) return undefined;
@ -404,7 +408,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
metadata.repo.owner,
metadata.repo.name,
ref,
file,
relativePath,
);
const authors = new Map<string, GitBlameAuthor>();
@ -440,7 +444,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
c.message.split('\n', 1)[0],
c.parents.nodes[0]?.oid ? [c.parents.nodes[0]?.oid] : [],
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 },
[],
);
@ -449,17 +453,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
}
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);
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()
@ -506,41 +501,110 @@ export class GitHubGitProvider implements GitProvider, Disposable {
async getBlameForLine(
uri: GitUri,
editorLine: number,
_options?: { forceSingleLine?: boolean },
document?: TextDocument | undefined,
options?: { forceSingleLine?: boolean },
): 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>' } })
async getBlameForLineContents(
uri: GitUri,
editorLine: number,
_uri: GitUri,
_editorLine: number,
_contents: string,
_options?: { forceSingleLine?: boolean },
): 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()
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;
return this.getBlameRange(blame, uri, range);
@ -548,7 +612,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
@log<GitHubGitProvider['getBlameForRangeContents']>({ args: { 2: '<contents>' } })
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;
return this.getBlameRange(blame, uri, range);
@ -575,7 +639,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
if (!shas.has(c.sha)) continue;
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);

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

@ -371,7 +371,7 @@ export class StatusBarController implements Disposable {
const tooltip = await Hovers.detailsMessage(
commit,
commit.getGitUri(),
commit.lines[0].to.line,
commit.lines[0].line,
this.container.config.statusBar.tooltipFormat,
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) {
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) {
cc.exitDetails = ` ${GlyphChars.Dot} blame failed`;
}
@ -163,12 +161,10 @@ export class GitLineTracker extends LineTracker {
return false;
}
this.setState(blameLine.line.to.line - 1, new GitLineState(blameLine.commit));
this.setState(blameLine.line.line - 1, new GitLineState(blameLine.commit));
} 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) {
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 {
let line;
if (this.commit.lines.length) {
line = this.commit.lines[0].to.line - 1;
line = this.commit.lines[0].line - 1;
} else {
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 {
let line;
if (this.commit.lines.length) {
line = this.commit.lines[0].to.line - 1;
line = this.commit.lines[0].line - 1;
} else {
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];
// 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(
(firstActive ? lastLine : firstLine).from.line - 1,
(firstActive ? lastLine : firstLine).originalLine - 1,
selection.anchor.character,
(firstActive ? firstLine : lastLine).from.line - 1,
(firstActive ? firstLine : lastLine).originalLine - 1,
selection.active.character,
);

Loading…
Cancel
Save