Skip to content

Commit

Permalink
Closes #389 - Adds difftool command to files in explorers
Browse files Browse the repository at this point in the history
  • Loading branch information
eamodio committed Sep 29, 2018
1 parent 453f8a0 commit 7e8b404
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 43 deletions.
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3260,10 +3260,15 @@
"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/",
Expand Down
56 changes: 45 additions & 11 deletions src/commands/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 =
Expand Down
56 changes: 53 additions & 3 deletions src/commands/externalDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
) {}
}

Expand All @@ -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 };
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/commands/stashSave.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
15 changes: 13 additions & 2 deletions src/git/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
17 changes: 11 additions & 6 deletions src/git/gitService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions src/git/models/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/git/parsers/diffParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/git/parsers/logParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
];
}

Expand Down
2 changes: 1 addition & 1 deletion src/git/parsers/stashParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
22 changes: 11 additions & 11 deletions src/views/nodes/resultsFileNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] {
Expand All @@ -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))
Expand All @@ -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);
}
Expand Down Expand Up @@ -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!,
Expand Down

0 comments on commit 7e8b404

Please sign in to comment.