Browse Source

Closes #389 - Adds difftool command to files in explorers

main
Eric Amodio 6 years ago
parent
commit
7e8b404c2c
11 changed files with 148 additions and 43 deletions
  1. +6
    -1
      package.json
  2. +45
    -11
      src/commands/common.ts
  3. +53
    -3
      src/commands/externalDiff.ts
  4. +1
    -1
      src/commands/stashSave.ts
  5. +13
    -2
      src/git/git.ts
  6. +11
    -6
      src/git/gitService.ts
  7. +3
    -3
      src/git/models/file.ts
  8. +1
    -1
      src/git/parsers/diffParser.ts
  9. +3
    -3
      src/git/parsers/logParser.ts
  10. +1
    -1
      src/git/parsers/stashParser.ts
  11. +11
    -11
      src/views/nodes/resultsFileNode.ts

+ 6
- 1
package.json View File

@ -3260,11 +3260,16 @@
"group": "2_gitlens@1"
},
{
"command": "gitlens.explorers.openChangesWithWorking",
"command": "gitlens.externalDiff",
"when": "viewItem =~ /gitlens:file\\b/",
"group": "2_gitlens@2"
},
{
"command": "gitlens.explorers.openChangesWithWorking",
"when": "viewItem =~ /gitlens:file\\b/",
"group": "2_gitlens@3"
},
{
"command": "gitlens.explorers.openFile",
"when": "viewItem =~ /gitlens:(file|history-file|status:file)\\b/",
"group": "3_gitlens@1"

+ 45
- 11
src/commands/common.ts View File

@ -180,23 +180,55 @@ export interface CommandViewContext extends CommandBaseContext {
export function isCommandViewContextWithBranch(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { branch: GitBranch } } {
return (
context.type === 'view' && (context.node as ExplorerNode & { branch?: GitBranch }).branch instanceof GitBranch
);
if (context.type !== 'view') return false;
return (context.node as ExplorerNode & { branch: GitBranch }).branch instanceof GitBranch;
}
export function isCommandViewContextWithCommit<T extends GitCommit>(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { commit: T } } {
if (context.type !== 'view') return false;
return (context.node as ExplorerNode & { commit: GitCommit }).commit instanceof GitCommit;
}
export function isCommandViewContextWithFile(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { file: GitFile; repoPath: string } } {
if (context.type !== 'view') return false;
const node = context.node as ExplorerNode & { file: GitFile; repoPath: string };
return node.file !== undefined && (node.file.repoPath !== undefined || node.repoPath !== undefined);
}
export function isCommandViewContextWithFileCommit(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { commit: GitCommit; file: GitFile; repoPath: string } } {
if (context.type !== 'view') return false;
const node = context.node as ExplorerNode & { commit: GitCommit; file: GitFile; repoPath: string };
return (
context.type === 'view' && (context.node as ExplorerNode & { commit?: GitCommit }).commit instanceof GitCommit
node.file !== undefined &&
node.commit instanceof GitCommit &&
(node.file.repoPath !== undefined || node.repoPath !== undefined)
);
}
export function isCommandViewContextWithFile(
export function isCommandViewContextWithFileRefs(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { file: GitFile } } {
return context.type === 'view' && (context.node as ExplorerNode & { file?: GitFile }).file !== undefined;
): context is CommandViewContext & {
node: ExplorerNode & { file: GitFile; ref1: string; ref2: string; repoPath: string };
} {
if (context.type !== 'view') return false;
const node = context.node as ExplorerNode & { file: GitFile; ref1: string; ref2: string; repoPath: string };
return (
node.file !== undefined &&
node.ref1 !== undefined &&
node.ref2 !== undefined &&
(node.file.repoPath !== undefined || node.repoPath !== undefined)
);
}
export function isCommandViewContextWithRef(
@ -208,15 +240,17 @@ export function isCommandViewContextWithRef(
export function isCommandViewContextWithRemote(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { remote: GitRemote } } {
return (
context.type === 'view' && (context.node as ExplorerNode & { remote?: GitRemote }).remote instanceof GitRemote
);
if (context.type !== 'view') return false;
return (context.node as ExplorerNode & { remote: GitRemote }).remote instanceof GitRemote;
}
export function isCommandViewContextWithRepo(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { repo: Repository } } {
return context.type === 'view' && (context.node as ExplorerNode & { repo?: Repository }).repo instanceof Repository;
if (context.type !== 'view') return false;
return (context.node as ExplorerNode & { repo?: Repository }).repo instanceof Repository;
}
export type CommandContext =

+ 53
- 3
src/commands/externalDiff.ts View File

@ -2,10 +2,18 @@
import { commands, SourceControlResourceState, Uri, window } from 'vscode';
import { BuiltInCommands, GlyphChars } from '../constants';
import { Container } from '../container';
import { GitService, GitUri } from '../git/gitService';
import { Logger } from '../logger';
import { Messages } from '../messages';
import { Arrays } from '../system';
import { Command, CommandContext, Commands, getRepoPathOrPrompt } from './common';
import {
Command,
CommandContext,
Commands,
getRepoPathOrPrompt,
isCommandViewContextWithFileCommit,
isCommandViewContextWithFileRefs
} from './common';
enum Status {
INDEX_MODIFIED,
@ -42,7 +50,9 @@ interface Resource extends SourceControlResourceState {
class ExternalDiffFile {
constructor(
public readonly uri: Uri,
public readonly staged: boolean
public readonly staged: boolean,
public readonly ref1?: string,
public readonly ref2?: string
) {}
}
@ -56,6 +66,41 @@ export class ExternalDiffCommand extends Command {
}
protected async preExecute(context: CommandContext, args: ExternalDiffCommandArgs = {}): Promise<any> {
if (isCommandViewContextWithFileCommit(context)) {
args = { ...args };
const ref1 = GitService.isUncommitted(context.node.commit.previousFileSha)
? ''
: context.node.commit.previousFileSha;
const ref2 = context.node.commit.isUncommitted ? '' : context.node.commit.sha;
args.files = [
new ExternalDiffFile(
GitUri.fromFile(context.node.file, context.node.file.repoPath || context.node.repoPath),
context.node.commit.isStagedUncommitted || context.node.file.indexStatus !== undefined,
ref1,
ref2
)
];
return this.execute(args);
}
if (isCommandViewContextWithFileRefs(context)) {
args = { ...args };
args.files = [
new ExternalDiffFile(
GitUri.fromFile(context.node.file, context.node.file.repoPath || context.node.repoPath),
context.node.file.indexStatus !== undefined,
context.node.ref1,
context.node.ref2
)
];
return this.execute(args);
}
if (args.files === undefined) {
if (context.type === 'scm-states') {
args = { ...args };
@ -162,7 +207,12 @@ export class ExternalDiffCommand extends Command {
}
for (const file of args.files) {
void Container.git.openDiffTool(repoPath, file.uri, file.staged, tool);
void Container.git.openDiffTool(repoPath, file.uri, {
ref1: file.ref1,
ref2: file.ref2,
staged: file.staged,
tool: tool
});
}
return undefined;

+ 1
- 1
src/commands/stashSave.ts View File

@ -31,7 +31,7 @@ export class StashSaveCommand extends Command {
protected async preExecute(context: CommandContext, args: StashSaveCommandArgs = {}): Promise<any> {
if (isCommandViewContextWithFile(context)) {
args = { ...args };
args.uris = [GitUri.fromFile(context.node.file, context.node.file.repoPath)];
args.uris = [GitUri.fromFile(context.node.file, context.node.file.repoPath || context.node.repoPath)];
}
else if (isCommandViewContextWithRepo(context)) {
args = { ...args };

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

@ -433,11 +433,22 @@ export class Git {
return git<string>({ cwd: repoPath }, ...params);
}
static difftool_fileDiff(repoPath: string, fileName: string, tool: string, staged: boolean) {
static difftool_fileDiff(
repoPath: string,
fileName: string,
tool: string,
options: { ref1?: string; ref2?: string; staged?: boolean } = {}
) {
const params = ['difftool', '--no-prompt', `--tool=${tool}`];
if (staged) {
if (options.staged) {
params.push('--staged');
}
if (options.ref1) {
params.push(options.ref1);
}
if (options.ref2) {
params.push(options.ref2);
}
params.push('--', fileName);
return git<string>({ cwd: repoPath }, ...params);

+ 11
- 6
src/git/gitService.ts View File

@ -1827,15 +1827,20 @@ export class GitService implements Disposable {
return (await Git.config_get('diff.guitool', repoPath)) || (await Git.config_get('diff.tool', repoPath));
}
async openDiffTool(repoPath: string, uri: Uri, staged: boolean, tool?: string) {
if (!tool) {
tool = await this.getDiffTool(repoPath);
if (tool === undefined) throw new Error('No diff tool found');
async openDiffTool(
repoPath: string,
uri: Uri,
options: { ref1?: string; ref2?: string; staged?: boolean; tool?: string } = {}
) {
if (!options.tool) {
options.tool = await this.getDiffTool(repoPath);
if (options.tool === undefined) throw new Error('No diff tool found');
}
Logger.log(`openDiffTool('${repoPath}', '${uri.fsPath}', ${staged}, '${tool}')`);
const { tool, ...opts } = options;
Logger.log(`openDiffTool('${repoPath}', '${uri.fsPath}', '${tool}', ${opts})`);
return Git.difftool_fileDiff(repoPath, uri.fsPath, tool, staged);
return Git.difftool_fileDiff(repoPath, uri.fsPath, tool, opts);
}
async openDirectoryDiff(repoPath: string, ref1: string, ref2?: string, tool?: string) {

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

@ -8,9 +8,9 @@ export declare type GitFileStatus = '!' | '?' | 'A' | 'C' | 'D' | 'M' | 'R' | 'T
export interface GitFile {
status: GitFileStatus;
readonly repoPath: string;
readonly indexStatus: GitFileStatus | undefined;
readonly workingTreeStatus: GitFileStatus | undefined;
readonly repoPath?: string;
readonly indexStatus?: GitFileStatus;
readonly workingTreeStatus?: GitFileStatus;
readonly fileName: string;
readonly originalFileName?: string;
}

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

@ -158,7 +158,7 @@ export class GitDiffParser {
fileName: (' ' + match[2]).substr(1),
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
originalFileName: match[3] === undefined ? undefined : (' ' + match[3]).substr(1)
} as GitFile);
});
} while (match != null);
if (!files.length) return undefined;

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

@ -148,7 +148,7 @@ export class GitLogParser {
status: line[0] as GitFileStatus,
fileName: line.substring(1),
originalFileName: undefined
} as GitFile;
};
this.parseFileName(status);
if (status.fileName) {
@ -274,10 +274,10 @@ export class GitLogParser {
if (type === GitCommitType.File) {
entry.files = [
{
status: entry.status,
status: entry.status!,
fileName: relativeFileName,
originalFileName: originalFileName
} as GitFile
}
];
}

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

@ -102,7 +102,7 @@ export class GitStashParser {
status: line[0] as GitFileStatus,
fileName: line.substring(1),
originalFileName: undefined
} as GitFile;
};
GitLogParser.parseFileName(status);
if (status.fileName) {

+ 11
- 11
src/views/nodes/resultsFileNode.ts View File

@ -10,13 +10,13 @@ import { ExplorerNode, ResourceType } from './explorerNode';
export class ResultsFileNode extends ExplorerNode {
constructor(
public readonly repoPath: string,
private readonly _file: GitFile,
private readonly _ref1: string,
private readonly _ref2: string,
public readonly file: GitFile,
public readonly ref1: string,
public readonly ref2: string,
parent: ExplorerNode,
public readonly explorer: Explorer
) {
super(GitUri.fromFile(_file, repoPath, _ref1 ? _ref1 : _ref2 ? _ref2 : undefined), parent);
super(GitUri.fromFile(file, repoPath, ref1 ? ref1 : ref2 ? ref2 : undefined), parent);
}
getChildren(): ExplorerNode[] {
@ -26,9 +26,9 @@ export class ResultsFileNode extends ExplorerNode {
getTreeItem(): TreeItem {
const item = new TreeItem(this.label, TreeItemCollapsibleState.None);
item.contextValue = ResourceType.ResultsFile;
item.tooltip = StatusFileFormatter.fromTemplate('${file}\n${directory}/\n\n${status}', this._file);
item.tooltip = StatusFileFormatter.fromTemplate('${file}\n${directory}/\n\n${status}', this.file);
const statusIcon = GitFile.getStatusIcon(this._file.status);
const statusIcon = GitFile.getStatusIcon(this.file.status);
item.iconPath = {
dark: Container.context.asAbsolutePath(path.join('images', 'dark', statusIcon)),
light: Container.context.asAbsolutePath(path.join('images', 'light', statusIcon))
@ -49,7 +49,7 @@ export class ResultsFileNode extends ExplorerNode {
private _label: string | undefined;
get label() {
if (this._label === undefined) {
this._label = StatusFileFormatter.fromTemplate('${filePath}', this._file, {
this._label = StatusFileFormatter.fromTemplate('${filePath}', this.file, {
relativePath: this.relativePath
} as IStatusFormatOptions);
}
@ -77,14 +77,14 @@ export class ResultsFileNode extends ExplorerNode {
this.uri,
{
lhs: {
sha: this._ref1,
sha: this.ref1,
uri: this.uri
},
rhs: {
sha: this._ref2,
sha: this.ref2,
uri:
this._file.status === 'R'
? GitUri.fromFile(this._file, this.uri.repoPath!, this._ref2, true)
this.file.status === 'R'
? GitUri.fromFile(this.file, this.uri.repoPath!, this.ref2, true)
: this.uri
},
repoPath: this.uri.repoPath!,

Loading…
Cancel
Save