diff --git a/src/commands/diffLineWithPrevious.ts b/src/commands/diffLineWithPrevious.ts index a849845..45212ff 100644 --- a/src/commands/diffLineWithPrevious.ts +++ b/src/commands/diffLineWithPrevious.ts @@ -1,16 +1,14 @@ 'use strict'; -import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; +import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands, getCommandUri } from './common'; -import { BuiltInCommands, GlyphChars } from '../constants'; -import { DiffWithPreviousCommandArgs } from './diffWithPrevious'; -import { DiffWithWorkingCommandArgs } from './diffWithWorking'; +import { DiffWithCommandArgs } from './diffWith'; import { GitCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import { Messages } from '../messages'; -import * as path from 'path'; export interface DiffLineWithPreviousCommandArgs { commit?: GitCommit; + line?: number; showOptions?: TextDocumentShowOptions; } @@ -43,56 +41,26 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand { if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare'); args.commit = blame.commit; - - // If we don't have a sha or the current commit matches the blame, show the previous - if (gitUri.sha === undefined || gitUri.sha === args.commit.sha) { - return commands.executeCommand(Commands.DiffWithPrevious, new GitUri(uri, args.commit), { - line: args.line, - showOptions: args.showOptions - } as DiffWithPreviousCommandArgs); - } - - // If the line is uncommitted, find the previous commit and treat it as a DiffWithWorking - if (args.commit.isUncommitted) { - uri = args.commit.uri; - args.commit = new GitCommit(args.commit.type, args.commit.repoPath, args.commit.previousSha!, args.commit.previousFileName!, args.commit.author, args.commit.date, args.commit.message); - args.line = (blame.line.line + 1) + gitUri.offset; - - return commands.executeCommand(Commands.DiffWithWorking, uri, { - commit: args.commit, - line: args.line, - showOptions: args.showOptions - } as DiffWithWorkingCommandArgs); - } } catch (ex) { - Logger.error(ex, 'DiffWithPreviousLineCommand', `getBlameForLine(${blameline})`); + Logger.error(ex, 'DiffLineWithPreviousCommand', `getBlameForLine(${blameline})`); return window.showErrorMessage(`Unable to open compare. See output channel for more details`); } } - try { - const [rhs, lhs] = await Promise.all([ - this.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, gitUri.sha!), - this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha) - ]); - - if (args.line !== undefined && args.line !== 0) { - if (args.showOptions === undefined) { - args.showOptions = {}; - } - args.showOptions.selection = new Range(args.line, 0, args.line, 0); - } - - await commands.executeCommand(BuiltInCommands.Diff, - Uri.file(lhs), - Uri.file(rhs), - `${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(gitUri.fsPath)} (${gitUri.shortSha})`, - args.showOptions); - } - catch (ex) { - Logger.error(ex, 'DiffWithPreviousLineCommand', 'getVersionedFile'); - return window.showErrorMessage(`Unable to open compare. See output channel for more details`); - } + const diffArgs: DiffWithCommandArgs = { + repoPath: args.commit.repoPath, + lhs: { + sha: args.commit.previousSha !== undefined ? args.commit.previousSha : GitService.fakeSha, + uri: args.commit.previousUri + }, + rhs: { + sha: args.commit.sha, + uri: args.commit.uri + }, + line: args.line, + showOptions: args.showOptions + }; + return commands.executeCommand(Commands.DiffWith, diffArgs); } } \ No newline at end of file diff --git a/src/commands/diffLineWithWorking.ts b/src/commands/diffLineWithWorking.ts index 2869361..bbb0310 100644 --- a/src/commands/diffLineWithWorking.ts +++ b/src/commands/diffLineWithWorking.ts @@ -1,13 +1,14 @@ 'use strict'; import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands, getCommandUri } from './common'; -import { DiffWithWorkingCommandArgs } from './diffWithWorking'; +import { DiffWithCommandArgs } from './diffWith'; import { GitCommit, GitService, GitUri } from '../gitService'; import { Messages } from '../messages'; import { Logger } from '../logger'; export interface DiffLineWithWorkingCommandArgs { commit?: GitCommit; + line?: number; showOptions?: TextDocumentShowOptions; } @@ -52,6 +53,19 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand { } } - return commands.executeCommand(Commands.DiffWithWorking, uri, args as DiffWithWorkingCommandArgs); + const diffArgs: DiffWithCommandArgs = { + repoPath: args.commit.repoPath, + lhs: { + sha: args.commit.sha, + uri: args.commit.uri + }, + rhs: { + sha: '', + uri: args.commit.uri + }, + line: args.line, + showOptions: args.showOptions + }; + return commands.executeCommand(Commands.DiffWith, diffArgs); } } diff --git a/src/commands/diffWith.ts b/src/commands/diffWith.ts index cc847a2..623ec68 100644 --- a/src/commands/diffWith.ts +++ b/src/commands/diffWith.ts @@ -35,11 +35,11 @@ export class DiffWithCommand extends ActiveEditorCommand { args = { repoPath: commit1.repoPath, lhs: { - sha: commit1.sha, + sha: 'HEAD', uri: commit1.uri }, rhs: { - sha: 'HEAD', + sha: '', uri: commit1.uri } }; @@ -81,25 +81,15 @@ export class DiffWithCommand extends ActiveEditorCommand { } async execute(editor?: TextEditor, uri?: Uri, args: DiffWithCommandArgs = {}): Promise { + args = { ...args }; if (args.repoPath === undefined || args.lhs === undefined || args.rhs === undefined) return undefined; - if (args.lhs.title === undefined) { - args.lhs.title = (args.lhs.sha === 'HEAD') - ? `${path.basename(args.lhs.uri.fsPath)}` - : `${path.basename(args.lhs.uri.fsPath)} (${GitService.shortenSha(args.lhs.sha)})`; - } - if (args.rhs.title === undefined) { - args.rhs.title = (args.rhs.sha === 'HEAD') - ? `${path.basename(args.rhs.uri.fsPath)}` - : `${path.basename(args.rhs.uri.fsPath)} (${GitService.shortenSha(args.rhs.sha)})`; - } - try { const [lhs, rhs] = await Promise.all([ - args.lhs.sha !== 'HEAD' + args.lhs.sha !== '' && !GitService.isUncommitted(args.lhs.sha) ? this.git.getVersionedFile(args.repoPath, args.lhs.uri.fsPath, args.lhs.sha) : args.lhs.uri.fsPath, - args.rhs.sha !== 'HEAD' + args.rhs.sha !== '' && !GitService.isUncommitted(args.rhs.sha) ? this.git.getVersionedFile(args.repoPath, args.rhs.uri.fsPath, args.rhs.sha) : args.rhs.uri.fsPath ]); @@ -111,10 +101,45 @@ export class DiffWithCommand extends ActiveEditorCommand { args.showOptions.selection = new Range(args.line, 0, args.line, 0); } - await commands.executeCommand(BuiltInCommands.Diff, - Uri.file(lhs), - Uri.file(rhs), - `${args.lhs.title} ${GlyphChars.ArrowLeftRight} ${args.rhs.title}`, + let lhsPrefix = ''; + if (lhs === undefined) { + lhsPrefix = 'deleted in '; + } + else if (args.rhs.sha === GitService.fakeSha) { + lhsPrefix = 'added in '; + } + + let rhsPrefix = ''; + if (rhs === undefined) { + rhsPrefix = 'deleted in '; + } + else if (args.lhs.sha === GitService.fakeSha) { + rhsPrefix = 'added in '; + } + + if (args.lhs.title === undefined && args.lhs.sha !== GitService.fakeSha) { + args.lhs.title = (args.lhs.sha === '' || GitService.isUncommitted(args.lhs.sha)) + ? `${path.basename(args.lhs.uri.fsPath)}` + : `${path.basename(args.lhs.uri.fsPath)} (${lhsPrefix}${GitService.shortenSha(args.lhs.sha)})`; + } + if (args.rhs.title === undefined && args.rhs.sha !== GitService.fakeSha) { + args.rhs.title = (args.rhs.sha === '' || GitService.isUncommitted(args.rhs.sha)) + ? `${path.basename(args.rhs.uri.fsPath)}` + : `${path.basename(args.rhs.uri.fsPath)} (${rhsPrefix}${GitService.shortenSha(args.rhs.sha)})`; + } + + const title = (args.lhs.title !== undefined && args.rhs.title !== undefined) + ? `${args.lhs.title} ${GlyphChars.ArrowLeftRight} ${args.rhs.title}` + : args.lhs.title || args.rhs.title; + + return await commands.executeCommand(BuiltInCommands.Diff, + lhs === undefined + ? GitService.toGitContentUri(GitService.fakeSha, args.lhs.uri.fsPath, args.repoPath) + : Uri.file(lhs), + rhs === undefined + ? GitService.toGitContentUri(GitService.fakeSha, args.rhs.uri.fsPath, args.repoPath) + : Uri.file(rhs), + title, args.showOptions); } catch (ex) { diff --git a/src/commands/diffWithBranch.ts b/src/commands/diffWithBranch.ts index d61c197..81a7d37 100644 --- a/src/commands/diffWithBranch.ts +++ b/src/commands/diffWithBranch.ts @@ -1,10 +1,9 @@ 'use strict'; -import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; +import { commands, TextDocumentShowOptions, TextEditor, Uri } from 'vscode'; import { ActiveEditorCommand, Commands, getCommandUri } from './common'; import { GlyphChars } from '../constants'; import { DiffWithCommandArgs } from './diffWith'; import { GitService, GitUri } from '../gitService'; -import { Logger } from '../logger'; import { Messages } from '../messages'; import { BranchesQuickPick, CommandQuickPickItem } from '../quickPicks'; import * as path from 'path'; @@ -43,26 +42,20 @@ export class DiffWithBranchCommand extends ActiveEditorCommand { const branch = pick.branch.name; if (branch === undefined) return undefined; - try { - const diffArgs: DiffWithCommandArgs = { - repoPath: gitUri.repoPath, - lhs: { - sha: pick.branch.remote ? `remotes/${branch}` : branch, - uri: gitUri as Uri, - title: `${path.basename(gitUri.fsPath)} (${branch})` - }, - rhs: { - sha: 'HEAD', - uri: gitUri as Uri - }, - line: args.line, - showOptions: args.showOptions - }; - await commands.executeCommand(Commands.DiffWith, diffArgs); - } - catch (ex) { - Logger.error(ex, 'DiffWithBranchCommand', 'getVersionedFile'); - return window.showErrorMessage(`Unable to open branch compare. See output channel for more details`); - } + const diffArgs: DiffWithCommandArgs = { + repoPath: gitUri.repoPath, + lhs: { + sha: pick.branch.remote ? `remotes/${branch}` : branch, + uri: gitUri as Uri, + title: `${path.basename(gitUri.fsPath)} (${branch})` + }, + rhs: { + sha: '', + uri: gitUri as Uri + }, + line: args.line, + showOptions: args.showOptions + }; + return commands.executeCommand(Commands.DiffWith, diffArgs); } } \ No newline at end of file diff --git a/src/commands/diffWithNext.ts b/src/commands/diffWithNext.ts index 593df31..29999b3 100644 --- a/src/commands/diffWithNext.ts +++ b/src/commands/diffWithNext.ts @@ -2,16 +2,16 @@ import { Iterables } from '../system'; import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands, getCommandUri } from './common'; -import { BuiltInCommands, GlyphChars } from '../constants'; +import { DiffWithCommandArgs } from './diffWith'; import { GitLogCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import { Messages } from '../messages'; -import * as path from 'path'; export interface DiffWithNextCommandArgs { commit?: GitLogCommit; - line?: number; range?: Range; + + line?: number; showOptions?: TextDocumentShowOptions; } @@ -54,28 +54,19 @@ export class DiffWithNextCommand extends ActiveEditorCommand { if (args.commit.nextSha === undefined) return commands.executeCommand(Commands.DiffWithWorking, uri); - try { - const [rhs, lhs] = await Promise.all([ - this.git.getVersionedFile(args.commit.repoPath, args.commit.nextUri.fsPath, args.commit.nextSha), - this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha) - ]); - - if (args.line !== undefined && args.line !== 0) { - if (args.showOptions === undefined) { - args.showOptions = {}; - } - args.showOptions.selection = new Range(args.line, 0, args.line, 0); - } - - await commands.executeCommand(BuiltInCommands.Diff, - Uri.file(lhs), - Uri.file(rhs), - `${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(args.commit.nextUri.fsPath)} (${args.commit.nextShortSha})`, - args.showOptions); - } - catch (ex) { - Logger.error(ex, 'DiffWithNextCommand', 'getVersionedFile'); - return window.showErrorMessage(`Unable to open compare. See output channel for more details`); - } + const diffArgs: DiffWithCommandArgs = { + repoPath: args.commit.repoPath, + lhs: { + sha: args.commit.sha, + uri: args.commit.uri + }, + rhs: { + sha: args.commit.nextSha, + uri: args.commit.nextUri + }, + line: args.line, + showOptions: args.showOptions + }; + return commands.executeCommand(Commands.DiffWith, diffArgs); } } \ No newline at end of file diff --git a/src/commands/diffWithPrevious.ts b/src/commands/diffWithPrevious.ts index 564587e..bde4da8 100644 --- a/src/commands/diffWithPrevious.ts +++ b/src/commands/diffWithPrevious.ts @@ -2,22 +2,18 @@ import { Iterables } from '../system'; import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands, getCommandUri } from './common'; -import { BuiltInCommands, FakeSha, GlyphChars } from '../constants'; +import { DiffWithCommandArgs } from './diffWith'; import { DiffWithWorkingCommandArgs } from './diffWithWorking'; import { GitCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import { Messages } from '../messages'; -import * as path from 'path'; export interface DiffWithPreviousCommandArgs { commit?: GitCommit; - line?: number; range?: Range; - showOptions?: TextDocumentShowOptions; - allowMissingPrevious?: boolean; - leftTitlePrefix?: string; - rightTitlePrefix?: string; + line?: number; + showOptions?: TextDocumentShowOptions; } export class DiffWithPreviousCommand extends ActiveEditorCommand { @@ -40,6 +36,7 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand { try { const sha = args.commit === undefined ? gitUri.sha : args.commit.sha; + if (sha === GitService.fakeSha) return Messages.showCommitHasNoPreviousCommitWarningMessage(); const log = await this.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, sha, { maxCount: 2, range: args.range!, skipMerges: true }); if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare'); @@ -47,7 +44,9 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand { args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values()); // If the sha is missing and the file is uncommitted, then treat it as a DiffWithWorking - if (gitUri.sha === undefined && await this.git.isFileUncommitted(gitUri)) return commands.executeCommand(Commands.DiffWithWorking, uri, { commit: args.commit, showOptions: args.showOptions } as DiffWithWorkingCommandArgs); + if (gitUri.sha === undefined && await this.git.isFileUncommitted(gitUri)) { + return commands.executeCommand(Commands.DiffWithWorking, uri, { commit: args.commit, showOptions: args.showOptions } as DiffWithWorkingCommandArgs); + } } catch (ex) { Logger.error(ex, 'DiffWithPreviousCommand', `getLogForFile(${gitUri.repoPath}, ${gitUri.fsPath})`); @@ -55,32 +54,19 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand { } } - if (args.commit.previousSha === undefined && !args.allowMissingPrevious) return Messages.showCommitHasNoPreviousCommitWarningMessage(args.commit); - - try { - const [rhs, lhs] = await Promise.all([ - this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha), - this.git.getVersionedFile(args.commit.repoPath, args.commit.previousUri.fsPath, args.commit.previousSha === undefined ? FakeSha : args.commit.previousSha) - ]); - - if (args.line !== undefined && args.line !== 0) { - if (args.showOptions === undefined) { - args.showOptions = {}; - } - args.showOptions.selection = new Range(args.line, 0, args.line, 0); - } - - await commands.executeCommand(BuiltInCommands.Diff, - Uri.file(lhs), - Uri.file(rhs), - args.commit.previousShortSha === undefined - ? `${path.basename(args.commit.uri.fsPath)} (${args.rightTitlePrefix || ''}${args.commit.shortSha})` - : `${path.basename(args.commit.previousUri.fsPath)} (${args.leftTitlePrefix || ''}${args.commit.previousShortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(args.commit.uri.fsPath)} (${args.rightTitlePrefix || ''}${args.commit.shortSha})`, - args.showOptions); - } - catch (ex) { - Logger.error(ex, 'DiffWithPreviousCommand', 'getVersionedFile'); - return window.showErrorMessage(`Unable to open compare. See output channel for more details`); - } + const diffArgs: DiffWithCommandArgs = { + repoPath: args.commit.repoPath, + lhs: { + sha: args.commit.previousSha !== undefined ? args.commit.previousSha : GitService.fakeSha, + uri: args.commit.previousUri + }, + rhs: { + sha: args.commit.sha, + uri: args.commit.uri + }, + line: args.line, + showOptions: args.showOptions + }; + return commands.executeCommand(Commands.DiffWith, diffArgs); } } \ No newline at end of file diff --git a/src/commands/diffWithRevision.ts b/src/commands/diffWithRevision.ts index d04e459..8a6e17f 100644 --- a/src/commands/diffWithRevision.ts +++ b/src/commands/diffWithRevision.ts @@ -8,8 +8,9 @@ import { Messages } from '../messages'; import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks'; export interface DiffWithRevisionCommandArgs { - line?: number; maxCount?: number; + + line?: number; showOptions?: TextDocumentShowOptions; } @@ -52,17 +53,17 @@ export class DiffWithRevisionCommand extends ActiveEditorCommand { uri: gitUri as Uri }, rhs: { - sha: 'HEAD', + sha: '', uri: gitUri as Uri }, line: args.line, showOptions: args.showOptions }; - await commands.executeCommand(Commands.DiffWith, diffArgs); + return await commands.executeCommand(Commands.DiffWith, diffArgs); } catch (ex) { - Logger.error(ex, 'DiffWithRevisionCommand', 'getVersionedFile'); - return window.showErrorMessage(`Unable to open history compare. See output channel for more details`); + Logger.error(ex, 'DiffWithRevisionCommand'); + return window.showErrorMessage(`Unable to open compare. See output channel for more details`); } } } \ No newline at end of file diff --git a/src/commands/diffWithWorking.ts b/src/commands/diffWithWorking.ts index 0bcd88f..574c230 100644 --- a/src/commands/diffWithWorking.ts +++ b/src/commands/diffWithWorking.ts @@ -1,14 +1,14 @@ 'use strict'; -import { commands, Range, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; +import { commands, TextDocumentShowOptions, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands, getCommandUri } from './common'; -import { BuiltInCommands, GlyphChars } from '../constants'; +import { DiffWithCommandArgs } from './diffWith'; import { GitCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import { Messages } from '../messages'; -import * as path from 'path'; export interface DiffWithWorkingCommandArgs { commit?: GitCommit; + line?: number; showOptions?: TextDocumentShowOptions; } @@ -48,25 +48,19 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand { const workingFileName = await this.git.findWorkingFileName(gitUri.repoPath, gitUri.fsPath); if (workingFileName === undefined) return undefined; - try { - const compare = await this.git.getVersionedFile(args.commit.repoPath, args.commit.uri.fsPath, args.commit.sha); - - if (args.line !== undefined && args.line !== 0) { - if (args.showOptions === undefined) { - args.showOptions = {}; - } - args.showOptions.selection = new Range(args.line, 0, args.line, 0); - } - - await commands.executeCommand(BuiltInCommands.Diff, - Uri.file(compare), - Uri.file(path.resolve(gitUri.repoPath, workingFileName)), - `${path.basename(args.commit.uri.fsPath)} (${args.commit.shortSha}) ${GlyphChars.ArrowLeftRight} ${path.basename(workingFileName)}`, - args.showOptions); - } - catch (ex) { - Logger.error(ex, 'DiffWithWorkingCommand', 'getVersionedFile'); - return window.showErrorMessage(`Unable to open compare. See output channel for more details`); - } + const diffArgs: DiffWithCommandArgs = { + repoPath: args.commit.repoPath, + lhs: { + sha: args.commit.sha, + uri: args.commit.uri + }, + rhs: { + sha: '', + uri: args.commit.uri + }, + line: args.line, + showOptions: args.showOptions + }; + return commands.executeCommand(Commands.DiffWith, diffArgs); } } diff --git a/src/constants.ts b/src/constants.ts index 1c48be5..ed8c755 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,8 +8,6 @@ export const QualifiedExtensionId = `eamodio.${ExtensionId}`; export const ApplicationInsightsKey = 'a9c302f8-6483-4d01-b92c-c159c799c679'; -export const FakeSha = 'ffffffffffffffffffffffffffffffffffffffff'; - export type BuiltInCommands = 'cursorMove' | 'editor.action.showReferences' | 'editor.action.toggleRenderWhitespace' | diff --git a/src/git/git.ts b/src/git/git.ts index 88b8b5d..c4c0972 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -39,38 +39,49 @@ const GitWarnings = [ /no upstream configured for branch/ ]; -async function gitCommand(options: { cwd: string, encoding?: string, onError?: (ex: Error) => string | undefined }, ...args: any[]) { +interface GitCommandOptions { + cwd: string; + encoding?: string; + overrideErrorHandling?: boolean; +} + +async function gitCommand(options: GitCommandOptions, ...args: any[]): Promise { + if (options.overrideErrorHandling) return gitCommandCore(options, ...args); + try { - // Fixes https://github.com/eamodio/vscode-gitlens/issues/73 - // See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x - args.splice(0, 0, '-c', 'core.quotepath=false'); - - const opts = { encoding: 'utf8', ...options }; - const s = await spawnPromise(git.path, args, { cwd: options.cwd, encoding: (opts.encoding === 'utf8') ? 'utf8' : 'binary' }); - Logger.log('git', ...args, ` cwd='${options.cwd}'`); - if (opts.encoding === 'utf8' || opts.encoding === 'binary') return s; - - return iconv.decode(Buffer.from(s, 'binary'), opts.encoding); + return await gitCommandCore(options, ...args); } catch (ex) { - if (options.onError !== undefined) { - const result = options.onError(ex); - if (result !== undefined) return result; - } + return gitCommandDefaultErrorHandler(ex, options, ...args); + } +} - const msg = ex && ex.toString(); - if (msg) { - for (const warning of GitWarnings) { - if (warning.test(msg)) { - Logger.warn('git', ...args, ` cwd='${options.cwd}'`, msg && `\n ${msg.replace(/\r?\n|\r/g, ' ')}`); - return ''; - } +async function gitCommandCore(options: GitCommandOptions, ...args: any[]): Promise { + // Fixes https://github.com/eamodio/vscode-gitlens/issues/73 + // See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x + args.splice(0, 0, '-c', 'core.quotepath=false'); + + const opts = { encoding: 'utf8', ...options }; + const s = await spawnPromise(git.path, args, { cwd: options.cwd, encoding: (opts.encoding === 'utf8') ? 'utf8' : 'binary' }); + Logger.log('git', ...args, ` cwd='${options.cwd}'`); + if (opts.encoding === 'utf8' || opts.encoding === 'binary') return s; + + return iconv.decode(Buffer.from(s, 'binary'), opts.encoding); +} + +function gitCommandDefaultErrorHandler(ex: Error, options: GitCommandOptions, ...args: any[]): string { + const msg = ex && ex.toString(); + if (msg) { + for (const warning of GitWarnings) { + if (warning.test(msg)) { + Logger.warn('git', ...args, ` cwd='${options.cwd}'`, msg && `\n ${msg.replace(/\r?\n|\r/g, ' ')}`); + return ''; } } - - Logger.error(ex, 'git', ...args, ` cwd='${options.cwd}'`, msg && `\n ${msg.replace(/\r?\n|\r/g, ' ')}`); - throw ex; } + + Logger.error(ex, 'git', ...args, ` cwd='${options.cwd}'`, msg && `\n ${msg.replace(/\r?\n|\r/g, ' ')}`); + throw ex; } export class Git { @@ -99,6 +110,7 @@ export class Git { static async getVersionedFile(repoPath: string | undefined, fileName: string, branchOrSha: string) { const data = await Git.show(repoPath, fileName, branchOrSha, 'binary'); + if (data === undefined) return undefined; const suffix = Strings.truncate(Strings.sanitizeForFS(Git.isSha(branchOrSha) ? Git.shortenSha(branchOrSha) : branchOrSha), 50, ''); const ext = path.extname(fileName); @@ -189,16 +201,20 @@ export class Git { return gitCommand({ cwd: repoPath }, ...params); } - static branch_current(repoPath: string) { + static async branch_current(repoPath: string) { const params = [`rev-parse`, `--abbrev-ref`, `--symbolic-full-name`, `@`, `@{u}`]; - const onError = (ex: Error) => { + + const opts = { cwd: repoPath, overrideErrorHandling: true }; + try { + return await gitCommand(opts, ...params); + } + catch (ex) { if (/no upstream configured for branch/.test(ex && ex.toString())) { return ex.message.split('\n')[0]; } - return undefined; - }; - return gitCommand({ cwd: repoPath, onError: onError }, ...params); + return gitCommandDefaultErrorHandler(ex, opts, ...params); + } } static checkout(repoPath: string, fileName: string, sha: string) { @@ -209,9 +225,9 @@ export class Git { static async config_get(key: string, repoPath?: string) { try { - return await gitCommand({ cwd: repoPath || '' }, `config`, `--get`, key); + return await gitCommand({ cwd: repoPath || '', overrideErrorHandling: true }, `config`, `--get`, key); } - catch (ex) { + catch { return ''; } } @@ -315,9 +331,9 @@ export class Git { static async ls_files(repoPath: string, fileName: string): Promise { try { - return await gitCommand({ cwd: repoPath }, 'ls-files', fileName); + return await gitCommand({ cwd: repoPath, overrideErrorHandling: true }, 'ls-files', fileName); } - catch (ex) { + catch { return ''; } } @@ -330,12 +346,24 @@ export class Git { return gitCommand({ cwd: repoPath }, 'remote', 'get-url', remote); } - static show(repoPath: string | undefined, fileName: string, branchOrSha: string, encoding?: string) { + static async show(repoPath: string | undefined, fileName: string, branchOrSha: string, encoding?: string) { const [file, root] = Git.splitPath(fileName, repoPath); branchOrSha = branchOrSha.replace('^', ''); - if (Git.isUncommitted(branchOrSha)) return Promise.reject(new Error(`sha=${branchOrSha} is uncommitted`)); - return gitCommand({ cwd: root, encoding: encoding || defaultEncoding }, 'show', `${branchOrSha}:./${file}`); + if (Git.isUncommitted(branchOrSha)) throw new Error(`sha=${branchOrSha} is uncommitted`); + + const opts = { cwd: root, encoding: encoding || defaultEncoding, overrideErrorHandling: true }; + const args = `${branchOrSha}:./${file}`; + try { + return await gitCommand(opts, 'show', args); + } + catch (ex) { + if (/Path \'.*?\' does not exist in/.test(ex && ex.toString())) { + return undefined; + } + + return gitCommandDefaultErrorHandler(ex, opts, args); + } } static stash_apply(repoPath: string, stashName: string, deleteAfter: boolean) { diff --git a/src/gitContentProvider.ts b/src/gitContentProvider.ts index 4683b8b..f2894c1 100644 --- a/src/gitContentProvider.ts +++ b/src/gitContentProvider.ts @@ -15,7 +15,9 @@ export class GitContentProvider implements TextDocumentContentProvider { const data = GitService.fromGitContentUri(uri); const fileName = data.originalFileName || data.fileName; try { - let text = await this.git.getVersionedFileText(data.repoPath, fileName, data.sha); + let text = data.sha !== GitService.fakeSha + ? await this.git.getVersionedFileText(data.repoPath, fileName, data.sha) + : ''; if (data.decoration) { text = `${data.decoration}\n${text}`; } diff --git a/src/gitService.ts b/src/gitService.ts index f93f21e..f81fa11 100644 --- a/src/gitService.ts +++ b/src/gitService.ts @@ -72,6 +72,8 @@ export const RepoChangedReasons = { export class GitService extends Disposable { + static fakeSha = 'ffffffffffffffffffffffffffffffffffffffff'; + private _onDidBlameFail = new EventEmitter(); get onDidBlameFail(): Event { return this._onDidBlameFail.event; @@ -954,6 +956,8 @@ export class GitService extends Disposable { Logger.log(`getVersionedFile('${repoPath}', '${fileName}', ${sha})`); const file = await Git.getVersionedFile(repoPath, fileName, sha); + if (file === undefined) return undefined; + const cacheKey = this.getCacheEntryKey(file); const entry = new UriCacheEntry(new GitUri(Uri.file(fileName), { sha, repoPath: repoPath!, fileName })); this._uriCache.set(cacheKey, entry); diff --git a/src/messages.ts b/src/messages.ts index 39e804f..1763fd2 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -32,7 +32,8 @@ export class Messages { this.context = context; } - static showCommitHasNoPreviousCommitWarningMessage(commit: GitCommit): Promise { + static showCommitHasNoPreviousCommitWarningMessage(commit?: GitCommit): Promise { + if (commit === undefined) return Messages._showMessage('info', `Commit has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning); return Messages._showMessage('info', `Commit ${commit.shortSha} (${commit.author}, ${moment(commit.date).fromNow()}) has no previous commit`, SuppressedKeys.CommitHasNoPreviousCommitWarning); } diff --git a/src/views/commitFileNode.ts b/src/views/commitFileNode.ts index 137e3c6..57ed32f 100644 --- a/src/views/commitFileNode.ts +++ b/src/views/commitFileNode.ts @@ -40,16 +40,6 @@ export class CommitFileNode extends ExplorerNode { } getCommand(): Command | undefined { - let allowMissingPrevious = false; - let prefix = undefined; - if (this.status.status === 'A') { - allowMissingPrevious = true; - prefix = 'added in '; - } - else if (this.status.status === 'D') { - prefix = 'deleted in '; - } - return { title: 'Compare File with Previous Revision', command: Commands.DiffWithPrevious, @@ -61,9 +51,7 @@ export class CommitFileNode extends ExplorerNode { showOptions: { preserveFocus: true, preview: true - }, - allowMissingPrevious: allowMissingPrevious, - rightTitlePrefix: prefix + } } as DiffWithPreviousCommandArgs ] };