Browse Source

Attempts to consolidate commit models (wip)

First pass at converting "blame" commits into a unified model
main
Eric Amodio 2 years ago
parent
commit
4979fb1e39
42 changed files with 847 additions and 474 deletions
  1. +3
    -3
      src/annotations/annotations.ts
  2. +15
    -15
      src/annotations/blameAnnotationProvider.ts
  3. +6
    -6
      src/annotations/gutterBlameAnnotationProvider.ts
  4. +2
    -2
      src/annotations/gutterHeatmapBlameAnnotationProvider.ts
  5. +3
    -3
      src/annotations/lineAnnotationController.ts
  6. +1
    -1
      src/avatars.ts
  7. +13
    -13
      src/codelens/codeLensProvider.ts
  8. +3
    -2
      src/commands/common.ts
  9. +27
    -15
      src/commands/diffLineWithWorking.ts
  10. +12
    -7
      src/commands/diffWith.ts
  11. +3
    -3
      src/commands/diffWithPrevious.ts
  12. +5
    -12
      src/commands/openCommitOnRemote.ts
  13. +4
    -4
      src/commands/showQuickCommit.ts
  14. +22
    -16
      src/commands/showQuickCommitFile.ts
  15. +13
    -13
      src/env/node/git/localGitProvider.ts
  16. +72
    -59
      src/git/formatters/commitFormatter.ts
  17. +6
    -0
      src/git/gitProviderService.ts
  18. +2
    -2
      src/git/gitUri.ts
  19. +0
    -1
      src/git/models.ts
  20. +4
    -5
      src/git/models/blame.ts
  21. +0
    -64
      src/git/models/blameCommit.ts
  22. +312
    -4
      src/git/models/commit.ts
  23. +1
    -1
      src/git/models/file.ts
  24. +24
    -6
      src/git/models/logCommit.ts
  25. +77
    -38
      src/git/parsers/blameParser.ts
  26. +1
    -1
      src/git/parsers/diffParser.ts
  27. +4
    -3
      src/git/parsers/logParser.ts
  28. +1
    -1
      src/git/parsers/stashParser.ts
  29. +41
    -34
      src/hovers/hovers.ts
  30. +17
    -17
      src/hovers/lineHoverController.ts
  31. +5
    -3
      src/messages.ts
  32. +42
    -26
      src/premium/github/github.ts
  33. +27
    -25
      src/premium/github/githubGitProvider.ts
  34. +2
    -2
      src/quickpicks/gitQuickPickItems.ts
  35. +5
    -5
      src/statusbar/statusBarController.ts
  36. +13
    -13
      src/system/string.ts
  37. +2
    -2
      src/trackers/gitLineTracker.ts
  38. +2
    -2
      src/views/nodes/commitFileNode.ts
  39. +2
    -2
      src/views/nodes/fileRevisionAsCommitNode.ts
  40. +23
    -23
      src/views/nodes/lineHistoryNode.ts
  41. +12
    -10
      src/webviews/rebaseEditor.ts
  42. +18
    -10
      src/webviews/webviewBase.ts

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

@ -15,7 +15,7 @@ import { Config, configuration } from '../configuration';
import { Colors, GlyphChars } from '../constants'; import { Colors, GlyphChars } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import { CommitFormatOptions, CommitFormatter } from '../git/formatters'; import { CommitFormatOptions, CommitFormatter } from '../git/formatters';
import { GitCommit } from '../git/models';
import { GitCommit2 } from '../git/models';
import { Strings } from '../system'; import { Strings } from '../system';
import { toRgba } from '../webviews/apps/shared/colors'; import { toRgba } from '../webviews/apps/shared/colors';
@ -141,7 +141,7 @@ export class Annotations {
} }
static gutter( static gutter(
commit: GitCommit,
commit: GitCommit2,
format: string, format: string,
dateFormatOrFormatOptions: string | null | CommitFormatOptions, dateFormatOrFormatOptions: string | null | CommitFormatOptions,
renderOptions: RenderOptions, renderOptions: RenderOptions,
@ -227,7 +227,7 @@ export class Annotations {
} }
static trailing( static trailing(
commit: GitCommit,
commit: GitCommit2,
// uri: GitUri, // uri: GitUri,
// editorLine: number, // editorLine: number,
format: string, format: string,

+ 15
- 15
src/annotations/blameAnnotationProvider.ts View File

@ -2,7 +2,7 @@ import { CancellationToken, Disposable, Hover, languages, Position, Range, TextD
import { FileAnnotationType } from '../config'; import { FileAnnotationType } from '../config';
import { Container } from '../container'; import { Container } from '../container';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { GitBlame, GitBlameCommit, GitCommit } from '../git/models';
import { GitBlame, GitCommit2 } from '../git/models';
import { Hovers } from '../hovers/hovers'; import { Hovers } from '../hovers/hovers';
import { log } from '../system'; import { log } from '../system';
import { GitDocumentState, TrackedDocument } from '../trackers/gitDocumentTracker'; import { GitDocumentState, TrackedDocument } from '../trackers/gitDocumentTracker';
@ -171,19 +171,19 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
); );
} }
private async getDetailsHoverMessage(commit: GitBlameCommit, document: TextDocument) {
// Get the full commit message -- since blame only returns the summary
let logCommit: GitCommit | undefined = undefined;
if (!commit.isUncommitted) {
logCommit = await this.container.git.getCommitForFile(commit.repoPath, commit.uri, {
ref: commit.sha,
});
if (logCommit != null) {
// Preserve the previous commit from the blame commit
logCommit.previousFileName = commit.previousFileName;
logCommit.previousSha = commit.previousSha;
}
}
private async getDetailsHoverMessage(commit: GitCommit2, document: TextDocument) {
// // Get the full commit message -- since blame only returns the summary
// let logCommit: GitCommit | undefined = undefined;
// if (!commit.isUncommitted) {
// logCommit = await this.container.git.getCommitForFile(commit.repoPath, commit.uri, {
// ref: commit.sha,
// });
// if (logCommit != null) {
// // Preserve the previous commit from the blame commit
// logCommit.previousFileName = commit.previousFileName;
// logCommit.previousSha = commit.previousSha;
// }
// }
let editorLine = this.editor.selection.active.line; let editorLine = this.editor.selection.active.line;
const line = editorLine + 1; const line = editorLine + 1;
@ -191,7 +191,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
editorLine = commitLine.originalLine - 1; editorLine = commitLine.originalLine - 1;
return Hovers.detailsMessage( return Hovers.detailsMessage(
logCommit ?? commit,
commit,
await GitUri.fromUri(document.uri), await GitUri.fromUri(document.uri),
editorLine, editorLine,
this.container.config.hovers.detailsMarkdownFormat, this.container.config.hovers.detailsMarkdownFormat,

+ 6
- 6
src/annotations/gutterBlameAnnotationProvider.ts View File

@ -3,7 +3,7 @@ import { FileAnnotationType, GravatarDefaultStyle } from '../configuration';
import { GlyphChars } from '../constants'; import { GlyphChars } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import { CommitFormatOptions, CommitFormatter } from '../git/formatters'; import { CommitFormatOptions, CommitFormatter } from '../git/formatters';
import { GitBlame, GitBlameCommit } from '../git/models';
import { GitBlame, GitCommit2 } from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Arrays, Iterables, log, Stopwatch, Strings } from '../system'; import { Arrays, Iterables, log, Stopwatch, Strings } from '../system';
import { GitDocumentState } from '../trackers/gitDocumentTracker'; import { GitDocumentState } from '../trackers/gitDocumentTracker';
@ -75,7 +75,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const decorationsMap = new Map<string, DecorationOptions | undefined>(); const decorationsMap = new Map<string, DecorationOptions | undefined>();
const avatarDecorationsMap = avatars ? new Map<string, ThemableDecorationAttachmentRenderOptions>() : undefined; const avatarDecorationsMap = avatars ? new Map<string, ThemableDecorationAttachmentRenderOptions>() : undefined;
let commit: GitBlameCommit | undefined;
let commit: GitCommit2 | undefined;
let compacted = false; let compacted = false;
let gutter: DecorationOptions | undefined; let gutter: DecorationOptions | undefined;
let previousSha: string | undefined; let previousSha: string | undefined;
@ -150,7 +150,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
decorationOptions.push(gutter); decorationOptions.push(gutter);
if (avatars && commit.email != null) {
if (avatars && commit.author.email != null) {
await this.applyAvatarDecoration(commit, gutter, gravatarDefault, avatarDecorationsMap!); await this.applyAvatarDecoration(commit, gutter, gravatarDefault, avatarDecorationsMap!);
} }
@ -208,12 +208,12 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
} }
private async applyAvatarDecoration( private async applyAvatarDecoration(
commit: GitBlameCommit,
commit: GitCommit2,
gutter: DecorationOptions, gutter: DecorationOptions,
gravatarDefault: GravatarDefaultStyle, gravatarDefault: GravatarDefaultStyle,
map: Map<string, ThemableDecorationAttachmentRenderOptions>, map: Map<string, ThemableDecorationAttachmentRenderOptions>,
) { ) {
let avatarDecoration = map.get(commit.email!);
let avatarDecoration = map.get(commit.author.email ?? '');
if (avatarDecoration == null) { if (avatarDecoration == null) {
const url = (await commit.getAvatarUri({ defaultStyle: gravatarDefault, size: 16 })).toString(true); const url = (await commit.getAvatarUri({ defaultStyle: gravatarDefault, size: 16 })).toString(true);
avatarDecoration = { avatarDecoration = {
@ -224,7 +224,7 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
url, url,
)});background-size:16px 16px;margin-left: 0 !important`, )});background-size:16px 16px;margin-left: 0 !important`,
}; };
map.set(commit.email!, avatarDecoration);
map.set(commit.author.email ?? '', avatarDecoration);
} }
gutter.renderOptions!.after = avatarDecoration; gutter.renderOptions!.after = avatarDecoration;

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

@ -1,7 +1,7 @@
import { Range, TextEditor, TextEditorDecorationType } from 'vscode'; import { Range, TextEditor, TextEditorDecorationType } from 'vscode';
import { FileAnnotationType } from '../configuration'; import { FileAnnotationType } from '../configuration';
import { Container } from '../container'; import { Container } from '../container';
import { GitBlameCommit } from '../git/models';
import { GitCommit2 } from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { log, Stopwatch } from '../system'; import { log, Stopwatch } from '../system';
import { GitDocumentState } from '../trackers/gitDocumentTracker'; import { GitDocumentState } from '../trackers/gitDocumentTracker';
@ -32,7 +32,7 @@ export class GutterHeatmapBlameAnnotationProvider extends BlameAnnotationProvide
>(); >();
const computedHeatmap = await this.getComputedHeatmap(blame); const computedHeatmap = await this.getComputedHeatmap(blame);
let commit: GitBlameCommit | undefined;
let commit: GitCommit2 | 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.line - 1; const editorLine = l.line - 1;

+ 3
- 3
src/annotations/lineAnnotationController.ts View File

@ -14,7 +14,7 @@ import { configuration } from '../configuration';
import { GlyphChars, isTextEditor } from '../constants'; import { GlyphChars, isTextEditor } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import { CommitFormatter } from '../git/formatters'; import { CommitFormatter } from '../git/formatters';
import { GitBlameCommit, PullRequest } from '../git/models';
import { GitCommit2, PullRequest } from '../git/models';
import { Authentication } from '../git/remotes/provider'; import { Authentication } from '../git/remotes/provider';
import { LogCorrelationContext, Logger } from '../logger'; import { LogCorrelationContext, Logger } from '../logger';
import { debug, log } from '../system/decorators/log'; import { debug, log } from '../system/decorators/log';
@ -155,7 +155,7 @@ export class LineAnnotationController implements Disposable {
private async getPullRequests( private async getPullRequests(
repoPath: string, repoPath: string,
lines: [number, GitBlameCommit][],
lines: [number, GitCommit2][],
{ timeout }: { timeout?: number } = {}, { timeout }: { timeout?: number } = {},
) { ) {
if (lines.length === 0) return undefined; if (lines.length === 0) return undefined;
@ -250,7 +250,7 @@ export class LineAnnotationController implements Disposable {
} }
const commitLines = [ const commitLines = [
...filterMap<LineSelection, [number, GitBlameCommit]>(selections, selection => {
...filterMap<LineSelection, [number, GitCommit2]>(selections, selection => {
const state = this.container.lineTracker.getState(selection.active); const state = this.container.lineTracker.getState(selection.active);
if (state?.commit == null) { if (state?.commit == null) {
Logger.debug(cc, `Line ${selection.active} returned no commit`); Logger.debug(cc, `Line ${selection.active} returned no commit`);

+ 1
- 1
src/avatars.ts View File

@ -78,7 +78,7 @@ export function getAvatarUri(
// Double the size to avoid blurring on the retina screen // Double the size to avoid blurring on the retina screen
size *= 2; size *= 2;
if (email == null || email.length === 0) {
if (!email) {
const avatar = createOrUpdateAvatar( const avatar = createOrUpdateAvatar(
`${missingGravatarHash}:${size}`, `${missingGravatarHash}:${size}`,
undefined, undefined,

+ 13
- 13
src/codelens/codeLensProvider.ts View File

@ -38,7 +38,7 @@ import {
import { BuiltInCommands, DocumentSchemes } from '../constants'; import { BuiltInCommands, DocumentSchemes } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import type { GitUri } from '../git/gitUri'; import type { GitUri } from '../git/gitUri';
import { GitBlame, GitBlameLines, GitCommit } from '../git/models';
import { GitBlame, GitBlameLines, GitCommit2 } from '../git/models';
import { RemoteResourceType } from '../git/remotes/provider'; import { RemoteResourceType } from '../git/remotes/provider';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { is, once } from '../system/function'; import { is, once } from '../system/function';
@ -488,7 +488,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
const blame = lens.getBlame(); const blame = lens.getBlame();
if (blame === undefined) return lens; if (blame === undefined) return lens;
const recentCommit: GitCommit = first(blame.commits.values());
const recentCommit = first(blame.commits.values());
// TODO@eamodio This is FAR too expensive, but this accounts for commits that delete lines -- is there another way? // TODO@eamodio This is FAR too expensive, but this accounts for commits that delete lines -- is there another way?
// if (lens.uri != null) { // if (lens.uri != null) {
// const commit = await this.container.git.getCommitForFile(lens.uri.repoPath, lens.uri.fsPath, { // const commit = await this.container.git.getCommitForFile(lens.uri.repoPath, lens.uri.fsPath, {
@ -503,7 +503,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
// } // }
// } // }
let title = `${recentCommit.author}, ${
let title = `${recentCommit.author.name}, ${
lens.dateFormat == null ? recentCommit.formattedDate : recentCommit.formatDate(lens.dateFormat) lens.dateFormat == null ? recentCommit.formattedDate : recentCommit.formatDate(lens.dateFormat)
}`; }`;
if (this.container.config.debug) { if (this.container.config.debug) {
@ -594,7 +594,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
return this.applyCommandWithNoClickAction(title, lens); return this.applyCommandWithNoClickAction(title, lens);
} }
const commit = find(blame.commits.values(), c => c.author === author) ?? first(blame.commits.values());
const commit = find(blame.commits.values(), c => c.author.name === author) ?? first(blame.commits.values());
switch (lens.desiredCommand) { switch (lens.desiredCommand) {
case CodeLensCommand.CopyRemoteCommitUrl: case CodeLensCommand.CopyRemoteCommitUrl:
@ -635,7 +635,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
private applyDiffWithPreviousCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>( private applyDiffWithPreviousCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string, title: string,
lens: T, lens: T,
commit: GitCommit | undefined,
commit: GitCommit2 | undefined,
): T { ): T {
lens.command = command<[undefined, DiffWithPreviousCommandArgs]>({ lens.command = command<[undefined, DiffWithPreviousCommandArgs]>({
title: title, title: title,
@ -654,7 +654,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
private applyCopyOrOpenCommitOnRemoteCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>( private applyCopyOrOpenCommitOnRemoteCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string, title: string,
lens: T, lens: T,
commit: GitCommit,
commit: GitCommit2,
clipboard: boolean = false, clipboard: boolean = false,
): T { ): T {
lens.command = command<[OpenOnRemoteCommandArgs]>({ lens.command = command<[OpenOnRemoteCommandArgs]>({
@ -677,7 +677,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
private applyCopyOrOpenFileOnRemoteCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>( private applyCopyOrOpenFileOnRemoteCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string, title: string,
lens: T, lens: T,
commit: GitCommit,
commit: GitCommit2,
clipboard: boolean = false, clipboard: boolean = false,
): T { ): T {
lens.command = command<[OpenOnRemoteCommandArgs]>({ lens.command = command<[OpenOnRemoteCommandArgs]>({
@ -687,7 +687,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
{ {
resource: { resource: {
type: RemoteResourceType.Revision, type: RemoteResourceType.Revision,
fileName: commit.fileName,
fileName: commit.file?.path ?? '',
sha: commit.sha, sha: commit.sha,
}, },
repoPath: commit.repoPath, repoPath: commit.repoPath,
@ -701,7 +701,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
private applyRevealCommitInViewCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>( private applyRevealCommitInViewCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string, title: string,
lens: T, lens: T,
commit: GitCommit | undefined,
commit: GitCommit2 | undefined,
): T { ): T {
lens.command = command<[Uri, ShowQuickCommitCommandArgs]>({ lens.command = command<[Uri, ShowQuickCommitCommandArgs]>({
title: title, title: title,
@ -721,7 +721,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
title: string, title: string,
lens: T, lens: T,
blame: GitBlameLines, blame: GitBlameLines,
commit?: GitCommit,
commit?: GitCommit2,
): T { ): T {
let refs; let refs;
if (commit === undefined) { if (commit === undefined) {
@ -746,7 +746,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
private applyShowQuickCommitDetailsCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>( private applyShowQuickCommitDetailsCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string, title: string,
lens: T, lens: T,
commit: GitCommit | undefined,
commit: GitCommit2 | undefined,
): T { ): T {
lens.command = command<[Uri, ShowQuickCommitCommandArgs]>({ lens.command = command<[Uri, ShowQuickCommitCommandArgs]>({
title: title, title: title,
@ -765,7 +765,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
private applyShowQuickCommitFileDetailsCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>( private applyShowQuickCommitFileDetailsCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string, title: string,
lens: T, lens: T,
commit: GitCommit | undefined,
commit: GitCommit2 | undefined,
): T { ): T {
lens.command = command<[Uri, ShowQuickCommitFileCommandArgs]>({ lens.command = command<[Uri, ShowQuickCommitFileCommandArgs]>({
title: title, title: title,
@ -825,7 +825,7 @@ export class GitCodeLensProvider implements CodeLensProvider {
private applyToggleFileChangesCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>( private applyToggleFileChangesCommand<T extends GitRecentChangeCodeLens | GitAuthorsCodeLens>(
title: string, title: string,
lens: T, lens: T,
commit: GitCommit,
commit: GitCommit2,
only?: boolean, only?: boolean,
): T { ): T {
lens.command = command<[Uri, ToggleFileChangesAnnotationCommandArgs]>({ lens.command = command<[Uri, ToggleFileChangesAnnotationCommandArgs]>({

+ 3
- 2
src/commands/common.ts View File

@ -20,6 +20,7 @@ import { GitUri } from '../git/gitUri';
import { import {
GitBranch, GitBranch,
GitCommit, GitCommit,
GitCommit2,
GitContributor, GitContributor,
GitFile, GitFile,
GitReference, GitReference,
@ -324,12 +325,12 @@ export function isCommandContextViewNodeHasBranch(
return GitBranch.is((context.node as ViewNode & { branch: GitBranch }).branch); return GitBranch.is((context.node as ViewNode & { branch: GitBranch }).branch);
} }
export function isCommandContextViewNodeHasCommit<T extends GitCommit>(
export function isCommandContextViewNodeHasCommit<T extends GitCommit | GitCommit2>(
context: CommandContext, context: CommandContext,
): context is CommandViewNodeContext & { node: ViewNode & { commit: T } } { ): context is CommandViewNodeContext & { node: ViewNode & { commit: T } } {
if (context.type !== 'viewItem') return false; if (context.type !== 'viewItem') return false;
return GitCommit.is((context.node as ViewNode & { commit: GitCommit }).commit);
return GitCommit.is((context.node as ViewNode & { commit: GitCommit | GitCommit2 }).commit);
} }
export function isCommandContextViewNodeHasContributor( export function isCommandContextViewNodeHasContributor(

+ 27
- 15
src/commands/diffLineWithWorking.ts View File

@ -1,14 +1,14 @@
import { TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode';
import type { Container } from '../container'; import type { Container } from '../container';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { GitCommit, GitRevision } from '../git/models';
import { GitCommit, GitCommit2, GitRevision } from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
import { ActiveEditorCommand, command, Commands, executeCommand, getCommandUri } from './common'; import { ActiveEditorCommand, command, Commands, executeCommand, getCommandUri } from './common';
import { DiffWithCommandArgs } from './diffWith'; import { DiffWithCommandArgs } from './diffWith';
export interface DiffLineWithWorkingCommandArgs { export interface DiffLineWithWorkingCommandArgs {
commit?: GitCommit;
commit?: GitCommit | GitCommit2;
line?: number; line?: number;
showOptions?: TextDocumentShowOptions; showOptions?: TextDocumentShowOptions;
@ -31,6 +31,9 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
args.line = editor?.selection.active.line ?? 0; args.line = editor?.selection.active.line ?? 0;
} }
let lhsSha: string;
let lhsUri: Uri;
if (args.commit == null || args.commit.isUncommitted) { if (args.commit == null || args.commit.isUncommitted) {
const blameline = args.line; const blameline = args.line;
if (blameline < 0) return; if (blameline < 0) return;
@ -47,28 +50,37 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
args.commit = blame.commit; args.commit = blame.commit;
// If the line is uncommitted, change the previous commit
// If the line is uncommitted, use previous commit (or index if the file is staged)
if (args.commit.isUncommitted) { if (args.commit.isUncommitted) {
const status = await this.container.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath); const status = await this.container.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
args.commit = args.commit.with({
sha: status?.indexStatus != null ? GitRevision.uncommittedStaged : args.commit.previousSha!,
fileName: args.commit.previousFileName!,
originalFileName: null,
previousSha: null,
previousFileName: null,
});
// editor lines are 0-based
args.line = blame.line.line - 1;
if (status?.indexStatus != null) {
lhsSha = GitRevision.uncommittedStaged;
lhsUri = this.container.git.getAbsoluteUri(
status.originalFileName || status.fileName,
args.commit.repoPath,
);
} else {
lhsSha = args.commit.file!.previousSha ?? GitRevision.deletedOrMissing;
lhsUri = args.commit.file!.previousUri;
}
} else {
lhsSha = args.commit.sha;
lhsUri = args.commit.file!.uri;
} }
// editor lines are 0-based
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');
return; return;
} }
} else {
lhsSha = args.commit.sha;
lhsUri = args.commit.file?.uri ?? gitUri;
} }
const workingUri = await args.commit.getWorkingUri();
const workingUri = await args.commit.file?.getWorkingUri();
if (workingUri == null) { if (workingUri == null) {
void window.showWarningMessage('Unable to open compare. File has been deleted from the working tree'); void window.showWarningMessage('Unable to open compare. File has been deleted from the working tree');
@ -78,8 +90,8 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
void (await executeCommand<DiffWithCommandArgs>(Commands.DiffWith, { void (await executeCommand<DiffWithCommandArgs>(Commands.DiffWith, {
repoPath: args.commit.repoPath, repoPath: args.commit.repoPath,
lhs: { lhs: {
sha: args.commit.sha,
uri: args.commit.uri,
sha: lhsSha,
uri: lhsUri,
}, },
rhs: { rhs: {
sha: '', sha: '',

+ 12
- 7
src/commands/diffWith.ts View File

@ -1,7 +1,7 @@
import { commands, Range, TextDocumentShowOptions, Uri, ViewColumn } from 'vscode'; import { commands, Range, TextDocumentShowOptions, Uri, ViewColumn } from 'vscode';
import { BuiltInCommands, GlyphChars } from '../constants'; import { BuiltInCommands, GlyphChars } from '../constants';
import type { Container } from '../container'; import type { Container } from '../container';
import { GitCommit, GitRevision } from '../git/models';
import { GitCommit, GitCommit2, GitRevision } from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
import { basename } from '../system/path'; import { basename } from '../system/path';
@ -25,10 +25,10 @@ export interface DiffWithCommandArgs {
@command() @command()
export class DiffWithCommand extends Command { export class DiffWithCommand extends Command {
static getMarkdownCommandArgs(args: DiffWithCommandArgs): string; static getMarkdownCommandArgs(args: DiffWithCommandArgs): string;
static getMarkdownCommandArgs(commit: GitCommit, line?: number): string;
static getMarkdownCommandArgs(argsOrCommit: DiffWithCommandArgs | GitCommit, line?: number): string {
let args: DiffWithCommandArgs | GitCommit;
if (GitCommit.is(argsOrCommit)) {
static getMarkdownCommandArgs(commit: GitCommit | GitCommit2, line?: number): string;
static getMarkdownCommandArgs(argsOrCommit: DiffWithCommandArgs | GitCommit | GitCommit2, line?: number): string {
let args: DiffWithCommandArgs | GitCommit | GitCommit2;
if (GitCommit.is(argsOrCommit) || GitCommit2.is(argsOrCommit)) {
const commit = argsOrCommit; const commit = argsOrCommit;
if (commit.isUncommitted) { if (commit.isUncommitted) {
@ -45,11 +45,16 @@ export class DiffWithCommand extends Command {
line: line, line: line,
}; };
} else { } else {
if (commit.file == null) {
debugger;
throw new Error('Commit has no file');
}
args = { args = {
repoPath: commit.repoPath, repoPath: commit.repoPath,
lhs: { lhs: {
sha: commit.previousSha != null ? commit.previousSha : GitRevision.deletedOrMissing,
uri: commit.previousUri,
sha: commit.file.previousSha ?? GitRevision.deletedOrMissing,
uri: commit.file.previousUri,
}, },
rhs: { rhs: {
sha: commit.sha, sha: commit.sha,

+ 3
- 3
src/commands/diffWithPrevious.ts View File

@ -1,7 +1,7 @@
import { TextDocumentShowOptions, TextEditor, Uri } from 'vscode'; import { TextDocumentShowOptions, TextEditor, Uri } from 'vscode';
import type { Container } from '../container'; import type { Container } from '../container';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { GitCommit, GitRevision } from '../git/models';
import { GitCommit, GitCommit2, GitRevision } from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
import { import {
@ -16,7 +16,7 @@ import {
import { DiffWithCommandArgs } from './diffWith'; import { DiffWithCommandArgs } from './diffWith';
export interface DiffWithPreviousCommandArgs { export interface DiffWithPreviousCommandArgs {
commit?: GitCommit;
commit?: GitCommit2 | GitCommit;
inDiffRightEditor?: boolean; inDiffRightEditor?: boolean;
uri?: Uri; uri?: Uri;
@ -58,7 +58,7 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
repoPath: args.commit.repoPath, repoPath: args.commit.repoPath,
lhs: { lhs: {
sha: `${args.commit.sha}^`, sha: `${args.commit.sha}^`,
uri: args.commit.originalUri,
uri: args.commit.originalUri ?? args.commit.uri,
}, },
rhs: { rhs: {
sha: args.commit.sha || '', sha: args.commit.sha || '',

+ 5
- 12
src/commands/openCommitOnRemote.ts View File

@ -1,6 +1,7 @@
import { TextEditor, Uri, window } from 'vscode'; import { TextEditor, Uri, window } from 'vscode';
import type { Container } from '../container'; import type { Container } from '../container';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { GitRevision } from '../git/models';
import { RemoteResourceType } from '../git/remotes/provider'; import { RemoteResourceType } from '../git/remotes/provider';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
@ -81,18 +82,10 @@ export class OpenCommitOnRemoteCommand extends ActiveEditorCommand {
return; return;
} }
let commit = blame.commit;
// If the line is uncommitted, find the previous commit
if (commit.isUncommitted) {
commit = commit.with({
sha: commit.previousSha,
fileName: commit.previousFileName,
previousSha: null,
previousFileName: null,
});
}
args.sha = commit.sha;
// If the line is uncommitted, use previous commit
args.sha = blame.commit.isUncommitted
? blame.commit.file!.previousSha ?? GitRevision.deletedOrMissing
: blame.commit.sha;
} }
void (await executeCommand<OpenOnRemoteCommandArgs>(Commands.OpenOnRemote, { void (await executeCommand<OpenOnRemoteCommandArgs>(Commands.OpenOnRemote, {

+ 4
- 4
src/commands/showQuickCommit.ts View File

@ -1,7 +1,7 @@
import { TextEditor, Uri } from 'vscode'; import { TextEditor, Uri } from 'vscode';
import type { Container } from '../container'; import type { Container } from '../container';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { GitCommit, GitLog, GitLogCommit } from '../git/models';
import { GitCommit, GitCommit2, GitLog, GitLogCommit } from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
import { import {
@ -17,7 +17,7 @@ import { executeGitCommand, GitActions } from './gitCommands';
export interface ShowQuickCommitCommandArgs { export interface ShowQuickCommitCommandArgs {
repoPath?: string; repoPath?: string;
sha?: string; sha?: string;
commit?: GitCommit | GitLogCommit;
commit?: GitCommit2 | GitCommit | GitLogCommit;
repoLog?: GitLog; repoLog?: GitLog;
revealInView?: boolean; revealInView?: boolean;
} }
@ -115,7 +115,7 @@ export class ShowQuickCommitCommand extends ActiveEditorCachedCommand {
} }
try { try {
if (args.commit == null || args.commit.isFile) {
if (args.commit == null || args.commit.file != null) {
if (args.repoLog != null) { if (args.repoLog != null) {
args.commit = args.repoLog.commits.get(args.sha); args.commit = args.repoLog.commits.get(args.sha);
// If we can't find the commit, kill the repoLog // If we can't find the commit, kill the repoLog
@ -148,7 +148,7 @@ export class ShowQuickCommitCommand extends ActiveEditorCachedCommand {
void (await executeGitCommand({ void (await executeGitCommand({
command: 'show', command: 'show',
state: { state: {
repo: repoPath!,
repo: repoPath,
reference: args.commit as GitLogCommit, reference: args.commit as GitLogCommit,
}, },
})); }));

+ 22
- 16
src/commands/showQuickCommitFile.ts View File

@ -1,7 +1,7 @@
import { TextEditor, Uri, window } from 'vscode'; import { TextEditor, Uri, window } from 'vscode';
import type { Container } from '../container'; import type { Container } from '../container';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { GitBlameCommit, GitCommit, GitLog, GitLogCommit } from '../git/models';
import { GitCommit2, GitLog, GitLogCommit } from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { Messages } from '../messages'; import { Messages } from '../messages';
import { import {
@ -16,7 +16,7 @@ import { executeGitCommand } from './gitCommands';
export interface ShowQuickCommitFileCommandArgs { export interface ShowQuickCommitFileCommandArgs {
sha?: string; sha?: string;
commit?: GitCommit | GitLogCommit;
commit?: GitCommit2 | GitLogCommit;
fileLog?: GitLog; fileLog?: GitLog;
revisionUri?: string; revisionUri?: string;
} }
@ -49,7 +49,7 @@ export class ShowQuickCommitFileCommand extends ActiveEditorCachedCommand {
args.sha = context.node.uri.sha; args.sha = context.node.uri.sha;
if (isCommandContextViewNodeHasCommit(context)) { if (isCommandContextViewNodeHasCommit(context)) {
args.commit = context.node.commit;
args.commit = context.node.commit as any;
} }
} }
@ -103,21 +103,21 @@ export class ShowQuickCommitFileCommand extends ActiveEditorCachedCommand {
} }
try { try {
if (args.commit === undefined || !args.commit.isFile) {
if (args.fileLog !== undefined) {
if (args.commit == null /*|| args.commit.file != null*/) {
if (args.fileLog != null) {
args.commit = args.fileLog.commits.get(args.sha); args.commit = args.fileLog.commits.get(args.sha);
// If we can't find the commit, kill the fileLog // If we can't find the commit, kill the fileLog
if (args.commit === undefined) {
if (args.commit == null) {
args.fileLog = undefined; args.fileLog = undefined;
} }
} }
if (args.fileLog === undefined) {
const repoPath = args.commit === undefined ? gitUri.repoPath : args.commit.repoPath;
if (args.fileLog == null) {
const repoPath = args.commit?.repoPath ?? gitUri.repoPath;
args.commit = await this.container.git.getCommitForFile(repoPath, gitUri, { args.commit = await this.container.git.getCommitForFile(repoPath, gitUri, {
ref: args.sha, ref: args.sha,
}); });
if (args.commit === undefined) {
if (args.commit == null) {
void Messages.showCommitNotFoundWarningMessage('Unable to show commit file details'); void Messages.showCommitNotFoundWarningMessage('Unable to show commit file details');
return; return;
@ -125,25 +125,31 @@ export class ShowQuickCommitFileCommand extends ActiveEditorCachedCommand {
} }
} }
if (args.commit === undefined) {
if (args.commit == null) {
void Messages.showCommitNotFoundWarningMessage('Unable to show commit file details'); void Messages.showCommitNotFoundWarningMessage('Unable to show commit file details');
return; return;
} }
const path = args.commit?.file?.path ?? gitUri.fsPath;
if (GitCommit2.is(args.commit)) {
if (args.commit.files == null) {
await args.commit.ensureFullDetails();
}
}
// const shortSha = GitRevision.shorten(args.sha); // const shortSha = GitRevision.shorten(args.sha);
const fileName = args.commit.fileName;
if (args.commit instanceof GitBlameCommit) {
args.commit = (await this.container.git.getCommit(args.commit.repoPath, args.commit.ref))!;
}
// if (args.commit instanceof GitBlameCommit) {
// args.commit = (await this.container.git.getCommit(args.commit.repoPath, args.commit.ref))!;
// }
void (await executeGitCommand({ void (await executeGitCommand({
command: 'show', command: 'show',
state: { state: {
repo: args.commit.repoPath, repo: args.commit.repoPath,
reference: args.commit as GitLogCommit,
fileName: fileName,
reference: args.commit,
fileName: path,
}, },
})); }));

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

@ -42,11 +42,11 @@ import {
BranchSortOptions, BranchSortOptions,
GitAuthor, GitAuthor,
GitBlame, GitBlame,
GitBlameCommit,
GitBlameLine, GitBlameLine,
GitBlameLines, GitBlameLines,
GitBranch, GitBranch,
GitBranchReference, GitBranchReference,
GitCommit2,
GitCommitType, GitCommitType,
GitContributor, GitContributor,
GitDiff, GitDiff,
@ -1083,7 +1083,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
const commit = blame.commits.get(blameLine.sha); const commit = blame.commits.get(blameLine.sha);
if (commit == null) return undefined; if (commit == null) return undefined;
const author = blame.authors.get(commit.author)!;
const author = blame.authors.get(commit.author.name)!;
return { return {
author: { ...author, lineCount: commit.lines.length }, author: { ...author, lineCount: commit.lines.length },
commit: commit, commit: commit,
@ -1134,7 +1134,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
const commit = blame.commits.get(blameLine.sha); const commit = blame.commits.get(blameLine.sha);
if (commit == null) return undefined; if (commit == null) return undefined;
const author = blame.authors.get(commit.author)!;
const author = blame.authors.get(commit.author.name)!;
return { return {
author: { ...author, lineCount: commit.lines.length }, author: { ...author, lineCount: commit.lines.length },
commit: commit, commit: commit,
@ -1197,7 +1197,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
const endLine = range.end.line + 1; const endLine = range.end.line + 1;
const authors = new Map<string, GitAuthor>(); const authors = new Map<string, GitAuthor>();
const commits = new Map<string, GitBlameCommit>();
const commits = new Map<string, GitCommit2>();
for (const c of blame.commits.values()) { for (const c of blame.commits.values()) {
if (!shas.has(c.sha)) continue; if (!shas.has(c.sha)) continue;
@ -1206,10 +1206,10 @@ export class LocalGitProvider implements GitProvider, Disposable {
}); });
commits.set(c.sha, commit); commits.set(c.sha, commit);
let author = authors.get(commit.author);
let author = authors.get(commit.author.name);
if (author == null) { if (author == null) {
author = { author = {
name: commit.author,
name: commit.author.name,
lineCount: 0, lineCount: 0,
}; };
authors.set(author.name, author); authors.set(author.name, author);
@ -2261,7 +2261,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
return undefined; return undefined;
} }
authors.set(c.author, log.authors.get(c.author)!);
authors.set(c.author.name, log.authors.get(c.author.name)!);
return [ref, c]; return [ref, c];
}, },
), ),
@ -2839,12 +2839,12 @@ export class LocalGitProvider implements GitProvider, Disposable {
// If line is committed, diff with line ref with previous // If line is committed, diff with line ref with previous
else { else {
ref = blameLine.commit.sha; ref = blameLine.commit.sha;
path = blameLine.commit.fileName || (blameLine.commit.originalFileName ?? path);
path = blameLine.commit.file?.path ?? blameLine.commit.file?.originalPath ?? path;
uri = this.getAbsoluteUri(path, repoPath); uri = this.getAbsoluteUri(path, repoPath);
editorLine = blameLine.line.originalLine - 1; editorLine = blameLine.line.originalLine - 1;
if (skip === 0 && blameLine.commit.previousSha) {
previous = GitUri.fromFile(path, repoPath, blameLine.commit.previousSha);
if (skip === 0 && blameLine.commit.file?.previousSha) {
previous = GitUri.fromFile(path, repoPath, blameLine.commit.file.previousSha);
} }
} }
} else { } else {
@ -2868,12 +2868,12 @@ export class LocalGitProvider implements GitProvider, Disposable {
// Diff with line ref with previous // Diff with line ref with previous
ref = blameLine.commit.sha; ref = blameLine.commit.sha;
path = blameLine.commit.fileName || (blameLine.commit.originalFileName ?? path);
path = blameLine.commit.file?.path ?? blameLine.commit.file?.originalPath ?? path;
uri = this.getAbsoluteUri(path, repoPath); uri = this.getAbsoluteUri(path, repoPath);
editorLine = blameLine.line.originalLine - 1; editorLine = blameLine.line.originalLine - 1;
if (skip === 0 && blameLine.commit.previousSha) {
previous = GitUri.fromFile(path, repoPath, blameLine.commit.previousSha);
if (skip === 0 && blameLine.commit.file?.previousSha) {
previous = GitUri.fromFile(path, repoPath, blameLine.commit.file.previousSha);
} }
} }

+ 72
- 59
src/git/formatters/commitFormatter.ts View File

@ -19,12 +19,18 @@ import { Iterables, Strings } from '../../system';
import { PromiseCancelledError } from '../../system/promise'; import { PromiseCancelledError } from '../../system/promise';
import { ContactPresence } from '../../vsls/vsls'; import { ContactPresence } from '../../vsls/vsls';
import type { GitUri } from '../gitUri'; import type { GitUri } from '../gitUri';
import { GitCommit, GitLogCommit, GitRemote, GitRevision, IssueOrPullRequest, PullRequest } from '../models';
import {
GitCommit,
GitCommit2,
GitLogCommit,
GitRemote,
GitRevision,
IssueOrPullRequest,
PullRequest,
} from '../models';
import { RemoteProvider } from '../remotes/provider'; import { RemoteProvider } from '../remotes/provider';
import { FormatOptions, Formatter } from './formatter'; import { FormatOptions, Formatter } from './formatter';
const emptyStr = '';
export interface CommitFormatOptions extends FormatOptions { export interface CommitFormatOptions extends FormatOptions {
autolinkedIssuesOrPullRequests?: Map<string, IssueOrPullRequest | PromiseCancelledError | undefined>; autolinkedIssuesOrPullRequests?: Map<string, IssueOrPullRequest | PromiseCancelledError | undefined>;
avatarSize?: number; avatarSize?: number;
@ -77,29 +83,29 @@ export interface CommitFormatOptions extends FormatOptions {
}; };
} }
export class CommitFormatter extends Formatter<GitCommit, CommitFormatOptions> {
export class CommitFormatter extends Formatter<GitCommit | GitCommit2, CommitFormatOptions> {
private get _authorDate() { private get _authorDate() {
return this._item.formatAuthorDate(this._options.dateFormat);
return this._item.author.formatDate(this._options.dateFormat);
} }
private get _authorDateAgo() { private get _authorDateAgo() {
return this._item.formatAuthorDateFromNow();
return this._item.author.fromNow();
} }
private get _authorDateAgoShort() { private get _authorDateAgoShort() {
return this._item.formatCommitterDateFromNow(true);
return this._item.author.fromNow(true);
} }
private get _committerDate() { private get _committerDate() {
return this._item.formatCommitterDate(this._options.dateFormat);
return this._item.committer.formatDate(this._options.dateFormat);
} }
private get _committerDateAgo() { private get _committerDateAgo() {
return this._item.formatCommitterDateFromNow();
return this._item.committer.fromNow();
} }
private get _committerDateAgoShort() { private get _committerDateAgoShort() {
return this._item.formatCommitterDateFromNow(true);
return this._item.committer.fromNow(true);
} }
private get _date() { private get _date() {
@ -116,16 +122,16 @@ export class CommitFormatter extends Formatter {
private get _pullRequestDate() { private get _pullRequestDate() {
const { pullRequestOrRemote: pr } = this._options; const { pullRequestOrRemote: pr } = this._options;
if (pr == null || !PullRequest.is(pr)) return emptyStr;
if (pr == null || !PullRequest.is(pr)) return '';
return pr.formatDate(this._options.dateFormat) ?? emptyStr;
return pr.formatDate(this._options.dateFormat) ?? '';
} }
private get _pullRequestDateAgo() { private get _pullRequestDateAgo() {
const { pullRequestOrRemote: pr } = this._options; const { pullRequestOrRemote: pr } = this._options;
if (pr == null || !PullRequest.is(pr)) return emptyStr;
if (pr == null || !PullRequest.is(pr)) return '';
return pr.formatDateFromNow() ?? emptyStr;
return pr.formatDateFromNow() ?? '';
} }
private get _pullRequestDateOrAgo() { private get _pullRequestDateOrAgo() {
@ -157,12 +163,11 @@ export class CommitFormatter extends Formatter {
} }
get author(): string { get author(): string {
const author = this._padOrTruncate(this._item.author, this._options.tokenOptions.author);
if (!this._options.markdown) {
return author;
}
const { name, email } = this._item.author;
const author = this._padOrTruncate(name, this._options.tokenOptions.author);
if (!this._options.markdown) return author;
return `[${author}](mailto:${this._item.email} "Email ${this._item.author} (${this._item.email})")`;
return `[${author}](mailto:${email} "Email ${name} (${email})")`;
} }
get authorAgo(): string { get authorAgo(): string {
@ -192,25 +197,26 @@ export class CommitFormatter extends Formatter {
} }
get authorNotYou(): string { get authorNotYou(): string {
if (this._item.author === 'You') return this._padOrTruncate(emptyStr, this._options.tokenOptions.authorNotYou);
const { name, email } = this._item.author;
if (name === 'You') return this._padOrTruncate('', this._options.tokenOptions.authorNotYou);
const author = this._padOrTruncate(this._item.author, this._options.tokenOptions.authorNotYou);
if (!this._options.markdown) {
return author;
}
const author = this._padOrTruncate(name, this._options.tokenOptions.authorNotYou);
if (!this._options.markdown) return author;
return `[${author}](mailto:${this._item.email} "Email ${this._item.author} (${this._item.email})")`;
return `[${author}](mailto:${email} "Email ${name} (${email})")`;
} }
get avatar(): string | Promise<string> { get avatar(): string | Promise<string> {
if (!this._options.markdown || !Container.instance.config.hovers.avatars) { if (!this._options.markdown || !Container.instance.config.hovers.avatars) {
return this._padOrTruncate(emptyStr, this._options.tokenOptions.avatar);
return this._padOrTruncate('', this._options.tokenOptions.avatar);
} }
const { name } = this._item.author;
const presence = this._options.presence; const presence = this._options.presence;
if (presence != null) { if (presence != null) {
const title = `${this._item.author} ${this._item.author === 'You' ? 'are' : 'is'} ${
presence.status === 'dnd' ? 'in ' : emptyStr
const title = `${name} ${name === 'You' ? 'are' : 'is'} ${
presence.status === 'dnd' ? 'in ' : ''
}${presence.statusText.toLocaleLowerCase()}`; }${presence.statusText.toLocaleLowerCase()}`;
const avatarMarkdownPromise = this._getAvatarMarkdown(title, this._options.avatarSize); const avatarMarkdownPromise = this._getAvatarMarkdown(title, this._options.avatarSize);
@ -222,7 +228,7 @@ export class CommitFormatter extends Formatter {
); );
} }
return this._getAvatarMarkdown(this._item.author, this._options.avatarSize);
return this._getAvatarMarkdown(name, this._options.avatarSize);
} }
private async _getAvatarMarkdown(title: string, size?: number) { private async _getAvatarMarkdown(title: string, size?: number) {
@ -243,31 +249,27 @@ export class CommitFormatter extends Formatter {
get changes(): string { get changes(): string {
return this._padOrTruncate( return this._padOrTruncate(
GitLogCommit.is(this._item) ? this._item.getFormattedDiffStatus() : emptyStr,
GitLogCommit.is(this._item) ? this._item.getFormattedDiffStatus() : '',
this._options.tokenOptions.changes, this._options.tokenOptions.changes,
); );
} }
get changesDetail(): string { get changesDetail(): string {
return this._padOrTruncate( return this._padOrTruncate(
GitLogCommit.is(this._item)
? this._item.getFormattedDiffStatus({ expand: true, separator: ', ' })
: emptyStr,
GitLogCommit.is(this._item) ? this._item.getFormattedDiffStatus({ expand: true, separator: ', ' }) : '',
this._options.tokenOptions.changesDetail, this._options.tokenOptions.changesDetail,
); );
} }
get changesShort(): string { get changesShort(): string {
return this._padOrTruncate( return this._padOrTruncate(
GitLogCommit.is(this._item)
? this._item.getFormattedDiffStatus({ compact: true, separator: emptyStr })
: emptyStr,
GitLogCommit.is(this._item) ? this._item.getFormattedDiffStatus({ compact: true, separator: '' }) : '',
this._options.tokenOptions.changesShort, this._options.tokenOptions.changesShort,
); );
} }
get commands(): string { get commands(): string {
if (!this._options.markdown) return this._padOrTruncate(emptyStr, this._options.tokenOptions.commands);
if (!this._options.markdown) return this._padOrTruncate('', this._options.tokenOptions.commands);
let commands; let commands;
if (this._item.isUncommitted) { if (this._item.isUncommitted) {
@ -284,11 +286,11 @@ export class CommitFormatter extends Formatter {
commands += ` &nbsp;[$(chevron-left)$(compare-changes)](${DiffWithCommand.getMarkdownCommandArgs({ commands += ` &nbsp;[$(chevron-left)$(compare-changes)](${DiffWithCommand.getMarkdownCommandArgs({
lhs: { lhs: {
sha: diffUris.previous.sha ?? emptyStr,
sha: diffUris.previous.sha ?? '',
uri: diffUris.previous.documentUri(), uri: diffUris.previous.documentUri(),
}, },
rhs: { rhs: {
sha: diffUris.current.sha ?? emptyStr,
sha: diffUris.current.sha ?? '',
uri: diffUris.current.documentUri(), uri: diffUris.current.documentUri(),
}, },
repoPath: this._item.repoPath, repoPath: this._item.repoPath,
@ -370,6 +372,8 @@ export class CommitFormatter extends Formatter {
} }
if (Container.instance.actionRunners.count('hover.commands') > 0) { if (Container.instance.actionRunners.count('hover.commands') > 0) {
const { name, email } = this._item.author;
commands += `${separator}[$(organization) Team${GlyphChars.SpaceThinnest}${ commands += `${separator}[$(organization) Team${GlyphChars.SpaceThinnest}${
GlyphChars.Ellipsis GlyphChars.Ellipsis
}](${getMarkdownActionCommand<HoverCommandsActionContext>('hover.commands', { }](${getMarkdownActionCommand<HoverCommandsActionContext>('hover.commands', {
@ -377,8 +381,8 @@ export class CommitFormatter extends Formatter {
commit: { commit: {
sha: this._item.sha, sha: this._item.sha,
author: { author: {
name: this._item.author,
email: this._item.email,
name: name,
email: email,
presence: this._options.presence, presence: this._options.presence,
}, },
}, },
@ -430,13 +434,14 @@ export class CommitFormatter extends Formatter {
} }
get email(): string { get email(): string {
return this._padOrTruncate(this._item.email ?? emptyStr, this._options.tokenOptions.email);
const { email } = this._item.author;
return this._padOrTruncate(email ?? '', this._options.tokenOptions.email);
} }
get footnotes(): string { get footnotes(): string {
return this._padOrTruncate( return this._padOrTruncate(
this._options.footnotes == null || this._options.footnotes.size === 0 this._options.footnotes == null || this._options.footnotes.size === 0
? emptyStr
? ''
: Iterables.join( : Iterables.join(
Iterables.map(this._options.footnotes, ([i, footnote]) => Iterables.map(this._options.footnotes, ([i, footnote]) =>
this._options.markdown ? footnote : `${Strings.getSuperscript(i)} ${footnote}`, this._options.markdown ? footnote : `${Strings.getSuperscript(i)} ${footnote}`,
@ -448,7 +453,7 @@ export class CommitFormatter extends Formatter {
} }
get id(): string { get id(): string {
const sha = this._padOrTruncate(this._item.shortSha ?? emptyStr, this._options.tokenOptions.id);
const sha = this._padOrTruncate(this._item.shortSha ?? '', this._options.tokenOptions.id);
if (this._options.markdown && this._options.unpublished) { if (this._options.markdown && this._options.unpublished) {
return `<span style="color:#35b15e;">${sha} (unpublished)</span>`; return `<span style="color:#35b15e;">${sha} (unpublished)</span>`;
} }
@ -471,7 +476,7 @@ export class CommitFormatter extends Formatter {
); );
} }
let message = this._item.message;
let message = this._item.message ?? this._item.summary;
if (this._options.messageTruncateAtNewLine) { if (this._options.messageTruncateAtNewLine) {
const index = message.indexOf('\n'); const index = message.indexOf('\n');
if (index !== -1) { if (index !== -1) {
@ -501,7 +506,7 @@ export class CommitFormatter extends Formatter {
get pullRequest(): string { get pullRequest(): string {
const { pullRequestOrRemote: pr } = this._options; const { pullRequestOrRemote: pr } = this._options;
if (pr == null) return this._padOrTruncate(emptyStr, this._options.tokenOptions.pullRequest);
if (pr == null) return this._padOrTruncate('', this._options.tokenOptions.pullRequest);
let text; let text;
if (PullRequest.is(pr)) { if (PullRequest.is(pr)) {
@ -543,9 +548,9 @@ export class CommitFormatter extends Formatter {
} else if (pr instanceof PromiseCancelledError) { } else if (pr instanceof PromiseCancelledError) {
text = this._options.markdown text = this._options.markdown
? `[PR $(loading~spin)](command:${Commands.RefreshHover} "Searching for a Pull Request (if any) that introduced this commit...")` ? `[PR $(loading~spin)](command:${Commands.RefreshHover} "Searching for a Pull Request (if any) that introduced this commit...")`
: this._options?.pullRequestPendingMessage ?? emptyStr;
: this._options?.pullRequestPendingMessage ?? '';
} else { } else {
return this._padOrTruncate(emptyStr, this._options.tokenOptions.pullRequest);
return this._padOrTruncate('', this._options.tokenOptions.pullRequest);
} }
return this._padOrTruncate(text, this._options.tokenOptions.pullRequest); return this._padOrTruncate(text, this._options.tokenOptions.pullRequest);
@ -566,13 +571,13 @@ export class CommitFormatter extends Formatter {
get pullRequestState(): string { get pullRequestState(): string {
const { pullRequestOrRemote: pr } = this._options; const { pullRequestOrRemote: pr } = this._options;
return this._padOrTruncate( return this._padOrTruncate(
pr == null || !PullRequest.is(pr) ? emptyStr : pr.state ?? emptyStr,
pr == null || !PullRequest.is(pr) ? '' : pr.state ?? '',
this._options.tokenOptions.pullRequestState, this._options.tokenOptions.pullRequestState,
); );
} }
get sha(): string { get sha(): string {
return this._padOrTruncate(this._item.shortSha ?? emptyStr, this._options.tokenOptions.sha);
return this._padOrTruncate(this._item.shortSha ?? '', this._options.tokenOptions.sha);
} }
get tips(): string { get tips(): string {
@ -583,19 +588,19 @@ export class CommitFormatter extends Formatter {
.map(t => `<span style="color:#ffffff;background-color:#1d76db;">&nbsp;&nbsp;${t}&nbsp;&nbsp;</span>`) .map(t => `<span style="color:#ffffff;background-color:#1d76db;">&nbsp;&nbsp;${t}&nbsp;&nbsp;</span>`)
.join(GlyphChars.Space.repeat(3)); .join(GlyphChars.Space.repeat(3));
} }
return this._padOrTruncate(branchAndTagTips ?? emptyStr, this._options.tokenOptions.tips);
return this._padOrTruncate(branchAndTagTips ?? '', this._options.tokenOptions.tips);
} }
static fromTemplate(template: string, commit: GitCommit, dateFormat: string | null): string;
static fromTemplate(template: string, commit: GitCommit, options?: CommitFormatOptions): string;
static fromTemplate(template: string, commit: GitCommit | GitCommit2, dateFormat: string | null): string;
static fromTemplate(template: string, commit: GitCommit | GitCommit2, options?: CommitFormatOptions): string;
static fromTemplate( static fromTemplate(
template: string, template: string,
commit: GitCommit,
commit: GitCommit | GitCommit2,
dateFormatOrOptions?: string | null | CommitFormatOptions, dateFormatOrOptions?: string | null | CommitFormatOptions,
): string; ): string;
static fromTemplate( static fromTemplate(
template: string, template: string,
commit: GitCommit,
commit: GitCommit | GitCommit2,
dateFormatOrOptions?: string | null | CommitFormatOptions, dateFormatOrOptions?: string | null | CommitFormatOptions,
): string { ): string {
if (dateFormatOrOptions == null || typeof dateFormatOrOptions === 'string') { if (dateFormatOrOptions == null || typeof dateFormatOrOptions === 'string') {
@ -618,16 +623,24 @@ export class CommitFormatter extends Formatter {
return super.fromTemplateCore(this, template, commit, dateFormatOrOptions); return super.fromTemplateCore(this, template, commit, dateFormatOrOptions);
} }
static fromTemplateAsync(template: string, commit: GitCommit, dateFormat: string | null): Promise<string>;
static fromTemplateAsync(template: string, commit: GitCommit, options?: CommitFormatOptions): Promise<string>;
static fromTemplateAsync( static fromTemplateAsync(
template: string, template: string,
commit: GitCommit,
commit: GitCommit | GitCommit2,
dateFormat: string | null,
): Promise<string>;
static fromTemplateAsync(
template: string,
commit: GitCommit | GitCommit2,
options?: CommitFormatOptions,
): Promise<string>;
static fromTemplateAsync(
template: string,
commit: GitCommit | GitCommit2,
dateFormatOrOptions?: string | null | CommitFormatOptions, dateFormatOrOptions?: string | null | CommitFormatOptions,
): Promise<string>; ): Promise<string>;
static fromTemplateAsync( static fromTemplateAsync(
template: string, template: string,
commit: GitCommit,
commit: GitCommit | GitCommit2,
dateFormatOrOptions?: string | null | CommitFormatOptions, dateFormatOrOptions?: string | null | CommitFormatOptions,
): Promise<string> { ): Promise<string> {
if (CommitFormatter.has(template, 'footnotes')) { if (CommitFormatter.has(template, 'footnotes')) {

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

@ -41,6 +41,7 @@ import {
BranchDateFormatting, BranchDateFormatting,
BranchSortOptions, BranchSortOptions,
CommitDateFormatting, CommitDateFormatting,
CommitShaFormatting,
GitBlame, GitBlame,
GitBlameLine, GitBlameLine,
GitBlameLines, GitBlameLines,
@ -155,6 +156,7 @@ export class GitProviderService implements Disposable {
BranchDateFormatting.reset(); BranchDateFormatting.reset();
CommitDateFormatting.reset(); CommitDateFormatting.reset();
CommitShaFormatting.reset();
PullRequestDateFormatting.reset(); PullRequestDateFormatting.reset();
this.updateContext(); this.updateContext();
@ -184,6 +186,10 @@ export class GitProviderService implements Disposable {
PullRequestDateFormatting.reset(); PullRequestDateFormatting.reset();
} }
if (configuration.changed(e, 'advanced.abbreviatedShaLength')) {
CommitShaFormatting.reset();
}
if (configuration.changed(e, 'views.contributors.showAllBranches')) { if (configuration.changed(e, 'views.contributors.showAllBranches')) {
this.resetCaches('contributors'); this.resetCaches('contributors');
} }

+ 2
- 2
src/git/gitUri.ts View File

@ -10,7 +10,7 @@ import { memoize } from '../system/decorators/memoize';
import { basename, dirname, isAbsolute, normalizePath, relative } from '../system/path'; import { basename, dirname, isAbsolute, normalizePath, relative } from '../system/path';
import { CharCode, truncateLeft, truncateMiddle } from '../system/string'; import { CharCode, truncateLeft, truncateMiddle } from '../system/string';
import { RevisionUriData } from './gitProvider'; import { RevisionUriData } from './gitProvider';
import { GitCommit, GitFile, GitRevision } from './models';
import { GitCommit, GitCommit2, GitFile, GitRevision } from './models';
export interface GitCommitish { export interface GitCommitish {
fileName?: string; fileName?: string;
@ -230,7 +230,7 @@ export class GitUri extends (Uri as any as UriEx) {
return Container.instance.git.getAbsoluteUri(this.fsPath, this.repoPath); return Container.instance.git.getAbsoluteUri(this.fsPath, this.repoPath);
} }
static fromCommit(commit: GitCommit, previous: boolean = false) {
static fromCommit(commit: GitCommit | GitCommit2, previous: boolean = false) {
if (!previous) return new GitUri(commit.uri, commit); if (!previous) return new GitUri(commit.uri, commit);
return new GitUri(commit.previousUri, { return new GitUri(commit.previousUri, {

+ 0
- 1
src/git/models.ts View File

@ -1,6 +1,5 @@
export * from './models/author'; export * from './models/author';
export * from './models/blame'; export * from './models/blame';
export * from './models/blameCommit';
export * from './models/branch'; export * from './models/branch';
export * from './models/commit'; export * from './models/commit';
export * from './models/contributor'; export * from './models/contributor';

+ 4
- 5
src/git/models/blame.ts View File

@ -1,16 +1,15 @@
import { GitBlameCommit } from './blameCommit';
import { GitAuthor, GitCommitLine } from './commit';
import { GitAuthor, GitCommit2, GitCommitLine } from './commit';
export interface GitBlame { export interface GitBlame {
readonly repoPath: string; readonly repoPath: string;
readonly authors: Map<string, GitAuthor>; readonly authors: Map<string, GitAuthor>;
readonly commits: Map<string, GitBlameCommit>;
readonly commits: Map<string, GitCommit2>;
readonly lines: GitCommitLine[]; readonly lines: GitCommitLine[];
} }
export interface GitBlameLine { export interface GitBlameLine {
readonly author?: GitAuthor; readonly author?: GitAuthor;
readonly commit: GitBlameCommit;
readonly commit: GitCommit2;
readonly line: GitCommitLine; readonly line: GitCommitLine;
} }
@ -20,6 +19,6 @@ export interface GitBlameLines extends GitBlame {
export interface GitBlameCommitLines { export interface GitBlameCommitLines {
readonly author: GitAuthor; readonly author: GitAuthor;
readonly commit: GitBlameCommit;
readonly commit: GitCommit2;
readonly lines: GitCommitLine[]; readonly lines: GitCommitLine[];
} }

+ 0
- 64
src/git/models/blameCommit.ts View File

@ -1,64 +0,0 @@
import { GitCommit, GitCommitLine, GitCommitType } from './commit';
export class GitBlameCommit extends GitCommit {
static override is(commit: any): commit is GitBlameCommit {
return (
commit instanceof GitBlameCommit
//|| (commit.repoPath !== undefined && commit.sha !== undefined && commit.type === GitCommitType.Blame)
);
}
constructor(
repoPath: string,
sha: string,
author: string,
email: string | undefined,
authorDate: Date,
committerDate: Date,
message: string,
fileName: string,
originalFileName: string | undefined,
previousSha: string | undefined,
previousFileName: string | undefined,
public readonly lines: GitCommitLine[],
) {
super(
GitCommitType.Blame,
repoPath,
sha,
author,
email,
authorDate,
committerDate,
message,
fileName,
originalFileName,
previousSha,
previousFileName,
);
}
with(changes: {
sha?: string;
fileName?: string;
originalFileName?: string | null;
previousFileName?: string | null;
previousSha?: string | null;
lines?: GitCommitLine[] | null;
}): GitBlameCommit {
return new GitBlameCommit(
this.repoPath,
changes.sha ?? this.sha,
this.author,
this.email,
this.authorDate,
this.committerDate,
this.message,
changes.fileName ?? this.fileName,
this.getChangedValue(changes.originalFileName, this.originalFileName),
this.getChangedValue(changes.previousSha, this.previousSha),
this.getChangedValue(changes.previousFileName, this.previousFileName),
this.getChangedValue(changes.lines, changes.sha ?? changes.fileName ? [] : this.lines) ?? [],
);
}
}

+ 312
- 4
src/git/models/commit.ts View File

@ -6,7 +6,14 @@ import { formatDate, fromNow } from '../../system/date';
import { memoize } from '../../system/decorators/memoize'; import { memoize } from '../../system/decorators/memoize';
import { CommitFormatter } from '../formatters'; import { CommitFormatter } from '../formatters';
import { GitUri } from '../gitUri'; import { GitUri } from '../gitUri';
import { GitReference, GitRevision, GitRevisionReference, PullRequest } from '../models';
import {
GitFileIndexStatus,
GitFileStatus,
GitReference,
GitRevision,
GitRevisionReference,
PullRequest,
} from '../models';
export interface GitAuthor { export interface GitAuthor {
name: string; name: string;
@ -41,7 +48,308 @@ export const CommitDateFormatting = {
}, },
}; };
export const CommitShaFormatting = {
length: undefined! as number,
reset: () => {
// Don't allow shas to be shortened to less than 5 characters
CommitShaFormatting.length = Math.max(5, Container.instance.config.advanced.abbreviatedShaLength);
},
};
export class GitCommitIdentity {
constructor(
public readonly name: string,
public readonly email: string | undefined,
public readonly date: Date,
private readonly avatarUrl?: string | undefined,
) {}
@memoize<GitCommitIdentity['formatDate']>(format => (format == null ? 'MMMM Do, YYYY h:mma' : format))
formatDate(format?: string | null) {
if (format == null) {
format = 'MMMM Do, YYYY h:mma';
}
return formatDate(this.date, format);
}
fromNow(short?: boolean) {
return fromNow(this.date, short);
}
getAvatarUri(
commit: GitCommit2,
options?: { defaultStyle?: GravatarDefaultStyle; size?: number },
): Uri | Promise<Uri> {
if (this.avatarUrl != null) Uri.parse(this.avatarUrl);
return getAvatarUri(this.email, commit, options);
}
}
export class GitFileChange {
constructor(
public readonly repoPath: string,
public readonly path: string,
public readonly status: GitFileStatus,
public readonly originalPath?: string | undefined,
public readonly previousSha?: string | undefined,
) {}
@memoize()
get uri(): Uri {
return Container.instance.git.getAbsoluteUri(this.path, this.repoPath);
}
@memoize()
get originalUri(): Uri | undefined {
return this.originalPath ? Container.instance.git.getAbsoluteUri(this.originalPath, this.repoPath) : undefined;
}
@memoize()
get previousUri(): Uri {
return Container.instance.git.getAbsoluteUri(this.originalPath || this.path, this.repoPath);
}
@memoize()
getWorkingUri(): Promise<Uri | undefined> {
return Container.instance.git.getWorkingUri(this.repoPath, this.uri);
}
}
const stashNumberRegex = /stash@{(\d+)}/;
export class GitCommit2 implements GitRevisionReference {
static is(commit: any): commit is GitCommit2 {
return commit instanceof GitCommit2;
}
static hasFullDetails(commit: GitCommit2): commit is GitCommit2 & SomeNonNullable<GitCommit2, 'message' | 'files'> {
return commit.message != null && commit.files != null && commit.parents.length !== 0;
}
static isOfRefType(commit: GitReference | undefined) {
return commit?.refType === 'revision' || commit?.refType === 'stash';
}
readonly lines: GitCommitLine[];
readonly ref: string;
readonly refType: GitRevisionReference['refType'];
readonly shortSha: string;
readonly stashName: string | undefined;
readonly stashNumber: number | undefined;
constructor(
public readonly repoPath: string,
public readonly sha: string,
public readonly author: GitCommitIdentity,
public readonly committer: GitCommitIdentity,
public readonly summary: string,
public readonly parents: string[],
message?: string | undefined,
files?: GitFileChange | GitFileChange[] | undefined,
lines?: GitCommitLine | GitCommitLine[] | undefined,
stashName?: string | undefined,
) {
this.ref = this.sha;
this.refType = 'revision';
this.shortSha = this.sha.substring(0, CommitShaFormatting.length);
if (message != null) {
this._message = message;
}
if (files != null) {
if (Array.isArray(files)) {
this._files = files;
} else {
this._file = files;
}
}
if (lines != null) {
if (Array.isArray(lines)) {
this.lines = lines;
} else {
this.lines = [lines];
}
} else {
this.lines = [];
}
if (stashName) {
this.stashName = stashName || undefined;
this.stashNumber = Number(stashNumberRegex.exec(stashName)?.[1]);
}
}
get date(): Date {
return CommitDateFormatting.dateSource === DateSource.Committed ? this.committer.date : this.author.date;
}
private _file: GitFileChange | undefined;
get file(): GitFileChange | undefined {
return this._file;
}
private _files: GitFileChange[] | undefined;
get files(): GitFileChange[] | undefined {
return this._files;
}
get formattedDate(): string {
return CommitDateFormatting.dateStyle === DateStyle.Absolute
? this.formatDate(CommitDateFormatting.dateFormat)
: this.formatDateFromNow();
}
get hasConflicts(): boolean | undefined {
return undefined;
// return this._files?.some(f => f.conflictStatus != null);
}
private _message: string | undefined;
get message(): string | undefined {
return this._message;
}
get name() {
return this.stashName ? this.stashName : this.shortSha;
}
@memoize()
get isUncommitted(): boolean {
return GitRevision.isUncommitted(this.sha);
}
@memoize()
get isUncommittedStaged(): boolean {
return GitRevision.isUncommittedStaged(this.sha);
}
/** @deprecated use `file.uri` */
get uri(): Uri /*| undefined*/ {
return this.file?.uri ?? Container.instance.git.getAbsoluteUri(this.repoPath, this.repoPath);
}
/** @deprecated use `file.originalUri` */
get originalUri(): Uri | undefined {
return this.file?.originalUri;
}
/** @deprecated use `file.getWorkingUri` */
getWorkingUri(): Promise<Uri | undefined> {
return Promise.resolve(this.file?.getWorkingUri());
}
/** @deprecated use `file.previousUri` */
get previousUri(): Uri /*| undefined*/ {
return this.file?.previousUri ?? Container.instance.git.getAbsoluteUri(this.repoPath, this.repoPath);
}
/** @deprecated use `file.previousSha` */
get previousSha(): string | undefined {
return this.file?.previousSha;
}
async ensureFullDetails(): Promise<void> {
if (this.isUncommitted || GitCommit2.hasFullDetails(this)) return;
const commit = await Container.instance.git.getCommit(this.repoPath, this.sha);
if (commit == null) return;
this.parents.push(...(commit.parentShas ?? []));
this._message = commit.message;
this._files = commit.files.map(f => new GitFileChange(this.repoPath, f.fileName, f.status, f.originalFileName));
}
formatDate(format?: string | null) {
return CommitDateFormatting.dateSource === DateSource.Committed
? this.committer.formatDate(format)
: this.author.formatDate(format);
}
formatDateFromNow(short?: boolean) {
return CommitDateFormatting.dateSource === DateSource.Committed
? this.committer.fromNow(short)
: this.author.fromNow(short);
}
// TODO@eamodio deal with memoization, since we don't want the timeout to apply
@memoize()
async getAssociatedPullRequest(options?: { timeout?: number }): Promise<PullRequest | undefined> {
const remote = await Container.instance.git.getRichRemoteProvider(this.repoPath);
if (remote?.provider == null) return undefined;
return Container.instance.git.getPullRequestForCommit(this.ref, remote, options);
}
getAvatarUri(options?: { defaultStyle?: GravatarDefaultStyle; size?: number }): Uri | Promise<Uri> {
return this.author.getAvatarUri(this, options);
}
@memoize<GitCommit['getPreviousLineDiffUris']>((u, e, r) => `${u.toString()}|${e}|${r ?? ''}`)
getPreviousLineDiffUris(uri: Uri, editorLine: number, ref: string | undefined) {
return this.file?.path
? Container.instance.git.getPreviousLineDiffUris(this.repoPath, uri, editorLine, ref)
: Promise.resolve(undefined);
}
@memoize()
toGitUri(previous: boolean = false): GitUri {
return GitUri.fromCommit(this, previous);
}
with(changes: {
sha?: string;
parents?: string[];
files?: GitFileChange | GitFileChange[] | null;
lines?: GitCommitLine[];
}): GitCommit2 {
return new GitCommit2(
this.repoPath,
changes.sha ?? this.sha,
this.author,
this.committer,
this.summary,
this.getChangedValue(changes.parents, this.parents) ?? [],
this.message,
this.getChangedValue(changes.files, this.files),
this.getChangedValue(changes.lines, this.lines),
this.stashName,
);
}
protected getChangedValue<T>(change: T | null | undefined, original: T | undefined): T | undefined {
if (change === undefined) return original;
return change !== null ? change : undefined;
}
}
export abstract class GitCommit implements GitRevisionReference { export abstract class GitCommit implements GitRevisionReference {
get file() {
return this.fileName
? new GitFileChange(this.repoPath, this.fileName, GitFileIndexStatus.Modified, this.originalFileName)
: undefined;
}
get parents(): string[] {
return this.previousSha ? [this.previousSha] : [];
}
get summary(): string {
return this.message.split('\n', 1)[0];
}
get author(): GitCommitIdentity {
return new GitCommitIdentity(this.authorName, this.authorEmail, this.authorDate);
}
get committer(): GitCommitIdentity {
return new GitCommitIdentity('', '', this.committerDate);
}
static is(commit: any): commit is GitCommit { static is(commit: any): commit is GitCommit {
return commit instanceof GitCommit; return commit instanceof GitCommit;
} }
@ -56,8 +364,8 @@ export abstract class GitCommit implements GitRevisionReference {
public readonly type: GitCommitType, public readonly type: GitCommitType,
public readonly repoPath: string, public readonly repoPath: string,
public readonly sha: string, public readonly sha: string,
public readonly author: string,
public readonly email: string | undefined,
public readonly authorName: string,
public readonly authorEmail: string | undefined,
public readonly authorDate: Date, public readonly authorDate: Date,
public readonly committerDate: Date, public readonly committerDate: Date,
public readonly message: string, public readonly message: string,
@ -209,7 +517,7 @@ export abstract class GitCommit implements GitRevisionReference {
} }
getAvatarUri(options?: { defaultStyle?: GravatarDefaultStyle; size?: number }): Uri | Promise<Uri> { getAvatarUri(options?: { defaultStyle?: GravatarDefaultStyle; size?: number }): Uri | Promise<Uri> {
return getAvatarUri(this.email, this, options);
return getAvatarUri(this.authorEmail, this, options);
} }
@memoize() @memoize()

+ 1
- 1
src/git/models/file.ts View File

@ -32,7 +32,7 @@ export const enum GitFileWorkingTreeStatus {
} }
export interface GitFile { export interface GitFile {
status: GitFileConflictStatus | GitFileIndexStatus | GitFileWorkingTreeStatus;
status: GitFileStatus;
readonly repoPath?: string; readonly repoPath?: string;
readonly conflictStatus?: GitFileConflictStatus; readonly conflictStatus?: GitFileConflictStatus;
readonly indexStatus?: GitFileIndexStatus; readonly indexStatus?: GitFileIndexStatus;

+ 24
- 6
src/git/models/logCommit.ts View File

@ -3,8 +3,8 @@ import { Container } from '../../container';
import { memoize, Strings } from '../../system'; import { memoize, Strings } from '../../system';
import { GitUri } from '../gitUri'; import { GitUri } from '../gitUri';
import { GitReference } from '../models'; import { GitReference } from '../models';
import { GitCommit, GitCommitType } from './commit';
import { GitFile, GitFileStatus } from './file';
import { GitCommit, GitCommitType, GitFileChange } from './commit';
import { GitFile, GitFileIndexStatus, GitFileStatus } from './file';
const emptyStats = Object.freeze({ const emptyStats = Object.freeze({
added: 0, added: 0,
@ -24,6 +24,10 @@ export interface GitLogCommitLine {
} }
export class GitLogCommit extends GitCommit { export class GitLogCommit extends GitCommit {
override get parents(): string[] {
return this.parentShas != null ? this.parentShas : [];
}
static override isOfRefType(commit: GitReference | undefined) { static override isOfRefType(commit: GitReference | undefined) {
return commit?.refType === 'revision'; return commit?.refType === 'revision';
} }
@ -39,6 +43,7 @@ export class GitLogCommit extends GitCommit {
nextSha?: string; nextSha?: string;
nextFileName?: string; nextFileName?: string;
readonly lines: GitLogCommitLine[];
constructor( constructor(
type: GitCommitType, type: GitCommitType,
@ -62,7 +67,7 @@ export class GitLogCommit extends GitCommit {
} }
| undefined, | undefined,
public readonly parentShas?: string[], public readonly parentShas?: string[],
public readonly line?: GitLogCommitLine,
lines?: GitLogCommitLine[],
) { ) {
super( super(
type, type,
@ -78,6 +83,19 @@ export class GitLogCommit extends GitCommit {
previousSha ?? `${sha}^`, previousSha ?? `${sha}^`,
previousFileName, previousFileName,
); );
this.lines = lines ?? [];
}
override get file() {
return this.isFile
? new GitFileChange(
this.repoPath,
this.fileName,
this.status ?? GitFileIndexStatus.Modified,
this.originalFileName,
)
: undefined;
} }
@memoize() @memoize()
@ -222,8 +240,8 @@ export class GitLogCommit extends GitCommit {
changes.type ?? this.type, changes.type ?? this.type,
this.repoPath, this.repoPath,
this.getChangedValue(changes.sha, this.sha)!, this.getChangedValue(changes.sha, this.sha)!,
changes.author ?? this.author,
changes.email ?? this.email,
changes.author ?? this.authorName,
changes.email ?? this.authorEmail,
changes.authorDate ?? this.authorDate, changes.authorDate ?? this.authorDate,
changes.committedDate ?? this.committerDate, changes.committedDate ?? this.committerDate,
changes.message ?? this.message, changes.message ?? this.message,
@ -235,7 +253,7 @@ export class GitLogCommit extends GitCommit {
this.getChangedValue(changes.previousFileName, this.previousFileName), this.getChangedValue(changes.previousFileName, this.previousFileName),
this._fileStats, this._fileStats,
this.parentShas, this.parentShas,
this.line,
this.lines,
); );
} }
} }

+ 77
- 38
src/git/parsers/blameParser.ts View File

@ -1,9 +1,17 @@
import { debug, Strings } from '../../system';
import { debug } from '../../system/decorators/log';
import { normalizePath, relative } from '../../system/path'; import { normalizePath, relative } from '../../system/path';
import { GitAuthor, GitBlame, GitBlameCommit, GitCommitLine, GitRevision, GitUser } from '../models';
const emptyStr = '';
const slash = '/';
import { getLines } from '../../system/string';
import {
GitAuthor,
GitBlame,
GitCommit2,
GitCommitIdentity,
GitCommitLine,
GitFileChange,
GitFileIndexStatus,
GitRevision,
GitUser,
} from '../models';
interface BlameEntry { interface BlameEntry {
sha: string; sha: string;
@ -17,8 +25,10 @@ interface BlameEntry {
authorTimeZone?: string; authorTimeZone?: string;
authorEmail?: string; authorEmail?: string;
committer: string;
committerDate?: string; committerDate?: string;
committerTimeZone?: string; committerTimeZone?: string;
committerEmail?: string;
previousSha?: string; previousSha?: string;
previousFileName?: string; previousFileName?: string;
@ -39,7 +49,7 @@ export class GitBlameParser {
if (!data) return undefined; if (!data) return undefined;
const authors = new Map<string, GitAuthor>(); const authors = new Map<string, GitAuthor>();
const commits = new Map<string, GitBlameCommit>();
const commits = new Map<string, GitCommit2>();
const lines: GitCommitLine[] = []; const lines: GitCommitLine[] = [];
let relativeFileName; let relativeFileName;
@ -50,13 +60,14 @@ export class GitBlameParser {
let first = true; let first = true;
for (line of Strings.lines(data)) {
for (line of getLines(data)) {
lineParts = line.split(' '); lineParts = line.split(' ');
if (lineParts.length < 2) continue; if (lineParts.length < 2) continue;
if (entry === undefined) { if (entry === undefined) {
entry = { entry = {
author: undefined!, author: undefined!,
committer: undefined!,
sha: lineParts[0], sha: lineParts[0],
originalLine: parseInt(lineParts[1], 10), originalLine: parseInt(lineParts[1], 10),
line: parseInt(lineParts[2], 10), line: parseInt(lineParts[2], 10),
@ -102,6 +113,33 @@ export class GitBlameParser {
entry.authorTimeZone = lineParts[1]; entry.authorTimeZone = lineParts[1];
break; break;
case 'committer':
if (GitRevision.isUncommitted(entry.sha)) {
entry.committer = 'You';
} else {
entry.committer = lineParts.slice(1).join(' ').trim();
}
break;
case 'committer-mail': {
if (GitRevision.isUncommitted(entry.sha)) {
entry.committerEmail = currentUser !== undefined ? currentUser.email : undefined;
continue;
}
entry.committerEmail = lineParts.slice(1).join(' ').trim();
const start = entry.committerEmail.indexOf('<');
if (start >= 0) {
const end = entry.committerEmail.indexOf('>', start);
if (end > start) {
entry.committerEmail = entry.committerEmail.substring(start + 1, end);
} else {
entry.committerEmail = entry.committerEmail.substring(start + 1);
}
}
break;
}
case 'committer-time': case 'committer-time':
entry.committerDate = lineParts[1]; entry.committerDate = lineParts[1];
break; break;
@ -125,10 +163,7 @@ export class GitBlameParser {
if (first && repoPath === undefined) { if (first && repoPath === undefined) {
// Try to get the repoPath from the most recent commit // Try to get the repoPath from the most recent commit
repoPath = normalizePath( repoPath = normalizePath(
fileName.replace(
fileName.startsWith(slash) ? `/${entry.fileName}` : entry.fileName,
emptyStr,
),
fileName.replace(fileName.startsWith('/') ? `/${entry.fileName}` : entry.fileName, ''),
); );
relativeFileName = normalizePath(relative(repoPath, fileName)); relativeFileName = normalizePath(relative(repoPath, fileName));
} else { } else {
@ -147,10 +182,10 @@ export class GitBlameParser {
} }
for (const [, c] of commits) { for (const [, c] of commits) {
if (c.author === undefined) return undefined;
if (!c.author.name) continue;
const author = authors.get(c.author);
if (author === undefined) return undefined;
const author = authors.get(c.author.name);
if (author == undefined) return undefined;
author.lineCount += c.lines.length; author.lineCount += c.lines.length;
} }
@ -170,28 +205,28 @@ export class GitBlameParser {
entry: BlameEntry, entry: BlameEntry,
repoPath: string | undefined, repoPath: string | undefined,
relativeFileName: string, relativeFileName: string,
commits: Map<string, GitBlameCommit>,
commits: Map<string, GitCommit2>,
authors: Map<string, GitAuthor>, authors: Map<string, GitAuthor>,
lines: GitCommitLine[], lines: GitCommitLine[],
currentUser: { name?: string; email?: string } | undefined, currentUser: { name?: string; email?: string } | undefined,
) { ) {
let commit = commits.get(entry.sha); let commit = commits.get(entry.sha);
if (commit === undefined) {
if (entry.author !== undefined) {
if (commit == null) {
if (entry.author != null) {
if ( if (
currentUser !== undefined &&
currentUser != null &&
// Name or e-mail is configured // Name or e-mail is configured
(currentUser.name !== undefined || currentUser.email !== undefined) &&
(currentUser.name != null || currentUser.email != null) &&
// Match on name if configured // Match on name if configured
(currentUser.name === undefined || currentUser.name === entry.author) &&
(currentUser.name == null || currentUser.name === entry.author) &&
// Match on email if configured // Match on email if configured
(currentUser.email === undefined || currentUser.email === entry.authorEmail)
(currentUser.email == null || currentUser.email === entry.authorEmail)
) { ) {
entry.author = 'You'; entry.author = 'You';
} }
let author = authors.get(entry.author); let author = authors.get(entry.author);
if (author === undefined) {
if (author == null) {
author = { author = {
name: entry.author, name: entry.author,
lineCount: 0, lineCount: 0,
@ -200,20 +235,27 @@ export class GitBlameParser {
} }
} }
commit = new GitBlameCommit(
commit = new GitCommit2(
repoPath!, repoPath!,
entry.sha, entry.sha,
entry.author,
entry.authorEmail,
new Date((entry.authorDate as any) * 1000),
new Date((entry.committerDate as any) * 1000),
new GitCommitIdentity(entry.author, entry.authorEmail, new Date((entry.authorDate as any) * 1000)),
new GitCommitIdentity(
entry.committer,
entry.committerEmail,
new Date((entry.committerDate as any) * 1000),
),
entry.summary!, entry.summary!,
relativeFileName,
entry.previousFileName !== undefined && entry.previousFileName !== entry.fileName
? entry.previousFileName
: undefined,
entry.previousSha,
entry.previousSha && entry.previousFileName,
[],
undefined,
new GitFileChange(
repoPath!,
relativeFileName,
GitFileIndexStatus.Modified,
entry.previousFileName && entry.previousFileName !== entry.fileName
? entry.previousFileName
: undefined,
entry.previousSha,
),
[], [],
); );
@ -225,13 +267,10 @@ export class GitBlameParser {
sha: entry.sha, sha: entry.sha,
line: entry.line + i, line: entry.line + i,
originalLine: entry.originalLine + i, originalLine: entry.originalLine + i,
previousSha: commit.file?.previousSha,
}; };
if (commit.previousSha) {
line.previousSha = commit.previousSha;
}
commit.lines.push(line);
commit.lines?.push(line);
lines[line.line - 1] = line; lines[line.line - 1] = line;
} }
} }

+ 1
- 1
src/git/parsers/diffParser.ts View File

@ -81,7 +81,7 @@ export class GitDiffParser {
let hasRemoved; let hasRemoved;
let removed = 0; let removed = 0;
for (const l of Strings.lines(hunk.diff)) {
for (const l of Strings.getLines(hunk.diff)) {
switch (l[0]) { switch (l[0]) {
case '+': case '+':
hasAddedOrChanged = true; hasAddedOrChanged = true;

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

@ -1,6 +1,7 @@
import { Range } from 'vscode'; import { Range } from 'vscode';
import { Arrays, debug, Strings } from '../../system';
import { Arrays, debug } from '../../system';
import { normalizePath, relative } from '../../system/path'; import { normalizePath, relative } from '../../system/path';
import { getLines } from '../../system/string';
import { import {
GitAuthor, GitAuthor,
GitCommitType, GitCommitType,
@ -105,7 +106,7 @@ export class GitLogParser {
let i = 0; let i = 0;
let first = true; let first = true;
const lines = Strings.lines(`${data}</f>`);
const lines = getLines(`${data}</f>`);
// Skip the first line since it will always be </f> // Skip the first line since it will always be </f>
let next = lines.next(); let next = lines.next();
if (next.done) return undefined; if (next.done) return undefined;
@ -455,7 +456,7 @@ export class GitLogParser {
undefined, undefined,
entry.fileStats, entry.fileStats,
entry.parentShas, entry.parentShas,
entry.line,
entry.line != null ? [entry.line] : [],
); );
commits.set(entry.ref!, commit); commits.set(entry.ref!, commit);

+ 1
- 1
src/git/parsers/stashParser.ts View File

@ -40,7 +40,7 @@ export class GitStashParser {
static parse(data: string, repoPath: string): GitStash | undefined { static parse(data: string, repoPath: string): GitStash | undefined {
if (!data) return undefined; if (!data) return undefined;
const lines = Strings.lines(`${data}</f>`);
const lines = Strings.getLines(`${data}</f>`);
// Skip the first line since it will always be </f> // Skip the first line since it will always be </f>
let next = lines.next(); let next = lines.next();
if (next.done) return undefined; if (next.done) return undefined;

+ 41
- 34
src/hovers/hovers.ts View File

@ -6,8 +6,7 @@ import { Container } from '../container';
import { CommitFormatter } from '../git/formatters'; import { CommitFormatter } from '../git/formatters';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { import {
GitBlameCommit,
GitCommit,
GitCommit2,
GitDiffHunk, GitDiffHunk,
GitDiffHunkLine, GitDiffHunkLine,
GitLogCommit, GitLogCommit,
@ -16,12 +15,13 @@ import {
PullRequest, PullRequest,
} from '../git/models'; } from '../git/models';
import { Logger, LogLevel } from '../logger'; import { Logger, LogLevel } from '../logger';
import { Iterables, Strings } from '../system';
import { count } from '../system/iterable';
import { PromiseCancelledError } from '../system/promise'; import { PromiseCancelledError } from '../system/promise';
import { getDurationMilliseconds } from '../system/string';
export namespace Hovers { export namespace Hovers {
export async function changesMessage( export async function changesMessage(
commit: GitBlameCommit | GitLogCommit,
commit: GitCommit2,
uri: GitUri, uri: GitUri,
editorLine: number, editorLine: number,
document: TextDocument, document: TextDocument,
@ -29,7 +29,7 @@ export namespace Hovers {
const documentRef = uri.sha; const documentRef = uri.sha;
async function getDiff() { async function getDiff() {
if (!GitBlameCommit.is(commit)) return undefined;
if (commit.file == null) return undefined;
// TODO: Figure out how to optimize this // TODO: Figure out how to optimize this
let ref; let ref;
@ -38,7 +38,7 @@ export namespace Hovers {
ref = documentRef; ref = documentRef;
} }
} else { } else {
ref = commit.previousSha;
ref = commit.file.previousSha;
if (ref == null) { if (ref == null) {
return `\`\`\`diff\n+ ${document.lineAt(editorLine).text}\n\`\`\``; return `\`\`\`diff\n+ ${document.lineAt(editorLine).text}\n\`\`\``;
} }
@ -47,10 +47,10 @@ export namespace Hovers {
const line = editorLine + 1; const line = editorLine + 1;
const commitLine = commit.lines.find(l => l.line === line) ?? commit.lines[0]; const commitLine = commit.lines.find(l => l.line === line) ?? commit.lines[0];
let originalFileName = commit.originalFileName;
if (originalFileName == null) {
if (uri.fsPath !== commit.uri.fsPath) {
originalFileName = commit.fileName;
let originalPath = commit.file.originalPath;
if (originalPath == null) {
if (uri.fsPath !== commit.file.uri.fsPath) {
originalPath = commit.file.path;
} }
} }
@ -96,9 +96,7 @@ export namespace Hovers {
previous = previous =
diffUris.previous.sha == null || diffUris.previous.isUncommitted diffUris.previous.sha == null || diffUris.previous.isUncommitted
? ` &nbsp;_${GitRevision.shorten(diffUris.previous.sha, { ? ` &nbsp;_${GitRevision.shorten(diffUris.previous.sha, {
strings: {
working: 'Working Tree',
},
strings: { working: 'Working Tree' },
})}_ &nbsp;${GlyphChars.ArrowLeftRightLong}&nbsp; ` })}_ &nbsp;${GlyphChars.ArrowLeftRightLong}&nbsp; `
: ` &nbsp;[$(git-commit) ${GitRevision.shorten( : ` &nbsp;[$(git-commit) ${GitRevision.shorten(
diffUris.previous.sha || '', diffUris.previous.sha || '',
@ -122,10 +120,11 @@ export namespace Hovers {
editorLine, editorLine,
)} "Open Changes")`; )} "Open Changes")`;
if (commit.previousSha) {
previous = ` &nbsp;[$(git-commit) ${
commit.previousShortSha
}](${ShowQuickCommitCommand.getMarkdownCommandArgs(commit.previousSha)} "Show Commit") &nbsp;${
const previousSha = commit.file?.previousSha;
if (previousSha) {
previous = ` &nbsp;[$(git-commit) ${GitRevision.shorten(
previousSha,
)}](${ShowQuickCommitCommand.getMarkdownCommandArgs(previousSha)} "Show Commit") &nbsp;${
GlyphChars.ArrowLeftRightLong GlyphChars.ArrowLeftRightLong
}&nbsp;`; }&nbsp;`;
} }
@ -190,7 +189,7 @@ export namespace Hovers {
} }
export async function detailsMessage( export async function detailsMessage(
commit: GitCommit,
commit: GitCommit2,
uri: GitUri, uri: GitUri,
editorLine: number, editorLine: number,
format: string, format: string,
@ -212,13 +211,21 @@ export namespace Hovers {
dateFormat = 'MMMM Do, YYYY h:mma'; dateFormat = 'MMMM Do, YYYY h:mma';
} }
let message = commit.message ?? commit.summary;
if (commit.message == null && !commit.isUncommitted) {
await commit.ensureFullDetails();
message = commit.message ?? commit.summary;
if (options?.cancellationToken?.isCancellationRequested) return new MarkdownString();
}
const remotes = await Container.instance.git.getRemotesWithProviders(commit.repoPath, { sort: true }); const remotes = await Container.instance.git.getRemotesWithProviders(commit.repoPath, { sort: true });
if (options?.cancellationToken?.isCancellationRequested) return new MarkdownString(); if (options?.cancellationToken?.isCancellationRequested) return new MarkdownString();
const [previousLineDiffUris, autolinkedIssuesOrPullRequests, pr, presence] = await Promise.all([ const [previousLineDiffUris, autolinkedIssuesOrPullRequests, pr, presence] = await Promise.all([
commit.isUncommitted ? commit.getPreviousLineDiffUris(uri, editorLine, uri.sha) : undefined, commit.isUncommitted ? commit.getPreviousLineDiffUris(uri, editorLine, uri.sha) : undefined,
getAutoLinkedIssuesOrPullRequests(commit.message, remotes),
getAutoLinkedIssuesOrPullRequests(message, remotes),
options?.pullRequests?.pr ?? options?.pullRequests?.pr ??
getPullRequestForCommit(commit.ref, remotes, { getPullRequestForCommit(commit.ref, remotes, {
pullRequests: pullRequests:
@ -232,7 +239,7 @@ export namespace Hovers {
'pullRequestState', 'pullRequestState',
), ),
}), }),
Container.instance.vsls.maybeGetPresence(commit.email),
Container.instance.vsls.maybeGetPresence(commit.author.email),
]); ]);
if (options?.cancellationToken?.isCancellationRequested) return new MarkdownString(); if (options?.cancellationToken?.isCancellationRequested) return new MarkdownString();
@ -284,14 +291,14 @@ export namespace Hovers {
!Container.instance.config.hovers.autolinks.enhanced || !Container.instance.config.hovers.autolinks.enhanced ||
!CommitFormatter.has(Container.instance.config.hovers.detailsMarkdownFormat, 'message') !CommitFormatter.has(Container.instance.config.hovers.detailsMarkdownFormat, 'message')
) { ) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.debug(cc, `completed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return undefined; return undefined;
} }
const remote = await Container.instance.git.getRichRemoteProvider(remotes); const remote = await Container.instance.git.getRichRemoteProvider(remotes);
if (remote?.provider == null) { if (remote?.provider == null) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.debug(cc, `completed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return undefined; return undefined;
} }
@ -306,15 +313,15 @@ export namespace Hovers {
if (autolinks != null && Logger.enabled(LogLevel.Debug)) { if (autolinks != null && Logger.enabled(LogLevel.Debug)) {
// If there are any issues/PRs that timed out, log it // If there are any issues/PRs that timed out, log it
const count = Iterables.count(autolinks.values(), pr => pr instanceof PromiseCancelledError);
if (count !== 0) {
const prCount = count(autolinks.values(), pr => pr instanceof PromiseCancelledError);
if (prCount !== 0) {
Logger.debug( Logger.debug(
cc, cc,
`timed out ${ `timed out ${
GlyphChars.Dash GlyphChars.Dash
} ${count} issue/pull request queries took too long (over ${timeout} ms) ${
} ${prCount} issue/pull request queries took too long (over ${timeout} ms) ${
GlyphChars.Dot GlyphChars.Dot
} ${Strings.getDurationMilliseconds(start)} ms`,
} ${getDurationMilliseconds(start)} ms`,
); );
// const pending = [ // const pending = [
@ -336,11 +343,11 @@ export namespace Hovers {
} }
} }
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.debug(cc, `completed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return autolinks; return autolinks;
} catch (ex) { } catch (ex) {
Logger.error(ex, cc, `failed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.error(ex, cc, `failed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return undefined; return undefined;
} }
@ -359,14 +366,14 @@ export namespace Hovers {
const start = hrtime(); const start = hrtime();
if (!options?.pullRequests) { if (!options?.pullRequests) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.debug(cc, `completed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return undefined; return undefined;
} }
const remote = await Container.instance.git.getRichRemoteProvider(remotes, { includeDisconnected: true }); const remote = await Container.instance.git.getRichRemoteProvider(remotes, { includeDisconnected: true });
if (remote?.provider == null) { if (remote?.provider == null) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.debug(cc, `completed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return undefined; return undefined;
} }
@ -374,7 +381,7 @@ export namespace Hovers {
const { provider } = remote; const { provider } = remote;
const connected = provider.maybeConnected ?? (await provider.isConnected()); const connected = provider.maybeConnected ?? (await provider.isConnected());
if (!connected) { if (!connected) {
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.debug(cc, `completed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return remote; return remote;
} }
@ -382,17 +389,17 @@ export namespace Hovers {
try { try {
const pr = await Container.instance.git.getPullRequestForCommit(ref, provider, { timeout: 250 }); const pr = await Container.instance.git.getPullRequestForCommit(ref, provider, { timeout: 250 });
Logger.debug(cc, `completed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.debug(cc, `completed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return pr; return pr;
} catch (ex) { } catch (ex) {
if (ex instanceof PromiseCancelledError) { if (ex instanceof PromiseCancelledError) {
Logger.debug(cc, `timed out ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.debug(cc, `timed out ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return ex; return ex;
} }
Logger.error(ex, cc, `failed ${GlyphChars.Dot} ${Strings.getDurationMilliseconds(start)} ms`);
Logger.error(ex, cc, `failed ${GlyphChars.Dot} ${getDurationMilliseconds(start)} ms`);
return undefined; return undefined;
} }

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

@ -116,22 +116,22 @@ export class LineHoverController implements Disposable {
); );
if (!wholeLine && range.start.character !== position.character) return undefined; if (!wholeLine && range.start.character !== position.character) return undefined;
// Get the full commit message -- since blame only returns the summary
let logCommit = lineState?.logCommit;
if (logCommit == null && !commit.isUncommitted) {
logCommit = await this.container.git.getCommitForFile(commit.repoPath, commit.uri, {
ref: commit.sha,
});
if (logCommit != null) {
// Preserve the previous commit from the blame commit
logCommit.previousSha = commit.previousSha;
logCommit.previousFileName = commit.previousFileName;
if (lineState != null) {
lineState.logCommit = logCommit;
}
}
}
// // Get the full commit message -- since blame only returns the summary
// let logCommit = lineState?.logCommit;
// if (logCommit == null && !commit.isUncommitted) {
// logCommit = await this.container.git.getCommitForFile(commit.repoPath, commit.uri, {
// ref: commit.sha,
// });
// if (logCommit != null) {
// // Preserve the previous commit from the blame commit
// logCommit.previousSha = commit.previousSha;
// logCommit.previousFileName = commit.previousFileName;
// if (lineState != null) {
// lineState.logCommit = logCommit;
// }
// }
// }
let editorLine = position.line; let editorLine = position.line;
const line = editorLine + 1; const line = editorLine + 1;
@ -142,7 +142,7 @@ export class LineHoverController implements Disposable {
if (trackedDocument == null) return undefined; if (trackedDocument == null) return undefined;
const message = await Hovers.detailsMessage( const message = await Hovers.detailsMessage(
logCommit ?? commit,
commit,
trackedDocument.uri, trackedDocument.uri,
editorLine, editorLine,
this.container.config.hovers.detailsMarkdownFormat, this.container.config.hovers.detailsMarkdownFormat,

+ 5
- 3
src/messages.ts View File

@ -1,6 +1,6 @@
import { ConfigurationTarget, env, MessageItem, Uri, window } from 'vscode'; import { ConfigurationTarget, env, MessageItem, Uri, window } from 'vscode';
import { configuration } from './configuration'; import { configuration } from './configuration';
import { GitCommit } from './git/models';
import { GitCommit, GitCommit2 } from './git/models';
import { Logger } from './logger'; import { Logger } from './logger';
export const enum SuppressedMessages { export const enum SuppressedMessages {
@ -18,7 +18,9 @@ export const enum SuppressedMessages {
} }
export class Messages { export class Messages {
static showCommitHasNoPreviousCommitWarningMessage(commit?: GitCommit): Promise<MessageItem | undefined> {
static showCommitHasNoPreviousCommitWarningMessage(
commit?: GitCommit | GitCommit2,
): Promise<MessageItem | undefined> {
if (commit === undefined) { if (commit === undefined) {
return Messages.showMessage( return Messages.showMessage(
'info', 'info',
@ -28,7 +30,7 @@ export class Messages {
} }
return Messages.showMessage( return Messages.showMessage(
'info', 'info',
`Commit ${commit.shortSha} (${commit.author}, ${commit.formattedDate}) has no previous commit.`,
`Commit ${commit.shortSha} (${commit.author.name}, ${commit.formattedDate}) has no previous commit.`,
SuppressedMessages.CommitHasNoPreviousCommitWarning, SuppressedMessages.CommitHasNoPreviousCommitWarning,
); );
} }

+ 42
- 26
src/premium/github/github.ts View File

@ -524,7 +524,11 @@ export class GitHubApi {
email email
name name
} }
committer { date }
committer {
date
email
name
}
} }
} }
} }
@ -645,20 +649,24 @@ export class GitHubApi {
const result = rsp?.data; const result = rsp?.data;
if (result == null) return undefined; if (result == null) return undefined;
const { commit } = result;
return { return {
oid: result.sha, oid: result.sha,
parents: { nodes: result.parents.map(p => ({ oid: p.sha })) }, parents: { nodes: result.parents.map(p => ({ oid: p.sha })) },
message: result.commit.message,
message: commit.message,
additions: result.stats?.additions, additions: result.stats?.additions,
changedFiles: result.files?.length, changedFiles: result.files?.length,
deletions: result.stats?.deletions, deletions: result.stats?.deletions,
author: { author: {
date: result.commit.author?.date ?? result.commit.committer?.date ?? new Date().toString(),
email: result.commit.author?.email ?? undefined,
name: result.commit.author?.name ?? '',
avatarUrl: result.author?.avatar_url ?? undefined,
date: commit.author?.date ?? new Date().toString(),
email: commit.author?.email ?? undefined,
name: commit.author?.name ?? '',
}, },
committer: { committer: {
date: result.commit.committer?.date ?? result.commit.author?.date ?? new Date().toString(),
date: commit.committer?.date ?? new Date().toString(),
email: commit.committer?.email ?? undefined,
name: commit.committer?.name ?? '',
}, },
files: result.files, files: result.files,
}; };
@ -720,11 +728,16 @@ export class GitHubApi {
changedFiles changedFiles
deletions deletions
author { author {
avatarUrl
date
email
name
}
committer {
date date
email email
name name
} }
committer { date }
} }
} }
} }
@ -783,23 +796,28 @@ export class GitHubApi {
endCursor endCursor
hasNextPage hasNextPage
} }
nodes { ... on Commit { ...commit } }
nodes {
... on Commit {
oid
message
parents(first: 3) { nodes { oid } }
author {
avatarUrl
date
email
name
}
committer {
date
email
name
}
}
}
} }
} }
} }
} }
}
fragment commit on Commit {
oid
message
parents(first: 100) { nodes { oid } }
author {
date
email
name
}
committer { date }
}`; }`;
const rsp = await this.graphql<QueryResult>(token, query, { const rsp = await this.graphql<QueryResult>(token, query, {
@ -1195,6 +1213,8 @@ export interface GitHubBlameRange {
}; };
committer: { committer: {
date: string; date: string;
email: string;
name: string;
}; };
}; };
} }
@ -1216,12 +1236,8 @@ export interface GitHubCommit {
additions: number | undefined; additions: number | undefined;
changedFiles: number | undefined; changedFiles: number | undefined;
deletions: number | undefined; deletions: number | undefined;
author: {
date: string;
email: string | undefined;
name: string;
};
committer: { date: string };
author: { avatarUrl: string | undefined; date: string; email: string | undefined; name: string };
committer: { date: string; email: string | undefined; name: string };
files?: Endpoints['GET /repos/{owner}/{repo}/commits/{ref}']['response']['data']['files']; files?: Endpoints['GET /repos/{owner}/{repo}/commits/{ref}']['response']['data']['files'];
} }

+ 27
- 25
src/premium/github/githubGitProvider.ts View File

@ -36,11 +36,12 @@ import {
BranchSortOptions, BranchSortOptions,
GitAuthor, GitAuthor,
GitBlame, GitBlame,
GitBlameCommit,
GitBlameLine, GitBlameLine,
GitBlameLines, GitBlameLines,
GitBranch, GitBranch,
GitBranchReference, GitBranchReference,
GitCommit2,
GitCommitIdentity,
GitCommitLine, GitCommitLine,
GitCommitType, GitCommitType,
GitContributor, GitContributor,
@ -49,6 +50,7 @@ import {
GitDiffHunkLine, GitDiffHunkLine,
GitDiffShortStat, GitDiffShortStat,
GitFile, GitFile,
GitFileChange,
GitFileIndexStatus, GitFileIndexStatus,
GitLog, GitLog,
GitLogCommit, GitLogCommit,
@ -376,7 +378,8 @@ export class GitHubGitProvider implements GitProvider, Disposable {
if (context == null) return undefined; if (context == null) return undefined;
const { metadata, github, remotehub, session } = context; const { metadata, github, remotehub, session } = context;
const file = this.getRelativePath(uri, remotehub.getProviderRootUri(uri));
const root = remotehub.getVirtualUri(remotehub.getProviderRootUri(uri));
const file = 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;
@ -391,17 +394,19 @@ export class GitHubGitProvider implements GitProvider, Disposable {
); );
const authors = new Map<string, GitAuthor>(); const authors = new Map<string, GitAuthor>();
const commits = new Map<string, GitBlameCommit>();
const commits = new Map<string, GitCommit2>();
const lines: GitCommitLine[] = []; const lines: GitCommitLine[] = [];
for (const range of blame.ranges) { for (const range of blame.ranges) {
const c = range.commit;
const { viewer = session.account.label } = blame; const { viewer = session.account.label } = blame;
const name = viewer != null && range.commit.author.name === viewer ? 'You' : range.commit.author.name;
const name = viewer != null && c.author.name === viewer ? 'You' : c.author.name;
let author = authors.get(rangeclass="p">.commit.author.name);
let author = authors.get(c.author.name);
if (author == null) { if (author == null) {
author = { author = {
name: range.commit.author.name,
name: c.author.name,
lineCount: 0, lineCount: 0,
}; };
authors.set(name, author); authors.set(name, author);
@ -409,29 +414,26 @@ export class GitHubGitProvider implements GitProvider, Disposable {
author.lineCount += range.endingLine - range.startingLine + 1; author.lineCount += range.endingLine - range.startingLine + 1;
let commit = commits.get(rangeclass="p">.commit.oid);
let commit = commits.get(c.oid);
if (commit == null) { if (commit == null) {
commit = new GitBlameCommit(
commit = new GitCommit2(
uri.repoPath!, uri.repoPath!,
range.commit.oid,
author.name,
range.commit.author.email,
new Date(range.commit.author.date),
new Date(range.commit.committer.date),
range.commit.message,
file,
undefined,
range.commit.parents.nodes[0]?.oid,
undefined,
c.oid,
new GitCommitIdentity(author.name, c.author.email, new Date(c.author.date), c.author.avatarUrl),
new GitCommitIdentity(c.committer.name, 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(), file, GitFileIndexStatus.Modified),
[], [],
); );
commits.set(rangeclass="p">.commit.oid, commit);
commits.set(c.oid, commit);
} }
for (let i = range.startingLine; i <= range.endingLine; i++) { for (let i = range.startingLine; i <= range.endingLine; i++) {
const line: GitCommitLine = { const line: GitCommitLine = {
sha: range.commit.oid,
sha: c.oid,
line: i, line: i,
originalLine: i, originalLine: i,
}; };
@ -495,7 +497,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
const commit = blame.commits.get(blameLine.sha); const commit = blame.commits.get(blameLine.sha);
if (commit == null) return undefined; if (commit == null) return undefined;
const author = blame.authors.get(commit.author)!;
const author = blame.authors.get(commit.author.name)!;
return { return {
author: { ...author, lineCount: commit.lines.length }, author: { ...author, lineCount: commit.lines.length },
commit: commit, commit: commit,
@ -545,7 +547,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
const endLine = range.end.line + 1; const endLine = range.end.line + 1;
const authors = new Map<string, GitAuthor>(); const authors = new Map<string, GitAuthor>();
const commits = new Map<string, GitBlameCommit>();
const commits = new Map<string, GitCommit2>();
for (const c of blame.commits.values()) { for (const c of blame.commits.values()) {
if (!shas.has(c.sha)) continue; if (!shas.has(c.sha)) continue;
@ -554,10 +556,10 @@ export class GitHubGitProvider implements GitProvider, Disposable {
}); });
commits.set(c.sha, commit); commits.set(c.sha, commit);
let author = authors.get(commit.author);
let author = authors.get(commit.author.name);
if (author == null) { if (author == null) {
author = { author = {
name: commit.author,
name: commit.author.name,
lineCount: 0, lineCount: 0,
}; };
authors.set(author.name, author); authors.set(author.name, author);
@ -1234,7 +1236,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
return undefined; return undefined;
} }
authors.set(c.author, log.authors.get(c.author)!);
authors.set(c.author.name, log.authors.get(c.author.name)!);
return [ref, c]; return [ref, c];
}, },
), ),

+ 2
- 2
src/quickpicks/gitQuickPickItems.ts View File

@ -188,7 +188,7 @@ export namespace CommitQuickPickItem {
if (options.compact) { if (options.compact) {
const item: CommitQuickPickItem<T> = { const item: CommitQuickPickItem<T> = {
label: `${options.icon ? pad('$(git-commit)', 0, 2) : ''}${commit.getShortMessage()}`, label: `${options.icon ? pad('$(git-commit)', 0, 2) : ''}${commit.getShortMessage()}`,
description: `${commit.author}, ${commit.formattedDate}${pad('$(git-commit)', 2, 2)}${
description: `${commit.author.name}, ${commit.formattedDate}${pad('$(git-commit)', 2, 2)}${
commit.shortSha commit.shortSha
}${pad(GlyphChars.Dot, 2, 2)}${commit.getFormattedDiffStatus({ compact: true })}`, }${pad(GlyphChars.Dot, 2, 2)}${commit.getFormattedDiffStatus({ compact: true })}`,
alwaysShow: options.alwaysShow, alwaysShow: options.alwaysShow,
@ -202,7 +202,7 @@ export namespace CommitQuickPickItem {
const item: CommitQuickPickItem<T> = { const item: CommitQuickPickItem<T> = {
label: `${options.icon ? pad('$(git-commit)', 0, 2) : ''}${commit.getShortMessage()}`, label: `${options.icon ? pad('$(git-commit)', 0, 2) : ''}${commit.getShortMessage()}`,
description: '', description: '',
detail: `${GlyphChars.Space.repeat(2)}${commit.author}, ${commit.formattedDate}${pad(
detail: `${GlyphChars.Space.repeat(2)}${commit.author.name}, ${commit.formattedDate}${pad(
'$(git-commit)', '$(git-commit)',
2, 2,
2, 2,

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

@ -15,7 +15,7 @@ import { configuration, FileAnnotationType, StatusBarCommand } from '../configur
import { GlyphChars, isTextEditor } from '../constants'; import { GlyphChars, isTextEditor } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import { CommitFormatter } from '../git/formatters'; import { CommitFormatter } from '../git/formatters';
import { GitBlameCommit, PullRequest } from '../git/models';
import { GitCommit2, PullRequest } from '../git/models';
import { Hovers } from '../hovers/hovers'; import { Hovers } from '../hovers/hovers';
import { LogCorrelationContext, Logger } from '../logger'; import { LogCorrelationContext, Logger } from '../logger';
import { debug } from '../system/decorators/log'; import { debug } from '../system/decorators/log';
@ -172,7 +172,7 @@ export class StatusBarController implements Disposable {
} }
@debug({ args: false }) @debug({ args: false })
private async updateBlame(editor: TextEditor, commit: GitBlameCommit, options?: { pr?: PullRequest | null }) {
private async updateBlame(editor: TextEditor, commit: GitCommit2, options?: { pr?: PullRequest | null }) {
const cfg = this.container.config.statusBar; const cfg = this.container.config.statusBar;
if (!cfg.enabled || this._statusBarBlame == null || !isTextEditor(editor)) return; if (!cfg.enabled || this._statusBarBlame == null || !isTextEditor(editor)) return;
@ -332,7 +332,7 @@ export class StatusBarController implements Disposable {
} }
private async getPullRequest( private async getPullRequest(
commit: GitBlameCommit,
commit: GitCommit2,
{ timeout }: { timeout?: number } = {}, { timeout }: { timeout?: number } = {},
): Promise<PullRequest | PromiseCancelledError<Promise<PullRequest | undefined>> | undefined> { ): Promise<PullRequest | PromiseCancelledError<Promise<PullRequest | undefined>> | undefined> {
const remote = await this.container.git.getRichRemoteProvider(commit.repoPath); const remote = await this.container.git.getRichRemoteProvider(commit.repoPath);
@ -348,7 +348,7 @@ export class StatusBarController implements Disposable {
private async updateCommitTooltip( private async updateCommitTooltip(
statusBarItem: StatusBarItem, statusBarItem: StatusBarItem,
commit: GitBlameCommit,
commit: GitCommit2,
actionTooltip: string, actionTooltip: string,
getBranchAndTagTips: getBranchAndTagTips:
| (( | ((
@ -386,7 +386,7 @@ export class StatusBarController implements Disposable {
private async waitForPendingPullRequest( private async waitForPendingPullRequest(
editor: TextEditor, editor: TextEditor,
commit: GitBlameCommit,
commit: GitCommit2,
pr: PullRequest | PromiseCancelledError<Promise<PullRequest | undefined>> | undefined, pr: PullRequest | PromiseCancelledError<Promise<PullRequest | undefined>> | undefined,
cancellationToken: CancellationToken, cancellationToken: CancellationToken,
timeout: number, timeout: number,

+ 13
- 13
src/system/string.ts View File

@ -137,6 +137,19 @@ export function getDurationMilliseconds(start: [number, number]) {
return secs * 1000 + Math.floor(nanosecs / 1000000); return secs * 1000 + Math.floor(nanosecs / 1000000);
} }
export function* getLines(s: string, char: string = '\n'): IterableIterator<string> {
let i = 0;
while (i < s.length) {
let j = s.indexOf(char, i);
if (j === -1) {
j = s.length;
}
yield s.substring(i, j);
i = j + 1;
}
}
const superscripts = ['\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079']; const superscripts = ['\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079'];
export function getSuperscript(num: number) { export function getSuperscript(num: number) {
@ -249,19 +262,6 @@ export function isUpperAsciiLetter(code: number): boolean {
return code >= CharCode.A && code <= CharCode.Z; return code >= CharCode.A && code <= CharCode.Z;
} }
export function* lines(s: string, char: string = '\n'): IterableIterator<string> {
let i = 0;
while (i < s.length) {
let j = s.indexOf(char, i);
if (j === -1) {
j = s.length;
}
yield s.substring(i, j);
i = j + 1;
}
}
export function md5(s: string, encoding: 'base64' | 'hex' = 'base64'): string { export function md5(s: string, encoding: 'base64' | 'hex' = 'base64'): string {
return _md5(s, encoding); return _md5(s, encoding);
} }

+ 2
- 2
src/trackers/gitLineTracker.ts View File

@ -1,7 +1,7 @@
import { Disposable, TextEditor } from 'vscode'; import { Disposable, TextEditor } from 'vscode';
import { GlyphChars } from '../constants'; import { GlyphChars } from '../constants';
import { Container } from '../container'; import { Container } from '../container';
import { GitBlameCommit, GitLogCommit } from '../git/models';
import { GitCommit2, GitLogCommit } from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { debug } from '../system'; import { debug } from '../system';
import { import {
@ -16,7 +16,7 @@ import { LinesChangeEvent, LineSelection, LineTracker } from './lineTracker';
export * from './lineTracker'; export * from './lineTracker';
export class GitLineState { export class GitLineState {
constructor(public readonly commit: GitBlameCommit | undefined, public logCommit?: GitLogCommit) {}
constructor(public readonly commit: GitCommit2 | undefined, public logCommit?: GitLogCommit) {}
} }
export class GitLineTracker extends LineTracker<GitLineState> { export class GitLineTracker extends LineTracker<GitLineState> {

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

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

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

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

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

@ -109,9 +109,9 @@ export class LineHistoryNode
const file: GitFile = { const file: GitFile = {
conflictStatus: status?.conflictStatus, conflictStatus: status?.conflictStatus,
fileName: commit.fileName,
fileName: commit.file?.path ?? '',
indexStatus: status?.indexStatus, indexStatus: status?.indexStatus,
originalFileName: commit.originalFileName,
originalFileName: commit.file?.originalPath,
repoPath: this.uri.repoPath!, repoPath: this.uri.repoPath!,
status: status?.status ?? GitFileIndexStatus.Modified, status: status?.status ?? GitFileIndexStatus.Modified,
workingTreeStatus: status?.workingTreeStatus, workingTreeStatus: status?.workingTreeStatus,
@ -123,16 +123,16 @@ export class LineHistoryNode
this.uri.repoPath!, this.uri.repoPath!,
GitRevision.uncommittedStaged, GitRevision.uncommittedStaged,
'You', 'You',
commit.email,
commit.authorDate,
commit.committerDate,
commit.message,
commit.fileName,
commit.author.email,
commit.author.date,
commit.committer.date,
commit.message ?? commit.summary,
file.fileName,
[file], [file],
GitFileIndexStatus.Modified, GitFileIndexStatus.Modified,
commit.originalFileName,
file.originalFileName,
commit.previousSha, commit.previousSha,
commit.originalFileName ?? commit.fileName,
file.originalFileName ?? file.fileName,
); );
children.splice( children.splice(
@ -148,16 +148,16 @@ export class LineHistoryNode
this.uri.repoPath!, this.uri.repoPath!,
GitRevision.uncommitted, GitRevision.uncommitted,
'You', 'You',
commit.email,
commit.authorDate,
commit.committerDate,
commit.message,
commit.fileName,
commit.author.email,
commit.author.date,
commit.committer.date,
commit.message ?? commit.summary,
file.fileName,
[file], [file],
GitFileIndexStatus.Modified, GitFileIndexStatus.Modified,
commit.originalFileName,
file.originalFileName,
GitRevision.uncommittedStaged, GitRevision.uncommittedStaged,
commit.originalFileName ?? commit.fileName,
file.originalFileName ?? file.fileName,
); );
children.splice( children.splice(
@ -177,16 +177,16 @@ export class LineHistoryNode
? GitRevision.uncommittedStaged ? GitRevision.uncommittedStaged
: commit.sha, : commit.sha,
'You', 'You',
commit.email,
commit.authorDate,
commit.committerDate,
commit.message,
commit.fileName,
commit.author.email,
commit.author.date,
commit.committer.date,
commit.message ?? commit.summary,
file.fileName,
[file], [file],
GitFileIndexStatus.Modified, GitFileIndexStatus.Modified,
commit.originalFileName,
file.originalFileName,
commit.previousSha, commit.previousSha,
commit.originalFileName ?? commit.fileName,
file.originalFileName ?? file.fileName,
); );
children.splice( children.splice(

+ 12
- 10
src/webviews/rebaseEditor.ts View File

@ -537,19 +537,20 @@ async function parseRebaseTodo(
const ontoCommit = onto ? foundCommits.find(c => c.ref.startsWith(onto)) : undefined; const ontoCommit = onto ? foundCommits.find(c => c.ref.startsWith(onto)) : undefined;
if (ontoCommit != null) { if (ontoCommit != null) {
if (!authors.has(ontoCommit.author)) {
authors.set(ontoCommit.author, {
author: ontoCommit.author,
const { name, email } = ontoCommit.author;
if (!authors.has(name)) {
authors.set(name, {
author: name,
avatarUrl: ( avatarUrl: (
await ontoCommit.getAvatarUri({ defaultStyle: container.config.defaultGravatarsStyle }) await ontoCommit.getAvatarUri({ defaultStyle: container.config.defaultGravatarsStyle })
).toString(true), ).toString(true),
email: ontoCommit.email,
email: email,
}); });
} }
commits.push({ commits.push({
ref: ontoCommit.ref, ref: ontoCommit.ref,
author: ontoCommit.author,
author: name,
date: ontoCommit.formatDate(container.config.defaultDateFormat), date: ontoCommit.formatDate(container.config.defaultDateFormat),
dateFromNow: ontoCommit.formatDateFromNow(), dateFromNow: ontoCommit.formatDateFromNow(),
message: ontoCommit.message || 'root', message: ontoCommit.message || 'root',
@ -566,19 +567,20 @@ async function parseRebaseTodo(
onto = ''; onto = '';
} }
if (!authors.has(commit.author)) {
authors.set(commit.author, {
author: commit.author,
const { name, email } = commit.author;
if (!authors.has(name)) {
authors.set(name, {
author: name,
avatarUrl: ( avatarUrl: (
await commit.getAvatarUri({ defaultStyle: container.config.defaultGravatarsStyle }) await commit.getAvatarUri({ defaultStyle: container.config.defaultGravatarsStyle })
).toString(true), ).toString(true),
email: commit.email,
email: email,
}); });
} }
commits.push({ commits.push({
ref: commit.ref, ref: commit.ref,
author: commit.author,
author: name,
date: commit.formatDate(container.config.defaultDateFormat), date: commit.formatDate(container.config.defaultDateFormat),
dateFromNow: commit.formatDateFromNow(), dateFromNow: commit.formatDateFromNow(),
message: commit.message, message: commit.message,

+ 18
- 10
src/webviews/webviewBase.ts View File

@ -16,7 +16,14 @@ import { Commands } from '../commands';
import { configuration } from '../configuration'; import { configuration } from '../configuration';
import { Container } from '../container'; import { Container } from '../container';
import { CommitFormatter } from '../git/formatters'; import { CommitFormatter } from '../git/formatters';
import { GitBlameCommit, PullRequest, PullRequestState } from '../git/models';
import {
GitCommit2,
GitCommitIdentity,
GitFileChange,
GitFileIndexStatus,
PullRequest,
PullRequestState,
} from '../git/models';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { import {
DidChangeConfigurationNotificationType, DidChangeConfigurationNotificationType,
@ -195,18 +202,19 @@ export abstract class WebviewBase implements Disposable {
onIpcCommand(PreviewConfigurationCommandType, e, async params => { onIpcCommand(PreviewConfigurationCommandType, e, async params => {
switch (params.type) { switch (params.type) {
case 'commit': { case 'commit': {
const commit = new GitBlameCommit(
const commit = new GitCommit2(
'~/code/eamodio/vscode-gitlens-demo', '~/code/eamodio/vscode-gitlens-demo',
'fe26af408293cba5b4bfd77306e1ac9ff7ccaef8', 'fe26af408293cba5b4bfd77306e1ac9ff7ccaef8',
'You',
'eamodio@gmail.com',
new Date('2016-11-12T20:41:00.000Z'),
new Date('2020-11-01T06:57:21.000Z'),
new GitCommitIdentity('You', 'eamodio@gmail.com', new Date('2016-11-12T20:41:00.000Z')),
new GitCommitIdentity('You', 'eamodio@gmail.com', new Date('2020-11-01T06:57:21.000Z')),
'Supercharged',
['3ac1d3f51d7cf5f438cc69f25f6740536ad80fef'],
'Supercharged', 'Supercharged',
'code.ts',
undefined,
'3ac1d3f51d7cf5f438cc69f25f6740536ad80fef',
'code.ts',
new GitFileChange(
'~/code/eamodio/vscode-gitlens-demo',
'code.ts',
GitFileIndexStatus.Modified,
),
[], [],
); );

Loading…
Cancel
Save