From cb133d890648adbffcd3c0816690b8c327092550 Mon Sep 17 00:00:00 2001
From: Eric Amodio <eamodio@gmail.com>
Date: Wed, 18 Oct 2017 17:50:03 -0400
Subject: [PATCH] Closes #164 - adds open all changes in difftool Renames
 directory compare commands

---
 CHANGELOG.md                    | 12 ++++++++
 README.md                       | 13 ++++++---
 package.json                    | 16 ++++++++++-
 src/commands/common.ts          | 63 +++++++++++++++++++++++++++--------------
 src/commands/diffDirectory.ts   | 12 +++++++-
 src/quickPicks/commitDetails.ts |  4 +--
 6 files changed, 90 insertions(+), 30 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5f507b..72e596b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased]
+### Added
+- Adds `Open All Changes (with difftool)` command (`gitlens.externalDiffAll`) - opens all working changes with the configured git difftool -- closes [#164](https://github.com/eamodio/vscode-gitlens/issues/164)
+  - Also adds the command to the Source Control group context menu
+
+### Changed
+- Renames `Directory Compare` command (`gitlens.diffDirectory`) to `Compare Directory with Branch...`
+- Renames `Directory Compare with Previous Commit` in quick pick menus to `Compare Directory with Previous Commit`
+- Renames `Directory Compare with Working Tree` in quick pick menus to `Compare Directory with Working Tree`
+- Changes the marketplace keywords for better discoverability
+
+### Fixed
+- Fixes [#163](https://github.com/eamodio/vscode-gitlens/issues/163) - GitLens can cause git locking in the background
 
 ## [5.6.5] - 2017-10-16
 ### Removed
diff --git a/README.md b/README.md
index 55d5c19..298756c 100644
--- a/README.md
+++ b/README.md
@@ -115,7 +115,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
 
 - Provides easy access to the following comparison commands via the `Command Palette` as well as in context via the many provided quick pick menus
 
-- Adds a `Directory Compare` command (`gitlens.diffDirectory`) to open the configured Git difftool to compare directories between branches
+- Adds a `Compare Directory with Branch...` command (`gitlens.diffDirectory`) to open the configured Git difftool to compare directories between branches
 
 - Adds a `Compare File with Branch...` command (`gitlens.diffWithBranch`) to compare the active file with the same file on the selected branch
 
@@ -224,7 +224,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
   ![Commit Details Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-commit-details.png)
 
   - Quickly see the set of files changed in the commit, complete with status indicators for adds, changes, renames, and deletes
-  - Provides entries to `Copy to Clipboard`, `Directory Compare`, `Open Changed Files`, `Open File in <remote-service>` when available, and more
+  - Provides entries to `Copy to Clipboard`, `Compare Directory with...`, `Open Changed Files`, `Open File in <remote-service>` when available, and more
   - Navigate back to the previous quick pick menu via `alt+left arrow`, if available
   - Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
   - Use the `alt+right arrow` shortcut on a file entry in the `Changed Files` section to preview the comparison of the current revision with the previous one
@@ -261,20 +261,25 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
     ![Stash Details Quick Pick Menu](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-stash-details.png)
 
     - Quickly see the set of files changed in the stash, complete with status indicators for adds, changes, renames, and deletes
-    - Provides entries to `Copy Message to Clipboard`, `Directory Compare`, and `Open Changed Files`
+    - Provides entries to `Copy Message to Clipboard`, `Compare Directory with...`, and `Open Changed Files`
     - Provides entries to `Apply Stashed Changes` and `Delete Stashed Changes` — both require a confirmation
     - Navigate back to the previous quick pick menu via `alt+left arrow`, if available
     - Use the `alt+right arrow` shortcut on an entry to execute it without closing the quick pick menu, if possible — commands that open windows outside of VS Code will still close the quick pick menu unless [`"gitlens.advanced.quickPick.closeOnFocusOut": false`](#extension-settings) is set
     - Use the `alt+right arrow` shortcut on a file entry in the `Changed Files` section to  preview the comparison of the current revision with the previous one
 
 - Adds a `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`) with a shortcut of `alt+-` to quickly get back to where you were when the last GitLens quick pick menu closed
-
+7
 ### And More
 
 - Adds a `Copy Commit ID to Clipboard` command (`gitlens.copyShaToClipboard`) to copy the commit id (sha) of the active line to the clipboard or from the most recent commit to the current branch, if there is no active editor
 
 - Adds a `Copy Commit Message to Clipboard` command (`gitlens.copyMessageToClipboard`) to copy the commit message of the active line to the clipboard or from the most recent commit to the current branch, if there is no active editor
 
+- Adds a `Open Changes (with difftool)` command (`gitlens.externalDiff`) to the source control group and source control resource context menus to open the changes of a file or set of files with the configured git difftool
+
+- Adds a `Open All Changes (with difftool)` command (`gitlens.externalDiffAll`) to open all working changes with the configured git difftool
+  - Also adds the command to the Source Control group context menu
+
 - Adds a `Open Changed Files` command (`gitlens.openChangedFiles`) to open any files with working tree changes
 
 - Adds a `Close Unchanged Files` command (`gitlens.closeUnchangedFiles`) to close any files without working tree changes
diff --git a/package.json b/package.json
index e8aed1a..93c1e4b 100644
--- a/package.json
+++ b/package.json
@@ -896,7 +896,7 @@
         "commands": [
             {
                 "command": "gitlens.diffDirectory",
-                "title": "Directory Compare",
+                "title": "Compare Directory with Branch...",
                 "category": "GitLens"
             },
             {
@@ -1114,6 +1114,11 @@
                 "category": "GitLens"
             },
             {
+                "command": "gitlens.externalDiffAll",
+                "title": "Open All Changes (with difftool)",
+                "category": "GitLens"
+            },
+            {
                 "command": "gitlens.resetSuppressedWarnings",
                 "title": "Reset Suppressed Warnings",
                 "category": "GitLens"
@@ -1235,6 +1240,10 @@
                     "when": "gitlens:isBlameable"
                 },
                 {
+                    "command": "gitlens.externalDiffAll",
+                    "when": "gitlens:enabled"
+                },
+                {
                     "command": "gitlens.showFileBlame",
                     "when": "gitlens:isBlameable"
                 },
@@ -1570,6 +1579,11 @@
                     "group": "2_gitlens@3"
                 },
                 {
+                    "command": "gitlens.externalDiffAll",
+                    "when": "gitlens:enabled",
+                    "group": "2_gitlens@4"
+                },
+                {
                     "command": "gitlens.stashSave",
                     "when": "gitlens:enabled",
                     "group": "3_gitlens@1"
diff --git a/src/commands/common.ts b/src/commands/common.ts
index 3a76153..b41f99a 100644
--- a/src/commands/common.ts
+++ b/src/commands/common.ts
@@ -11,6 +11,7 @@ export enum Commands {
     CopyMessageToClipboard = 'gitlens.copyMessageToClipboard',
     CopyShaToClipboard = 'gitlens.copyShaToClipboard',
     DiffDirectory = 'gitlens.diffDirectory',
+    ExternalDiffAll = 'gitlens.externalDiffAll',
     DiffWith = 'gitlens.diffWith',
     DiffWithBranch = 'gitlens.diffWithBranch',
     DiffWithNext = 'gitlens.diffWithNext',
@@ -61,6 +62,7 @@ export interface CommandContextParsingOptions {
 }
 
 export interface CommandBaseContext {
+    command: string;
     editor?: TextEditor;
     uri?: Uri;
 }
@@ -134,10 +136,18 @@ export abstract class Command extends Disposable {
 
     private _disposable: Disposable;
 
-    constructor(protected command: Commands) {
+    constructor(command: Commands | Commands[]) {
         super(() => this.dispose());
 
-        this._disposable = commands.registerCommand(command, this._execute, this);
+        if (!Array.isArray(command)) {
+            command = [command];
+        }
+
+        const subscriptions = [];
+        for (const cmd of command) {
+            subscriptions.push(commands.registerCommand(cmd, (...args: any[]) => this._execute(cmd, ...args), this));
+        }
+        this._disposable = Disposable.from(...subscriptions);
     }
 
     dispose() {
@@ -150,14 +160,14 @@ export abstract class Command extends Disposable {
 
     abstract execute(...args: any[]): any;
 
-    protected _execute(...args: any[]): any {
-        Telemetry.trackEvent(this.command);
+    protected _execute(command: string, ...args: any[]): any {
+        Telemetry.trackEvent(command);
 
-        const [context, rest] = Command._parseContext(this.contextParsingOptions, ...args);
+        const [context, rest] = Command._parseContext(command, this.contextParsingOptions, ...args);
         return this.preExecute(context, ...rest);
     }
 
-    private static _parseContext(options: CommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
+    private static _parseContext(command: string, options: CommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
         let editor: TextEditor | undefined = undefined;
 
         let firstArg = args[0];
@@ -169,12 +179,12 @@ export abstract class Command extends Disposable {
 
         if (options.uri && (firstArg === undefined || firstArg instanceof Uri)) {
             const [uri, ...rest] = args as [Uri, any];
-            return [{ type: 'uri', editor: editor, uri: uri }, rest];
+            return [{ command: command, type: 'uri', editor: editor, uri: uri }, rest];
         }
 
         if (firstArg instanceof ExplorerNode) {
             const [node, ...rest] = args as [ExplorerNode, any];
-            return [{ type: 'view', node: node, uri: node.uri }, rest];
+            return [{ command: command, type: 'view', node: node, uri: node.uri }, rest];
         }
 
         if (isScmResourceState(firstArg)) {
@@ -187,7 +197,7 @@ export abstract class Command extends Disposable {
                 states.push(arg);
             }
 
-            return [{ type: 'scm-states', scmResourceStates: states, uri: states[0].resourceUri }, args.slice(count)];
+            return [{ command: command, type: 'scm-states', scmResourceStates: states, uri: states[0].resourceUri }, args.slice(count)];
         }
 
         if (isScmResourceGroup(firstArg)) {
@@ -200,10 +210,10 @@ export abstract class Command extends Disposable {
                 groups.push(arg);
             }
 
-            return [{ type: 'scm-groups', scmResourceGroups: groups }, args.slice(count)];
+            return [{ command: command, type: 'scm-groups', scmResourceGroups: groups }, args.slice(count)];
         }
 
-        return [{ type: 'unknown', editor: editor }, args];
+        return [{ command: command, type: 'unknown', editor: editor }, args];
     }
 }
 
@@ -211,7 +221,7 @@ export abstract class ActiveEditorCommand extends Command {
 
     protected readonly contextParsingOptions: CommandContextParsingOptions = { editor: true, uri: true };
 
-    constructor(public readonly command: Commands) {
+    constructor(command: Commands | Commands[]) {
         super(command);
     }
 
@@ -219,8 +229,8 @@ export abstract class ActiveEditorCommand extends Command {
         return this.execute(context.editor, context.uri, ...args);
     }
 
-    protected _execute(...args: any[]): any {
-        return super._execute(window.activeTextEditor, ...args);
+    protected _execute(command: string, ...args: any[]): any {
+        return super._execute(command, window.activeTextEditor, ...args);
     }
 
     abstract execute(editor?: TextEditor, ...args: any[]): any;
@@ -233,16 +243,16 @@ export function getLastCommand() {
 
 export abstract class ActiveEditorCachedCommand extends ActiveEditorCommand {
 
-    constructor(public readonly command: Commands) {
+    constructor(command: Commands | Commands[]) {
         super(command);
     }
 
-    protected _execute(...args: any[]): any {
+    protected _execute(command: string, ...args: any[]): any {
         lastCommand = {
-            command: this.command,
+            command: command,
             args: args
         };
-        return super._execute(...args);
+        return super._execute(command, ...args);
     }
 
     abstract execute(editor: TextEditor, ...args: any[]): any;
@@ -252,17 +262,26 @@ export abstract class EditorCommand extends Disposable {
 
     private _disposable: Disposable;
 
-    constructor(public readonly command: Commands) {
+    constructor(command: Commands | Commands[]) {
         super(() => this.dispose());
-        this._disposable = commands.registerTextEditorCommand(command, this._execute, this);
+
+        if (!Array.isArray(command)) {
+            command = [command];
+        }
+
+        const subscriptions = [];
+        for (const cmd of command) {
+            subscriptions.push(commands.registerCommand(cmd, (editor: TextEditor, edit: TextEditorEdit, ...args: any[]) => this._execute(cmd, editor, edit, ...args), this));
+        }
+        this._disposable = Disposable.from(...subscriptions);
     }
 
     dispose() {
         this._disposable && this._disposable.dispose();
     }
 
-    private _execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any {
-        Telemetry.trackEvent(this.command);
+    private _execute(command: string, editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any {
+        Telemetry.trackEvent(command);
         return this.execute(editor, edit, ...args);
     }
 
diff --git a/src/commands/diffDirectory.ts b/src/commands/diffDirectory.ts
index cff4e4a..9910006 100644
--- a/src/commands/diffDirectory.ts
+++ b/src/commands/diffDirectory.ts
@@ -2,6 +2,7 @@
 import { Iterables } from '../system';
 import { commands, TextEditor, Uri, window } from 'vscode';
 import { ActiveEditorCommand, Commands, getCommandUri } from './common';
+import { CommandContext } from '../commands';
 import { BuiltInCommands, GlyphChars } from '../constants';
 import { GitService } from '../gitService';
 import { Logger } from '../logger';
@@ -16,7 +17,16 @@ export interface DiffDirectoryCommandCommandArgs {
 export class DiffDirectoryCommand extends ActiveEditorCommand {
 
     constructor(private git: GitService) {
-        super(Commands.DiffDirectory);
+        super([Commands.DiffDirectory, Commands.ExternalDiffAll]);
+    }
+
+    protected async preExecute(context: CommandContext, args: DiffDirectoryCommandCommandArgs = {}): Promise<any> {
+        if (context.command === Commands.ExternalDiffAll) {
+            args.shaOrBranch1 = 'HEAD';
+            args.shaOrBranch2 = undefined;
+        }
+
+        return this.execute(context.editor, context.uri, args);
     }
 
     async execute(editor?: TextEditor, uri?: Uri, args: DiffDirectoryCommandCommandArgs = {}): Promise<any> {
diff --git a/src/quickPicks/commitDetails.ts b/src/quickPicks/commitDetails.ts
index 60d3621..f5d79a3 100644
--- a/src/quickPicks/commitDetails.ts
+++ b/src/quickPicks/commitDetails.ts
@@ -161,7 +161,7 @@ export class CommitDetailsQuickPick {
             }
 
             items.splice(index++, 0, new CommandQuickPickItem({
-                label: `$(git-compare) Directory Compare with Previous Commit`,
+                label: `$(git-compare) Compare Directory with Previous Commit`,
                 description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.previousShortSha || `${commit.shortSha}^`} ${GlyphChars.Space} $(git-compare) ${GlyphChars.Space} $(git-commit) ${commit.shortSha}`
             }, Commands.DiffDirectory, [
                     commit.uri,
@@ -173,7 +173,7 @@ export class CommitDetailsQuickPick {
         }
 
         items.splice(index++, 0, new CommandQuickPickItem({
-            label: `$(git-compare) Directory Compare with Working Tree`,
+            label: `$(git-compare) Compare Directory with Working Tree`,
             description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(git-commit) ${commit.shortSha} ${GlyphChars.Space} $(git-compare) ${GlyphChars.Space} $(file-directory) Working Tree`
         }, Commands.DiffDirectory, [
                 uri,