Skip to content

Commit

Permalink
#293 Added an "Open a Terminal for this Repository" button onto the G…
Browse files Browse the repository at this point in the history
…it Graph View Control Bar.
  • Loading branch information
mhutchie committed Jun 29, 2020
1 parent 3648fae commit ac27ccb
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 53 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@
"git-graph.integratedTerminalShell": {
"type": "string",
"default": "",
"description": "Specifies the path and filename of the Shell executable to be used by the Visual Studio Code Integrated Terminal, when opened by Git Graph during Interactive Rebase's. For example, to use Git Bash on Windows this setting would commonly be set to \"C:\\Program Files\\Git\\bin\\bash.exe\". If this setting is left blank, the default Shell is used.",
"description": "Specifies the path and filename of the Shell executable to be used by the Visual Studio Code Integrated Terminal, when it is opened by Git Graph. For example, to use Git Bash on Windows this setting would commonly be set to \"C:\\Program Files\\Git\\bin\\bash.exe\". If this setting is left blank, the default Shell is used.",
"scope": "machine"
},
"git-graph.loadMoreCommits": {
Expand Down
36 changes: 27 additions & 9 deletions src/dataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getConfig } from './config';
import { Event } from './event';
import { Logger } from './logger';
import { CommitOrdering, DateType, ErrorInfo, GitCommit, GitCommitDetails, GitCommitStash, GitConfigLocation, GitFileChange, GitFileStatus, GitPushBranchMode, GitRepoSettings, GitResetMode, GitSignatureStatus, GitStash, MergeActionOn, RebaseActionOn, SquashMessageFormat } from './types';
import { abbrevCommit, constructIncompatibleGitVersionMessage, getPathFromStr, getPathFromUri, GitExecutable, isGitAtLeastVersion, realpath, runGitCommandInNewTerminal, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED } from './utils';
import { abbrevCommit, constructIncompatibleGitVersionMessage, getPathFromStr, getPathFromUri, GitExecutable, isGitAtLeastVersion, openGitTerminal, realpath, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED } from './utils';

const EOL_REGEX = /\r\n|\r|\n/g;
const INVALID_BRANCH_REGEX = /^\(.* .*\)$/;
Expand Down Expand Up @@ -843,14 +843,11 @@ export class DataSource implements vscode.Disposable {
*/
public rebase(repo: string, obj: string, actionOn: RebaseActionOn, ignoreDate: boolean, interactive: boolean) {
if (interactive) {
return new Promise<ErrorInfo>(resolve => {
if (this.gitExecutable === null) return resolve(UNABLE_TO_FIND_GIT_MSG);

runGitCommandInNewTerminal(repo, this.gitExecutable.path,
'rebase --interactive ' + (actionOn === RebaseActionOn.Branch ? obj.replace(/'/g, '"\'"') : obj),
'Git Rebase on "' + (actionOn === RebaseActionOn.Branch ? obj : abbrevCommit(obj)) + '"');
setTimeout(() => resolve(null), 1000);
});
return this.openGitTerminal(
repo,
'rebase --interactive ' + (actionOn === RebaseActionOn.Branch ? obj.replace(/'/g, '"\'"') : obj),
'Rebase on "' + (actionOn === RebaseActionOn.Branch ? obj : abbrevCommit(obj)) + '"'
);
} else {
let args = ['rebase', obj];
if (ignoreDate) args.push('--ignore-date');
Expand Down Expand Up @@ -1053,6 +1050,27 @@ export class DataSource implements vscode.Disposable {
}


/* Public Utils */

/**
* Open a new terminal, set up the Git executable, and optionally run a command.
* @param repo The path of the repository.
* @param command The command to run.
* @param name The name for the terminal.
* @returns The ErrorInfo from opening the terminal.
*/
public openGitTerminal(repo: string, command: string | null, name: string) {
return new Promise<ErrorInfo>((resolve) => {
if (this.gitExecutable === null) {
resolve(UNABLE_TO_FIND_GIT_MSG);
} else {
openGitTerminal(repo, this.gitExecutable.path, command, name);
setTimeout(() => resolve(null), 1000);
}
});
}


/* Private Data Providers */

/**
Expand Down
9 changes: 8 additions & 1 deletion src/gitGraphView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Logger } from './logger';
import { RepoFileWatcher } from './repoFileWatcher';
import { RepoManager } from './repoManager';
import { ErrorInfo, GitConfigLocation, GitGraphViewInitialState, GitPushBranchMode, GitRepoSet, LoadGitGraphViewTo, RefLabelAlignment, RequestMessage, ResponseMessage, TabIconColourTheme } from './types';
import { archive, copyFilePathToClipboard, copyToClipboard, createPullRequest, getNonce, openExtensionSettings, openFile, showErrorMessage, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, viewDiff, viewFileAtRevision, viewScm } from './utils';
import { archive, copyFilePathToClipboard, copyToClipboard, createPullRequest, getNonce, getRepoName, openExtensionSettings, openFile, showErrorMessage, UNABLE_TO_FIND_GIT_MSG, UNCOMMITTED, viewDiff, viewFileAtRevision, viewScm } from './utils';

/**
* Manages the Git Graph View.
Expand Down Expand Up @@ -422,6 +422,12 @@ export class GitGraphView implements vscode.Disposable {
error: await openFile(msg.repo, msg.filePath)
});
break;
case 'openTerminal':
this.sendMessage({
command: 'openTerminal',
error: await this.dataSource.openGitTerminal(msg.repo, null, getRepoName(msg.repo))
});
break;
case 'popStash':
this.sendMessage({
command: 'popStash',
Expand Down Expand Up @@ -634,6 +640,7 @@ export class GitGraphView implements vscode.Disposable {
<span id="branchControl"><span class="unselectable">Branches: </span><div id="branchDropdown" class="dropdown"></div></span>
<label id="showRemoteBranchesControl"><input type="checkbox" id="showRemoteBranchesCheckbox" tabindex="-1"><span class="customCheckbox"></span>Show Remote Branches</label>
<div id="findBtn" title="Find"></div>
<div id="terminalBtn" title="Open a Terminal for this Repository"></div>
<div id="settingsBtn" title="Repository Settings"></div>
<div id="fetchBtn"></div>
<div id="refreshBtn"></div>
Expand Down
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,13 @@ export interface ResponseOpenFile extends ResponseWithErrorInfo {
readonly command: 'openFile';
}

export interface RequestOpenTerminal extends RepoRequest {
readonly command: 'openTerminal';
}
export interface ResponseOpenTerminal extends ResponseWithErrorInfo {
readonly command: 'openTerminal';
}

export interface RequestPopStash extends RepoRequest {
readonly command: 'popStash';
readonly selector: string;
Expand Down Expand Up @@ -1099,6 +1106,7 @@ export type RequestMessage =
| RequestMerge
| RequestOpenExtensionSettings
| RequestOpenFile
| RequestOpenTerminal
| RequestPopStash
| RequestPruneRemote
| RequestPullBranch
Expand Down Expand Up @@ -1154,6 +1162,7 @@ export type ResponseMessage =
| ResponseMerge
| ResponseOpenExtensionSettings
| ResponseOpenFile
| ResponseOpenTerminal
| ResponsePopStash
| ResponsePruneRemote
| ResponsePullBranch
Expand Down
20 changes: 13 additions & 7 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,23 +368,29 @@ export function viewScm(): Thenable<ErrorInfo> {
}

/**
* Open a new terminal and run the specified Git command.
* Open a new terminal, set up the Git executable, and optionally run a command.
* @param cwd The working directory for the terminal.
* @param gitPath The path of the Git executable.
* @param command The Git command to run.
* @param command The command to run.
* @param name The name for the terminal.
*/
export function runGitCommandInNewTerminal(cwd: string, gitPath: string, command: string, name: string) {
export function openGitTerminal(cwd: string, gitPath: string, command: string | null, name: string) {
let p = process.env['PATH'] || '', sep = isWindows() ? ';' : ':';
if (p !== '' && !p.endsWith(sep)) p += sep;
p += path.dirname(gitPath);

let options: vscode.TerminalOptions = { cwd: cwd, name: name, env: { 'PATH': p } };
let shell = getConfig().integratedTerminalShell;
const options: vscode.TerminalOptions = {
cwd: cwd,
name: 'Git Graph: ' + name,
env: { 'PATH': p }
};
const shell = getConfig().integratedTerminalShell;
if (shell !== '') options.shellPath = shell;

let terminal = vscode.window.createTerminal(options);
terminal.sendText('git ' + command);
const terminal = vscode.window.createTerminal(options);
if (command !== null) {
terminal.sendText('git ' + command);
}
terminal.show();
}

Expand Down
1 change: 1 addition & 0 deletions tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1816,6 +1816,7 @@ describe('Config', () => {
// Run
const value = config.integratedTerminalShell;

// Assert
expect(workspaceConfiguration.get).toBeCalledWith('integratedTerminalShell', '');
expect(value).toBe('/path/to/shell');
});
Expand Down
12 changes: 6 additions & 6 deletions tests/dataSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4512,28 +4512,28 @@ describe('DataSource', () => {

it('Should launch the interactive rebase of the current branch on a branch in a terminal', async () => {
// Setup
const spyOnRunGitCommandInNewTerminal = jest.spyOn(utils, 'runGitCommandInNewTerminal');
spyOnRunGitCommandInNewTerminal.mockReturnValueOnce();
const spyOnOpenGitTerminal = jest.spyOn(utils, 'openGitTerminal');
spyOnOpenGitTerminal.mockReturnValueOnce();

// Run
const result = await dataSource.rebase('/path/to/repo', 'develop', RebaseActionOn.Branch, false, true);

// Assert
expect(result).toBe(null);
expect(spyOnRunGitCommandInNewTerminal).toBeCalledWith('/path/to/repo', '/path/to/git', 'rebase --interactive develop', 'Git Rebase on "develop"');
expect(spyOnOpenGitTerminal).toBeCalledWith('/path/to/repo', '/path/to/git', 'rebase --interactive develop', 'Rebase on "develop"');
});

it('Should launch the interactive rebase of the current branch on a commit in a terminal', async () => {
// Setup
const spyOnRunGitCommandInNewTerminal = jest.spyOn(utils, 'runGitCommandInNewTerminal');
spyOnRunGitCommandInNewTerminal.mockReturnValueOnce();
const spyOnOpenGitTerminal = jest.spyOn(utils, 'openGitTerminal');
spyOnOpenGitTerminal.mockReturnValueOnce();

// Run
const result = await dataSource.rebase('/path/to/repo', '1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', RebaseActionOn.Commit, false, true);

// Assert
expect(result).toBe(null);
expect(spyOnRunGitCommandInNewTerminal).toBeCalledWith('/path/to/repo', '/path/to/git', 'rebase --interactive 1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', 'Git Rebase on "1a2b3c4d"');
expect(spyOnOpenGitTerminal).toBeCalledWith('/path/to/repo', '/path/to/git', 'rebase --interactive 1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b', 'Rebase on "1a2b3c4d"');
});

it('Should return the "Unable to Find Git" error message when no git executable is known', async () => {
Expand Down
51 changes: 35 additions & 16 deletions tests/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { EventEmitter } from '../src/event';
import { ExtensionState } from '../src/extensionState';
import { Logger } from '../src/logger';
import { GitFileStatus, PullRequestProvider } from '../src/types';
import { abbrevCommit, abbrevText, archive, constructIncompatibleGitVersionMessage, copyFilePathToClipboard, copyToClipboard, createPullRequest, evalPromises, findGit, getGitExecutable, getNonce, getPathFromStr, getPathFromUri, getRelativeTimeDiff, getRepoName, GitExecutable, isGitAtLeastVersion, isPathInWorkspace, openExtensionSettings, openFile, pathWithTrailingSlash, realpath, resolveToSymbolicPath, runGitCommandInNewTerminal, showErrorMessage, showInformationMessage, UNCOMMITTED, viewDiff, viewFileAtRevision, viewScm } from '../src/utils';
import { abbrevCommit, abbrevText, archive, constructIncompatibleGitVersionMessage, copyFilePathToClipboard, copyToClipboard, createPullRequest, evalPromises, findGit, getGitExecutable, getNonce, getPathFromStr, getPathFromUri, getRelativeTimeDiff, getRepoName, GitExecutable, isGitAtLeastVersion, isPathInWorkspace, openExtensionSettings, openFile, openGitTerminal, pathWithTrailingSlash, realpath, resolveToSymbolicPath, showErrorMessage, showInformationMessage, UNCOMMITTED, viewDiff, viewFileAtRevision, viewScm } from '../src/utils';

let extensionContext = vscode.mocks.extensionContext;
let terminal = vscode.mocks.terminal;
Expand Down Expand Up @@ -1187,7 +1187,7 @@ describe('viewScm', () => {
});
});

describe('runGitCommandInNewTerminal', () => {
describe('openGitTerminal', () => {
let ostype: string | undefined, path: string | undefined, platform: NodeJS.Platform;
beforeEach(() => {
ostype = process.env.OSTYPE;
Expand All @@ -1203,20 +1203,39 @@ describe('runGitCommandInNewTerminal', () => {
Object.defineProperty(process, 'platform', { value: platform });
});

it('Should open a new terminal', () => {
// Setup
workspaceConfiguration.get.mockImplementationOnce((_, defaultValue) => defaultValue); // integratedTerminalShell

// Run
openGitTerminal('/path/to/repo', '/path/to/git/git', null, 'Name');

// Assert
expect(vscode.window.createTerminal).toHaveBeenCalledWith({
cwd: '/path/to/repo',
env: {
PATH: '/path/to/executable:/path/to/git'
},
name: 'Git Graph: Name'
});
expect(terminal.sendText).toHaveBeenCalledTimes(0);
expect(terminal.show).toHaveBeenCalled();
});

it('Should open a new terminal and run the git command', () => {
// Setup
workspaceConfiguration.get.mockImplementationOnce((_, defaultValue) => defaultValue); // integratedTerminalShell

// Run
runGitCommandInNewTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');
openGitTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');

// Assert
expect(vscode.window.createTerminal).toHaveBeenCalledWith({
cwd: '/path/to/repo',
env: {
PATH: '/path/to/executable:/path/to/git'
},
name: 'Name'
name: 'Git Graph: Name'
});
expect(terminal.sendText).toHaveBeenCalledWith('git rebase');
expect(terminal.show).toHaveBeenCalled();
Expand All @@ -1228,15 +1247,15 @@ describe('runGitCommandInNewTerminal', () => {
process.env.PATH = '';

// Run
runGitCommandInNewTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');
openGitTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');

// Assert
expect(vscode.window.createTerminal).toHaveBeenCalledWith({
cwd: '/path/to/repo',
env: {
PATH: '/path/to/git'
},
name: 'Name'
name: 'Git Graph: Name'
});
expect(terminal.sendText).toHaveBeenCalledWith('git rebase');
expect(terminal.show).toHaveBeenCalled();
Expand All @@ -1247,15 +1266,15 @@ describe('runGitCommandInNewTerminal', () => {
workspaceConfiguration.get.mockReturnValueOnce('/path/to/shell'); // integratedTerminalShell

// Run
runGitCommandInNewTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');
openGitTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');

// Assert
expect(vscode.window.createTerminal).toHaveBeenCalledWith({
cwd: '/path/to/repo',
env: {
PATH: '/path/to/executable:/path/to/git'
},
name: 'Name',
name: 'Git Graph: Name',
shellPath: '/path/to/shell'
});
expect(terminal.sendText).toHaveBeenCalledWith('git rebase');
Expand All @@ -1268,15 +1287,15 @@ describe('runGitCommandInNewTerminal', () => {
Object.defineProperty(process, 'platform', { value: 'win32' });

// Run
runGitCommandInNewTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');
openGitTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');

// Assert
expect(vscode.window.createTerminal).toHaveBeenCalledWith({
cwd: '/path/to/repo',
env: {
PATH: '/path/to/executable;/path/to/git'
},
name: 'Name'
name: 'Git Graph: Name'
});
expect(terminal.sendText).toHaveBeenCalledWith('git rebase');
expect(terminal.show).toHaveBeenCalled();
Expand All @@ -1288,15 +1307,15 @@ describe('runGitCommandInNewTerminal', () => {
process.env.OSTYPE = 'cygwin';

// Run
runGitCommandInNewTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');
openGitTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');

// Assert
expect(vscode.window.createTerminal).toHaveBeenCalledWith({
cwd: '/path/to/repo',
env: {
PATH: '/path/to/executable;/path/to/git'
},
name: 'Name'
name: 'Git Graph: Name'
});
expect(terminal.sendText).toHaveBeenCalledWith('git rebase');
expect(terminal.show).toHaveBeenCalled();
Expand All @@ -1308,15 +1327,15 @@ describe('runGitCommandInNewTerminal', () => {
process.env.OSTYPE = 'msys';

// Run
runGitCommandInNewTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');
openGitTerminal('/path/to/repo', '/path/to/git/git', 'rebase', 'Name');

// Assert
expect(vscode.window.createTerminal).toHaveBeenCalledWith({
cwd: '/path/to/repo',
env: {
PATH: '/path/to/executable;/path/to/git'
},
name: 'Name'
name: 'Git Graph: Name'
});
expect(terminal.sendText).toHaveBeenCalledWith('git rebase');
expect(terminal.show).toHaveBeenCalled();
Expand Down Expand Up @@ -1955,15 +1974,15 @@ describe('isGitAtLeastVersion', () => {
expect(result2).toBe(true);
});

it('Should return TRUE if executable version is invalid', ()=>{
it('Should return TRUE if executable version is invalid', () => {
// Run
const result = isGitAtLeastVersion({ version: 'a2.4.6', path: '' }, '1.4.6');

// Assert
expect(result).toBe(true);
});

it('Should return TRUE if version is invalid', ()=>{
it('Should return TRUE if version is invalid', () => {
// Run
const result = isGitAtLeastVersion({ version: '2.4.6', path: '' }, 'a1.4.6');

Expand Down
Loading

0 comments on commit ac27ccb

Please sign in to comment.