From 643e2262df1f77249a249935713f6c374a4801c8 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Sun, 5 May 2019 12:12:25 -0400 Subject: [PATCH] Overhauls previous line diffing - closes #719 Changes & Details hovers now know about staged vs unstaged changes Significantly increased accuracy with the following: - Open Line Changes with Previous Revision - Open Changes with Previous Revision - Changes & Details hovers --- src/annotations/annotations.ts | 168 +++++++++++++------ src/annotations/lineAnnotationController.ts | 3 + src/annotations/recentChangesAnnotationProvider.ts | 2 +- src/commands/diffLineWithPrevious.ts | 60 +------ src/commands/externalDiff.ts | 2 +- src/git/formatters/commitFormatter.ts | 65 ++++++-- src/git/git.ts | 13 +- src/git/gitService.ts | 185 ++++++++++++++++++--- src/git/models/commit.ts | 54 +----- src/views/nodes/commitFileNode.ts | 2 +- src/views/nodes/statusFileNode.ts | 2 +- 11 files changed, 356 insertions(+), 200 deletions(-) diff --git a/src/annotations/annotations.ts b/src/annotations/annotations.ts index e22cc52..556987f 100644 --- a/src/annotations/annotations.ts +++ b/src/annotations/annotations.ts @@ -17,6 +17,8 @@ import { GitBlameCommit, GitCommit, GitDiffHunkLine, + GitLogCommit, + GitService, GitUri } from '../git/gitService'; import { Objects, Strings } from '../system'; @@ -62,74 +64,121 @@ export class Annotations { commit: GitBlameCommit, uri: GitUri, editorLine: number + ): Promise; + static async changesHoverMessage( + commit: GitLogCommit, + uri: GitUri, + editorLine: number, + hunkLine: GitDiffHunkLine + ): Promise; + static async changesHoverMessage( + commit: GitBlameCommit | GitLogCommit, + uri: GitUri, + editorLine: number, + hunkLine?: GitDiffHunkLine ): Promise { - let ref; - if (commit.isUncommitted) { - if (uri.isUncommittedStaged) { - ref = uri.sha; + const documentRef = uri.sha; + if (commit instanceof GitBlameCommit) { + // TODO: Figure out how to optimize this + let ref; + if (commit.isUncommitted) { + if (GitService.isUncommittedStaged(documentRef)) { + ref = documentRef; + } + } + else { + ref = commit.sha; } - } - else { - ref = commit.sha; - } - const line = editorLine + 1; - const commitLine = commit.lines.find(l => l.line === line) || commit.lines[0]; + const line = editorLine + 1; + const commitLine = commit.lines.find(l => l.line === line) || commit.lines[0]; - let originalFileName = commit.originalFileName; - if (originalFileName === undefined) { - if (uri.fsPath !== commit.uri.fsPath) { - originalFileName = commit.fileName; + let originalFileName = commit.originalFileName; + if (originalFileName === undefined) { + if (uri.fsPath !== commit.uri.fsPath) { + originalFileName = commit.fileName; + } } - } - const commitEditorLine = commitLine.originalLine - 1; - const hunkLine = await Container.git.getDiffForLine(uri, commitEditorLine, ref, undefined, originalFileName); - return this.changesHoverDiffMessage(commit, uri, hunkLine, commitEditorLine); - } + editorLine = commitLine.originalLine - 1; + hunkLine = await Container.git.getDiffForLine(uri, editorLine, ref, undefined, originalFileName); + + // If we didn't find a diff & ref is undefined (meaning uncommitted), check for a staged diff + if (hunkLine === undefined && ref === undefined) { + hunkLine = await Container.git.getDiffForLine( + uri, + editorLine, + undefined, + GitService.uncommittedStagedSha, + originalFileName + ); + } + } - static changesHoverDiffMessage( - commit: GitCommit, - uri: GitUri, - hunkLine: GitDiffHunkLine | undefined, - editorLine?: number - ): MarkdownString | undefined { if (hunkLine === undefined || commit.previousSha === undefined) return undefined; const diff = this.getDiffFromHunkLine(hunkLine); - let message: string; + let message; + let previous; + let current; if (commit.isUncommitted) { - if (uri.isUncommittedStaged) { - message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs( - commit, - editorLine - )} "Open Changes")   ${GlyphChars.Dash}   [\`${ - commit.previousShortSha - }\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( - commit.previousSha! - )} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} _${uri.shortSha}_\n${diff}`; - } - else { - message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs( - commit, - editorLine - )} "Open Changes")   ${GlyphChars.Dash}   _uncommitted changes_\n${diff}`; + const diffUris = await commit.getPreviousLineDiffUris(uri, editorLine, documentRef); + if (diffUris === undefined || diffUris.previous === undefined) { + return undefined; } + + message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs({ + lhs: { + sha: diffUris.previous.sha || '', + uri: diffUris.previous.documentUri() + }, + rhs: { + sha: diffUris.current.sha || '', + uri: diffUris.current.documentUri() + }, + repoPath: commit.repoPath, + line: editorLine + })} "Open Changes")`; + + previous = + diffUris.previous.sha === undefined || diffUris.previous.isUncommitted + ? `_${GitService.shortenSha(diffUris.previous.sha, { + working: 'Working Tree' + })}_` + : `[\`${GitService.shortenSha( + diffUris.previous.sha || '' + )}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( + diffUris.previous.sha || '' + )} "Show Commit Details")`; + + current = + diffUris.current.sha === undefined || diffUris.current.isUncommitted + ? `_${GitService.shortenSha(diffUris.current.sha, { + working: 'Working Tree' + })}_` + : `[\`${GitService.shortenSha( + diffUris.current.sha || '' + )}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( + diffUris.current.sha || '' + )} "Show Commit Details")`; } else { - message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs( - commit, - editorLine - )} "Open Changes")   ${GlyphChars.Dash}   [\`${ - commit.previousShortSha - }\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)} "Show Commit Details") ${ - GlyphChars.ArrowLeftRightLong - } [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( + message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit, editorLine)} "Open Changes")`; + + previous = `[\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( + commit.previousSha! + )} "Show Commit Details")`; + + current = `[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( commit.sha - )} "Show Commit Details")\n${diff}`; + )} "Show Commit Details")`; } + message += `   ${GlyphChars.Dash}   ${previous}  ${ + GlyphChars.ArrowLeftRightLong + }  ${current}\n${diff}`; + const markdown = new MarkdownString(message); markdown.isTrusted = true; return markdown; @@ -140,14 +189,15 @@ export class Annotations { uri: GitUri, editorLine: number, dateFormat: string | null, - annotationType?: FileAnnotationType + annotationType: FileAnnotationType | undefined ): Promise { if (dateFormat === null) { dateFormat = 'MMMM Do, YYYY h:mma'; } - const [presence, remotes] = await Promise.all([ + const [presence, previousLineDiffUris, remotes] = await Promise.all([ Container.vsls.getContactPresence(commit.email), + commit.isUncommitted ? commit.getPreviousLineDiffUris(uri, editorLine, uri.sha) : undefined, Container.git.getRemotes(commit.repoPath) ]); @@ -158,6 +208,7 @@ export class Annotations { line: editorLine, markdown: true, presence: presence, + previousLineDiffUris: previousLineDiffUris, remotes: remotes }) ); @@ -274,13 +325,22 @@ export class Annotations { static trailing( commit: GitCommit, + // uri: GitUri, + // editorLine: number, format: string, dateFormat: string | null, scrollable: boolean = true ): Partial { + // TODO: Enable this once there is better caching + // let diffUris; + // if (commit.isUncommitted) { + // diffUris = await commit.getPreviousLineDiffUris(uri, editorLine, uri.sha); + // } + const message = CommitFormatter.fromTemplate(format, commit, { - truncateMessageAtNewLine: true, - dateFormat: dateFormat + dateFormat: dateFormat, + // previousLineDiffUris: diffUris, + truncateMessageAtNewLine: true }); return { diff --git a/src/annotations/lineAnnotationController.ts b/src/annotations/lineAnnotationController.ts index 44d11e5..12c8de7 100644 --- a/src/annotations/lineAnnotationController.ts +++ b/src/annotations/lineAnnotationController.ts @@ -185,6 +185,8 @@ export class LineAnnotationController implements Disposable { const decoration = Annotations.trailing( state.commit, + // await GitUri.fromUri(editor.document.uri), + // l, cfg.format, cfg.dateFormat === null ? Container.config.defaultDateFormat : cfg.dateFormat, cfg.scrollable @@ -192,6 +194,7 @@ export class LineAnnotationController implements Disposable { decoration.range = editor.document.validateRange( new Range(l, Number.MAX_SAFE_INTEGER, l, Number.MAX_SAFE_INTEGER) ); + decorations.push(decoration); } diff --git a/src/annotations/recentChangesAnnotationProvider.ts b/src/annotations/recentChangesAnnotationProvider.ts index f179b96..e120564 100644 --- a/src/annotations/recentChangesAnnotationProvider.ts +++ b/src/annotations/recentChangesAnnotationProvider.ts @@ -73,7 +73,7 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase { } if (cfg.hovers.annotations.changes) { - message = Annotations.changesHoverDiffMessage(commit, this._uri, hunkLine, count); + message = await Annotations.changesHoverMessage(commit, this._uri, count, hunkLine); if (message === undefined) continue; } } diff --git a/src/commands/diffLineWithPrevious.ts b/src/commands/diffLineWithPrevious.ts index a9fe55c..36090ce 100644 --- a/src/commands/diffLineWithPrevious.ts +++ b/src/commands/diffLineWithPrevious.ts @@ -31,66 +31,12 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand { const gitUri = args.commit !== undefined ? GitUri.fromCommit(args.commit) : await GitUri.fromUri(uri); - if (gitUri.sha === undefined || gitUri.isUncommitted) { - const blame = - editor && editor.document.isDirty - ? await Container.git.getBlameForLineContents(gitUri, args.line, editor.document.getText()) - : await Container.git.getBlameForLine(gitUri, args.line); - if (blame === undefined) { - return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare'); - } - - // If the line is uncommitted, change the previous commit - if (blame.commit.isUncommitted) { - // Since there could be a change in the line number, update it - args.line = blame.line.originalLine - 1; - - try { - const previous = await Container.git.getPreviousUri( - gitUri.repoPath!, - gitUri, - gitUri.sha, - 0, - args.line - ); - - if (previous === undefined) { - return Messages.showCommitHasNoPreviousCommitWarningMessage(); - } - - const diffArgs: DiffWithCommandArgs = { - repoPath: gitUri.repoPath!, - lhs: { - sha: previous.sha || '', - uri: previous.documentUri() - }, - rhs: { - sha: gitUri.sha || '', - uri: gitUri.documentUri() - }, - line: args.line, - showOptions: args.showOptions - }; - return commands.executeCommand(Commands.DiffWith, diffArgs); - } - catch (ex) { - Logger.error( - ex, - 'DiffLineWithPreviousCommand', - `getPreviousUri(${gitUri.repoPath}, ${gitUri.fsPath}, ${gitUri.sha})` - ); - return Messages.showGenericErrorMessage('Unable to open compare'); - } - } - } - try { - const diffUris = await Container.git.getPreviousDiffUris( + const diffUris = await Container.git.getPreviousLineDiffUris( gitUri.repoPath!, gitUri, - gitUri.sha, - 0, - args.line + args.line, + gitUri.sha ); if (diffUris === undefined || diffUris.previous === undefined) { diff --git a/src/commands/externalDiff.ts b/src/commands/externalDiff.ts index 76b4c17..3411e50 100644 --- a/src/commands/externalDiff.ts +++ b/src/commands/externalDiff.ts @@ -77,7 +77,7 @@ export class ExternalDiffCommand extends Command { args.files = [ { uri: GitUri.fromFile(context.node.file, context.node.file.repoPath || context.node.repoPath), - staged: context.node.commit.isStagedUncommitted || context.node.file.indexStatus !== undefined, + staged: context.node.commit.isUncommittedStaged || context.node.file.indexStatus !== undefined, ref1: ref1, ref2: ref2 } diff --git a/src/git/formatters/commitFormatter.ts b/src/git/formatters/commitFormatter.ts index 0ed5250..ab7282c 100644 --- a/src/git/formatters/commitFormatter.ts +++ b/src/git/formatters/commitFormatter.ts @@ -10,10 +10,8 @@ import { import { DateStyle, FileAnnotationType } from '../../configuration'; import { GlyphChars } from '../../constants'; import { Container } from '../../container'; +import { GitCommit, GitCommitType, GitLogCommit, GitRemote, GitService, GitUri } from '../gitService'; import { Strings } from '../../system'; -import { GitUri } from '../gitUri'; -import { GitCommit, GitCommitType } from '../models/commit'; -import { GitLogCommit, GitRemote } from '../models/models'; import { FormatOptions, Formatter } from './formatter'; import * as emojis from '../../emojis.json'; import { ContactPresence } from '../../vsls/vsls'; @@ -32,6 +30,7 @@ export interface CommitFormatOptions extends FormatOptions { line?: number; markdown?: boolean; presence?: ContactPresence; + previousLineDiffUris?: { current: GitUri; previous: GitUri | undefined }; remotes?: GitRemote[]; truncateMessageAtNewLine?: boolean; @@ -164,15 +163,47 @@ export class CommitFormatter extends Formatter { } get commands() { + if (!this._options.markdown) return emptyStr; + + let commands; if (this._item.isUncommitted) { - return `\`${ - this._item.shortSha === 'Working Tree' - ? this._padOrTruncate('00000000', this._options.tokenOptions.id) - : this.id - }\``; + const { previousLineDiffUris: diffUris } = this._options; + if (diffUris !== undefined && diffUris.previous !== undefined) { + commands = `\`${this._padOrTruncate( + GitService.shortenSha( + GitService.isUncommittedStaged(diffUris.current.sha) + ? diffUris.current.sha + : GitService.uncommittedSha + )!, + this._options.tokenOptions.id + )}\``; + + commands += ` **[\`${GlyphChars.MuchLessThan}\`](${DiffWithCommand.getMarkdownCommandArgs({ + lhs: { + sha: diffUris.previous.sha || '', + uri: diffUris.previous.documentUri() + }, + rhs: { + sha: diffUris.current.sha || '', + uri: diffUris.current.documentUri() + }, + repoPath: this._item.repoPath, + line: this._options.line + })} "Open Changes")** `; + } + else { + commands = `\`${this._padOrTruncate( + GitService.shortenSha( + this._item.isUncommittedStaged ? GitService.uncommittedStagedSha : GitService.uncommittedSha + )!, + this._options.tokenOptions.id + )}\``; + } + + return commands; } - let commands = `[\`${this.id}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( + commands = `[\`${this.id}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs( this._item.sha )} "Show Commit Details") `; @@ -247,11 +278,17 @@ export class CommitFormatter extends Formatter { get message() { let message: string; - if (this._item.isStagedUncommitted) { - message = 'Staged changes'; - } - else if (this._item.isUncommitted) { - message = 'Uncommitted changes'; + if (this._item.isUncommitted) { + if ( + this._item.isUncommittedStaged || + (this._options.previousLineDiffUris !== undefined && + this._options.previousLineDiffUris.current.isUncommittedStaged) + ) { + message = 'Staged changes'; + } + else { + message = 'Uncommitted changes'; + } } else { if (this._options.truncateMessageAtNewLine) { diff --git a/src/git/git.ts b/src/git/git.ts index ce78a55..cdcbe0c 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -26,9 +26,10 @@ const slash = '/'; // This is a root sha of all git repo's if using sha1 const rootSha = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; -const GitErrors = { +export const GitErrors = { badRevision: /bad revision '(.*?)'/i, - notAValidObjectName: /Not a valid object name/i + notAValidObjectName: /Not a valid object name/i, + invalidLineCount: /file .+? has only \d+ lines/i }; const GitWarnings = { @@ -258,14 +259,14 @@ export class Git { static shortenSha( ref: string | undefined, { - stagedUncommitted = 'Index', uncommitted = 'Working Tree', + uncommittedStaged: uncommittedStaged = 'Index', working = emptyStr - }: { stagedUncommitted?: string; uncommitted?: string; working?: string } = {} + }: { uncommittedStaged?: string; uncommitted?: string; working?: string } = {} ) { if (ref == null || ref.length === 0) return working; if (Git.isUncommitted(ref)) { - return Git.isUncommittedStaged(ref) ? stagedUncommitted : uncommitted; + return Git.isUncommittedStaged(ref) ? uncommittedStaged : uncommitted; } if (!Git.isShaLike(ref)) return ref; @@ -749,7 +750,7 @@ export class Git { // static log__shortstat(repoPath: string, options: { ref?: string }) { // const params = ['log', '--shortstat', '--oneline']; - // if (options.ref && !Git.isStagedUncommitted(options.ref)) { + // if (options.ref && !Git.isUncommittedStaged(options.ref)) { // params.push(options.ref); // } // return git({ cwd: repoPath }, ...params, '--'); diff --git a/src/git/gitService.ts b/src/git/gitService.ts index 3c4d99e..e1bb810 100644 --- a/src/git/gitService.ts +++ b/src/git/gitService.ts @@ -45,6 +45,7 @@ import { GitDiffHunkLine, GitDiffParser, GitDiffShortStat, + GitErrors, GitFile, GitLog, GitLogCommit, @@ -1210,12 +1211,7 @@ export class GitService implements Disposable { originalFileName?: string ): Promise { try { - let diff = await this.getDiffForFile(uri, ref1, ref2, originalFileName); - // If we didn't find a diff & ref1 is undefined (meaning uncommitted), check for a staged diff - if (diff === undefined && ref1 === undefined) { - diff = await this.getDiffForFile(uri, Git.uncommittedStagedSha, ref2, originalFileName); - } - + const diff = await this.getDiffForFile(uri, ref1, ref2, originalFileName); if (diff === undefined) return undefined; const line = editorLine + 1; @@ -1727,34 +1723,155 @@ export class GitService implements Disposable { repoPath: string, uri: Uri, ref: string | undefined, - skip: number = 0, - editorLine?: number + skip: number = 0 ): Promise<{ current: GitUri; previous: GitUri | undefined } | undefined> { if (ref === GitService.deletedOrMissingSha) return undefined; const fileName = GitUri.relativeTo(uri, repoPath); - // If the ref is missing (i.e. working tree), check the file status to see if there is anything staged - if ((ref === undefined || ref.length === 0) && editorLine === undefined) { + // If we are at the working tree (i.e. no ref), we need to dig deeper to figure out where to go + if (ref === undefined || ref.length === 0) { + // First, check the file status to see if there is anything staged const status = await Container.git.getStatusForFile(repoPath, fileName); if (status !== undefined) { - // If the file is staged, diff with the staged version + // If the file is staged with working changes, diff working with staged (index) + // If the file is staged without working changes, diff staged with HEAD if (status.indexStatus !== undefined) { + // Backs up to get to HEAD + if (status.workingTreeStatus === undefined) { + skip++; + } + if (skip === 0) { + // Diff working with staged return { - current: GitUri.fromFile(fileName, repoPath, ref), + current: GitUri.fromFile(fileName, repoPath, undefined), previous: GitUri.fromFile(fileName, repoPath, GitService.uncommittedStagedSha) }; } return { + // Diff staged with HEAD (or prior if more skips) current: GitUri.fromFile(fileName, repoPath, GitService.uncommittedStagedSha), - previous: await this.getPreviousUri(repoPath, uri, ref, skip - 1, editorLine) + previous: await this.getPreviousUri(repoPath, uri, ref, skip - 1) }; } } } - else if (Git.isUncommittedStaged(ref)) { + // If we are at the index (staged), diff staged with HEAD + else if (GitService.isUncommittedStaged(ref)) { + const current = + skip === 0 + ? GitUri.fromFile(fileName, repoPath, ref) + : (await this.getPreviousUri(repoPath, uri, undefined, skip - 1))!; + if (current.sha === GitService.deletedOrMissingSha) return undefined; + + return { + current: current, + previous: await this.getPreviousUri(repoPath, uri, undefined, skip) + }; + } + + // If we are at a commit, diff commit with previous + const current = + skip === 0 + ? GitUri.fromFile(fileName, repoPath, ref) + : (await this.getPreviousUri(repoPath, uri, ref, skip - 1))!; + if (current.sha === GitService.deletedOrMissingSha) return undefined; + + return { + current: current, + previous: await this.getPreviousUri(repoPath, uri, ref, skip) + }; + } + + @log() + async getPreviousLineDiffUris( + repoPath: string, + uri: Uri, + editorLine: number, + ref: string | undefined, + skip: number = 0 + ): Promise<{ current: GitUri; previous: GitUri | undefined } | undefined> { + if (ref === GitService.deletedOrMissingSha) return undefined; + + let fileName = GitUri.relativeTo(uri, repoPath); + + // If we are at the working tree (i.e. no ref), we need to dig deeper to figure out where to go + if (ref === undefined || ref.length === 0) { + // First, check the blame on the current line to see if there are any working/staged changes + const gitUri = new GitUri(uri, repoPath); + + const document = await workspace.openTextDocument(uri); + const blameLine = document.isDirty + ? await this.getBlameForLineContents(gitUri, editorLine, document.getText()) + : await this.getBlameForLine(gitUri, editorLine); + if (blameLine === undefined) return undefined; + + // If line is uncommitted, we need to dig deeper to figure out where to go (because blame can't be trusted) + if (blameLine.commit.isUncommitted) { + // If the document is dirty (unsaved), use the status to determine where to go + if (document.isDirty) { + // Check the file status to see if there is anything staged + const status = await Container.git.getStatusForFile(repoPath, fileName); + if (status !== undefined) { + // If the file is staged, diff working with staged (index) + // If the file is not staged, diff working with HEAD + if (status.indexStatus !== undefined) { + // Diff working with staged + return { + current: GitUri.fromFile(fileName, repoPath, undefined), + previous: GitUri.fromFile(fileName, repoPath, GitService.uncommittedStagedSha) + }; + } + } + + // Diff working with HEAD (or prior if more skips) + return { + current: GitUri.fromFile(fileName, repoPath, undefined), + previous: await this.getPreviousUri(repoPath, uri, undefined, skip, editorLine) + }; + } + + // First, check if we have a diff in the working tree + let hunkLine = await this.getDiffForLine(gitUri!, editorLine, undefined); + if (hunkLine === undefined) { + // Next, check if we have a diff in the index (staged) + hunkLine = await this.getDiffForLine( + gitUri!, + editorLine, + undefined, + GitService.uncommittedStagedSha + ); + + if (hunkLine !== undefined) { + ref = GitService.uncommittedStagedSha; + } + else { + skip++; + } + } + } + // If line is committed, diff with line ref with previous + else { + ref = blameLine.commit.sha; + fileName = blameLine.commit.fileName || blameLine.commit.originalFileName || fileName; + uri = GitUri.resolveToUri(fileName, repoPath); + editorLine = blameLine.line.originalLine - 1; + } + + const current = + skip === 0 + ? GitUri.fromFile(fileName, repoPath, ref) + : (await this.getPreviousUri(repoPath, uri, ref, skip - 1, editorLine))!; + if (current.sha === GitService.deletedOrMissingSha) return undefined; + + return { + current: current, + previous: await this.getPreviousUri(repoPath, uri, ref, skip, editorLine) + }; + } + else if (GitService.isUncommittedStaged(ref)) { const current = skip === 0 ? GitUri.fromFile(fileName, repoPath, ref) @@ -1788,6 +1905,9 @@ export class GitService implements Disposable { editorLine?: number ): Promise { if (ref === GitService.deletedOrMissingSha) return undefined; + + const cc = Logger.getCorrelationContext(); + if (ref === GitService.uncommittedSha) { ref = undefined; } @@ -1797,11 +1917,35 @@ export class GitService implements Disposable { } const fileName = GitUri.relativeTo(uri, repoPath); - const data = await Git.log__file(repoPath, fileName, ref, { - format: GitLogParser.simpleFormat, - maxCount: skip + 1, - startLine: editorLine !== undefined ? editorLine + 1 : undefined - }); + // TODO: Add caching + let data; + try { + data = await Git.log__file(repoPath, fileName, ref, { + format: GitLogParser.simpleFormat, + maxCount: skip + 1, + startLine: editorLine !== undefined ? editorLine + 1 : undefined + }); + } + catch (ex) { + // If the line count is invalid just fallback to the most recent commit + if ( + (ref === undefined || GitService.isUncommittedStaged(ref)) && + GitErrors.invalidLineCount.test(ex.message) + ) { + if (ref === undefined) { + const status = await Container.git.getStatusForFile(repoPath, fileName); + if (status !== undefined && status.indexStatus !== undefined) { + return GitUri.fromFile(fileName, repoPath, GitService.uncommittedStagedSha); + } + } + + ref = await Git.log__file_recent(repoPath, fileName); + return GitUri.fromFile(fileName, repoPath, ref || GitService.deletedOrMissingSha); + } + + Logger.error(ex, cc); + throw ex; + } if (data == null || data.length === 0) throw new Error('File has no history'); const [previousRef, file] = GitLogParser.parseSimple(data, skip, editorLine !== undefined ? ref : undefined); @@ -2156,6 +2300,7 @@ export class GitService implements Disposable { return GitUri.resolveToUri(data, repoPath); } + // TODO: Add caching // Get the most recent commit for this file name ref = await Git.log__file_recent(repoPath, fileName, { similarityThreshold: Container.config.advanced.similarityThreshold @@ -2451,7 +2596,7 @@ export class GitService implements Disposable { { deletedOrMissing = '(deleted)', ...strings - }: { deletedOrMissing?: string; stagedUncommitted?: string; uncommitted?: string; working?: string } = {} + }: { deletedOrMissing?: string; uncommitted?: string; uncommittedStaged?: string; working?: string } = {} ) { if (ref === GitService.deletedOrMissingSha) return deletedOrMissing; diff --git a/src/git/models/commit.ts b/src/git/models/commit.ts index 2f6bba3..ba646a8 100644 --- a/src/git/models/commit.ts +++ b/src/git/models/commit.ts @@ -93,13 +93,13 @@ export abstract class GitCommit { } @memoize() - get isStagedUncommitted(): boolean { - return Git.isUncommittedStaged(this.sha); + get isUncommitted(): boolean { + return Git.isUncommitted(this.sha); } @memoize() - get isUncommitted(): boolean { - return Git.isUncommitted(this.sha); + get isUncommittedStaged(): boolean { + return Git.isUncommittedStaged(this.sha); } get previousFileSha(): string { @@ -119,51 +119,15 @@ export abstract class GitCommit { return GitUri.resolveToUri(this.fileName, this.repoPath); } - // @memoize() - // getFileStatus() { - // if (!this.isFile) return Promise.resolve(undefined); - - // return Container.git.getStatusForFile(this.repoPath, this.fileName); - // } - - // @memoize( - // (uri: Uri, ref: string | undefined, editorLine?: number) => - // `${uri.toString(true)}|${ref || ''}|${editorLine || ''}` - // ) - getPreviousDiffUris(uri: Uri, ref: string | undefined, editorLine?: number) { + @memoize( + (uri, editorLine, ref) => `${uri.toString(true)}|${editorLine || ''}|${ref || ''}` + ) + getPreviousLineDiffUris(uri: Uri, editorLine: number, ref: string | undefined) { if (!this.isFile) return Promise.resolve(undefined); - return Container.git.getPreviousDiffUris(this.repoPath, uri, ref, 0, editorLine); + return Container.git.getPreviousLineDiffUris(this.repoPath, uri, editorLine, ref); } - // private _previousUriPromise: Promise | undefined; - // getPreviousUri(staged: boolean = false) { - // if (!this.isFile) return Promise.resolve(undefined); - // if (!this.isUncommitted && this.previousSha !== undefined && !GitService.isShaParent(this.previousSha)) { - // return Promise.resolve(this.toGitUri(true)); - // } - - // if (this._previousUriPromise === undefined) { - // this._previousUriPromise = this._getPreviousUriCore(staged); - // } - - // return this._previousUriPromise; - // } - - // private async _getPreviousUriCore(staged: boolean) { - // if (!staged && !GitService.isStagedUncommitted(this.sha)) { - // const status = await this.getFileStatus(); - // if (status !== undefined) { - // // If the file is staged, diff with the staged version - // if (status.indexStatus !== undefined) { - // return GitUri.fromFile(this.fileName, this.repoPath, GitService.stagedUncommittedSha); - // } - // } - // } - - // return Container.git.getPreviousUri(this.repoPath, this.uri, this.sha); - // } - @memoize() getWorkingUri(): Promise { if (!this.isFile) return Promise.resolve(undefined); diff --git a/src/views/nodes/commitFileNode.ts b/src/views/nodes/commitFileNode.ts index c0b3c94..32b07da 100644 --- a/src/views/nodes/commitFileNode.ts +++ b/src/views/nodes/commitFileNode.ts @@ -150,7 +150,7 @@ export class CommitFileNode extends ViewRefFileNode { protected get resourceType(): string { if (!this.commit.isUncommitted) return ResourceType.CommitFile; - return this.commit.isStagedUncommitted ? `${ResourceType.File}+staged` : `${ResourceType.File}+unstaged`; + return this.commit.isUncommittedStaged ? `${ResourceType.File}+staged` : `${ResourceType.File}+unstaged`; } private _tooltip: string | undefined; diff --git a/src/views/nodes/statusFileNode.ts b/src/views/nodes/statusFileNode.ts index 4664e08..bb3cf05 100644 --- a/src/views/nodes/statusFileNode.ts +++ b/src/views/nodes/statusFileNode.ts @@ -23,7 +23,7 @@ export class StatusFileNode extends ViewNode { super(GitUri.fromFile(file, repoPath, 'HEAD'), view, parent); for (const c of this.commits) { - if (c.isStagedUncommitted) { + if (c.isUncommittedStaged) { this._hasStagedChanges = true; } else if (c.isUncommitted) {