Skip to content

Commit 61e3708

Browse files
ckaczoreamodio
authored andcommitted
Add "external diff" command to SCM groups and files
1 parent 65a3b31 commit 61e3708

File tree

9 files changed

+165
-7
lines changed

9 files changed

+165
-7
lines changed

package.json

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,11 @@
10681068
"light": "images/light/icon-add.svg"
10691069
}
10701070
},
1071+
{
1072+
"command": "gitlens.externalDiff",
1073+
"title": "Open Changes (with difftool)",
1074+
"category": "GitLens"
1075+
},
10711076
{
10721077
"command": "gitlens.resetSuppressedWarnings",
10731078
"title": "Reset Suppressed Warnings",
@@ -1520,17 +1525,22 @@
15201525
{
15211526
"command": "gitlens.openChangedFiles",
15221527
"when": "gitlens:enabled",
1523-
"group": "1_gitlens@1"
1528+
"group": "2_gitlens@1"
15241529
},
15251530
{
15261531
"command": "gitlens.closeUnchangedFiles",
15271532
"when": "gitlens:enabled",
1528-
"group": "1_gitlens@2"
1533+
"group": "2_gitlens@2"
1534+
},
1535+
{
1536+
"command": "gitlens.externalDiff",
1537+
"when": "gitlens:enabled",
1538+
"group": "2_gitlens@3"
15291539
},
15301540
{
15311541
"command": "gitlens.stashSave",
15321542
"when": "gitlens:enabled",
1533-
"group": "2_gitlens@1"
1543+
"group": "3_gitlens@1"
15341544
}
15351545
],
15361546
"scm/resourceState/context": [
@@ -1539,6 +1549,11 @@
15391549
"when": "gitlens:enabled && gitlens:hasRemotes",
15401550
"group": "navigation"
15411551
},
1552+
{
1553+
"command": "gitlens.externalDiff",
1554+
"when": "gitlens:enabled",
1555+
"group": "navigation"
1556+
},
15421557
{
15431558
"command": "gitlens.diffWithRevision",
15441559
"when": "gitlens:enabled",

src/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export * from './commands/diffWithNext';
1414
export * from './commands/diffWithPrevious';
1515
export * from './commands/diffWithRevision';
1616
export * from './commands/diffWithWorking';
17+
export * from './commands/externalDiff';
1718
export * from './commands/openChangedFiles';
1819
export * from './commands/openBranchesInRemote';
1920
export * from './commands/openBranchInRemote';

src/commands/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type Commands =
1919
'gitlens.diffWithRevision' |
2020
'gitlens.diffWithWorking' |
2121
'gitlens.diffLineWithWorking' |
22+
'gitlens.externalDiff' |
2223
'gitlens.openChangedFiles' |
2324
'gitlens.openBranchesInRemote' |
2425
'gitlens.openBranchInRemote' |
@@ -61,6 +62,7 @@ export const Commands = {
6162
DiffWithRevision: 'gitlens.diffWithRevision' as Commands,
6263
DiffWithWorking: 'gitlens.diffWithWorking' as Commands,
6364
DiffLineWithWorking: 'gitlens.diffLineWithWorking' as Commands,
65+
ExternalDiff: 'gitlens.externalDiff' as Commands,
6466
OpenChangedFiles: 'gitlens.openChangedFiles' as Commands,
6567
OpenBranchesInRemote: 'gitlens.openBranchesInRemote' as Commands,
6668
OpenBranchInRemote: 'gitlens.openBranchInRemote' as Commands,

src/commands/externalDiff.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
'use strict';
2+
import { commands, SourceControlResourceState, Uri, window } from 'vscode';
3+
import { Command, Commands } from './common';
4+
import { BuiltInCommands } from '../constants';
5+
import { CommandContext } from '../commands';
6+
import { GitService } from '../gitService';
7+
import { Logger } from '../logger';
8+
import { Messages } from '../messages';
9+
10+
enum Status {
11+
INDEX_MODIFIED,
12+
INDEX_ADDED,
13+
INDEX_DELETED,
14+
INDEX_RENAMED,
15+
INDEX_COPIED,
16+
17+
MODIFIED,
18+
DELETED,
19+
UNTRACKED,
20+
IGNORED,
21+
22+
ADDED_BY_US,
23+
ADDED_BY_THEM,
24+
DELETED_BY_US,
25+
DELETED_BY_THEM,
26+
BOTH_ADDED,
27+
BOTH_DELETED,
28+
BOTH_MODIFIED
29+
}
30+
31+
enum ResourceGroupType {
32+
Merge,
33+
Index,
34+
WorkingTree
35+
}
36+
37+
interface Resource extends SourceControlResourceState {
38+
readonly resourceGroupType: ResourceGroupType;
39+
readonly type: Status;
40+
}
41+
42+
class ExternalDiffFile {
43+
constructor(public uri: Uri, public staged: boolean) {
44+
}
45+
}
46+
47+
export interface ExternalDiffCommandArgs {
48+
files?: ExternalDiffFile[];
49+
}
50+
51+
export class ExternalDiffCommand extends Command {
52+
constructor(private git: GitService) {
53+
super(Commands.ExternalDiff);
54+
}
55+
56+
protected async preExecute(context: CommandContext, args: ExternalDiffCommandArgs = {}): Promise<any> {
57+
if (context.type === 'scm-states') {
58+
args = { ...args };
59+
args.files = context.scmResourceStates.map<ExternalDiffFile>((_: Resource) => new ExternalDiffFile(_.resourceUri, _.resourceGroupType === ResourceGroupType.Index));
60+
61+
return this.execute(args);
62+
} else if (context.type === 'scm-groups') {
63+
const isModified = (status: Status): boolean => status === Status.BOTH_MODIFIED || status === Status.INDEX_MODIFIED || status === Status.MODIFIED;
64+
65+
args = { ...args };
66+
args.files = context.scmResourceGroups[0].resourceStates.filter((_: Resource) => isModified(_.type)).map<ExternalDiffFile>((_: Resource) => new ExternalDiffFile(_.resourceUri, _.resourceGroupType === ResourceGroupType.Index));
67+
68+
return this.execute(args);
69+
}
70+
71+
return this.execute(args);
72+
}
73+
74+
async execute(args: ExternalDiffCommandArgs = {}) {
75+
try {
76+
const diffTool = await this.git.getConfig('diff.tool');
77+
if (!diffTool) {
78+
const result = await window.showWarningMessage(`Unable to open file compare because there is no Git diff tool configured`, 'View Git Docs');
79+
if (!result) return undefined;
80+
81+
return commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://git-scm.com/docs/git-config#git-config-difftool'));
82+
}
83+
84+
const repoPath = await this.git.getRepoPathFromUri(undefined);
85+
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to open changed files`);
86+
87+
if (!args.files) {
88+
const status = await this.git.getStatusForRepo(repoPath);
89+
if (status === undefined) return window.showWarningMessage(`Unable to open changed files`);
90+
91+
args.files = [];
92+
93+
for (const file of status.files) {
94+
if (file.indexStatus === 'M') {
95+
args.files.push(new ExternalDiffFile(file.Uri, true));
96+
}
97+
98+
if (file.workTreeStatus === 'M') {
99+
args.files.push(new ExternalDiffFile(file.Uri, false));
100+
}
101+
}
102+
}
103+
104+
for (const file of args.files) {
105+
this.git.openDiffTool(repoPath, file.uri, file.staged);
106+
}
107+
108+
return undefined;
109+
}
110+
catch (ex) {
111+
Logger.error(ex, 'ExternalDiffCommand');
112+
return window.showErrorMessage(`Unable to open external diff. See output channel for more details`);
113+
}
114+
}
115+
}

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { commands, ExtensionContext, extensions, languages, window, workspace } from 'vscode';
44
import { AnnotationController } from './annotations/annotationController';
55
import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands';
6+
import { ExternalDiffCommand } from './commands';
67
import { OpenBranchesInRemoteCommand, OpenBranchInRemoteCommand, OpenCommitInRemoteCommand, OpenFileInRemoteCommand, OpenInRemoteCommand, OpenRepoInRemoteCommand } from './commands';
78
import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands';
89
import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithRevisionCommand, DiffWithWorkingCommand } from './commands';
@@ -99,6 +100,7 @@ export async function activate(context: ExtensionContext) {
99100
context.subscriptions.push(commands.registerTextEditorCommand('gitlens.computingFileAnnotations', () => { }));
100101

101102
context.subscriptions.push(new CloseUnchangedFilesCommand(git));
103+
context.subscriptions.push(new ExternalDiffCommand(git));
102104
context.subscriptions.push(new OpenChangedFilesCommand(git));
103105
context.subscriptions.push(new CopyMessageToClipboardCommand(git));
104106
context.subscriptions.push(new CopyShaToClipboardCommand(git));

src/git/git.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,17 @@ export class Git {
276276
return gitCommand({ cwd: repoPath }, ...params);
277277
}
278278

279+
static difftool_fileDiff(repoPath: string, fileName: string, staged: boolean) {
280+
const params = [`difftool`, `--no-prompt`];
281+
if (staged) {
282+
params.push('--staged');
283+
}
284+
params.push('--');
285+
params.push(fileName);
286+
287+
return gitCommand({ cwd: repoPath }, ...params);
288+
}
289+
279290
static log(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false) {
280291
const params = [...defaultLogParams, `-m`];
281292
if (maxCount && !reverse) {

src/git/models/status.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export interface IGitStatusFile {
2626
status: GitStatusFileStatus;
2727
fileName: string;
2828
originalFileName?: string;
29+
workTreeStatus: GitStatusFileStatus;
30+
indexStatus: GitStatusFileStatus;
2931
}
3032

3133
export interface IGitStatusFileWithCommit extends IGitStatusFile {
@@ -36,7 +38,7 @@ export class GitStatusFile implements IGitStatusFile {
3638

3739
originalFileName?: string;
3840

39-
constructor(public repoPath: string, public status: GitStatusFileStatus, public fileName: string, public staged: boolean, originalFileName?: string) {
41+
constructor(public repoPath: string, public status: GitStatusFileStatus, public workTreeStatus: GitStatusFileStatus, public indexStatus: GitStatusFileStatus, public fileName: string, public staged: boolean, originalFileName?: string) {
4042
this.originalFileName = originalFileName;
4143
}
4244

src/git/parsers/statusParser.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ interface FileStatusEntry {
66
status: GitStatusFileStatus;
77
fileName: string;
88
originalFileName: string;
9+
workTreeStatus: GitStatusFileStatus;
10+
indexStatus: GitStatusFileStatus;
911
}
1012

1113
const aheadStatusV1Regex = /(?:ahead ([0-9]+))/;
@@ -69,7 +71,7 @@ export class GitStatusParser {
6971
else {
7072
entry = this._parseFileEntry(rawStatus, fileName);
7173
}
72-
status.files.push(new GitStatusFile(repoPath, entry.status, entry.fileName, entry.staged, entry.originalFileName));
74+
status.files.push(new GitStatusFile(repoPath, entry.status, entry.workTreeStatus, entry.indexStatus, entry.fileName, entry.staged, entry.originalFileName));
7375
}
7476
}
7577
}
@@ -117,7 +119,7 @@ export class GitStatusParser {
117119
}
118120

119121
if (entry !== undefined) {
120-
status.files.push(new GitStatusFile(repoPath, entry.status, entry.fileName, entry.staged, entry.originalFileName));
122+
status.files.push(new GitStatusFile(repoPath, entry.status, entry.workTreeStatus, entry.indexStatus, entry.fileName, entry.staged, entry.originalFileName));
121123
}
122124
}
123125
}
@@ -131,7 +133,9 @@ export class GitStatusParser {
131133
status: (indexStatus || workTreeStatus || '?') as GitStatusFileStatus,
132134
fileName: fileName,
133135
originalFileName: originalFileName,
134-
staged: !!indexStatus
136+
staged: !!indexStatus,
137+
indexStatus: indexStatus,
138+
workTreeStatus: workTreeStatus
135139
} as FileStatusEntry;
136140
}
137141
}

src/gitService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,12 @@ export class GitService extends Disposable {
10361036
return !!result;
10371037
}
10381038

1039+
openDiffTool(repoPath: string, uri: Uri, staged: boolean) {
1040+
Logger.log(`openDiffTool('${repoPath}', '${uri}', ${staged})`);
1041+
1042+
return Git.difftool_fileDiff(repoPath, uri.fsPath, staged);
1043+
}
1044+
10391045
openDirectoryDiff(repoPath: string, sha1: string, sha2?: string) {
10401046
Logger.log(`openDirectoryDiff('${repoPath}', ${sha1}, ${sha2})`);
10411047

0 commit comments

Comments
 (0)