Skip to content

Commit

Permalink
Adds quick git commands (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
eamodio committed Jul 11, 2019
1 parent 2d3bd94 commit ee4b5a1
Show file tree
Hide file tree
Showing 17 changed files with 1,148 additions and 4 deletions.
9 changes: 9 additions & 0 deletions package.json
Expand Up @@ -2076,6 +2076,11 @@
"title": "Toggle Git Code Lens",
"category": "GitLens"
},
{
"command": "gitlens.gitCommands",
"title": "Git Commands",
"category": "GitLens"
},
{
"command": "gitlens.switchMode",
"title": "Switch Mode",
Expand Down Expand Up @@ -3174,6 +3179,10 @@
"command": "gitlens.toggleCodeLens",
"when": "gitlens:enabled && gitlens:canToggleCodeLens"
},
{
"command": "gitlens.gitCommands",
"when": "gitlens:enabled"
},
{
"command": "gitlens.switchMode",
"when": "gitlens:enabled"
Expand Down
1 change: 1 addition & 0 deletions src/commands.ts
Expand Up @@ -28,6 +28,7 @@ export * from './commands/openFileRevisionFrom';
export * from './commands/openInRemote';
export * from './commands/openRepoInRemote';
export * from './commands/openWorkingFile';
export * from './commands/gitCommands';
export * from './commands/repositories';
export * from './commands/resetSuppressedWarnings';
export * from './commands/searchCommits';
Expand Down
1 change: 1 addition & 0 deletions src/commands/common.ts
Expand Up @@ -68,6 +68,7 @@ export enum Commands {
OpenWorkingFile = 'gitlens.openWorkingFile',
PullRepositories = 'gitlens.pullRepositories',
PushRepositories = 'gitlens.pushRepositories',
GitCommands = 'gitlens.gitCommands',
ResetSuppressedWarnings = 'gitlens.resetSuppressedWarnings',
ShowCommitInView = 'gitlens.showCommitInView',
SearchCommits = 'gitlens.showCommitSearch',
Expand Down
189 changes: 189 additions & 0 deletions src/commands/gitCommands.ts
@@ -0,0 +1,189 @@
'use strict';
import { Disposable, QuickInputButtons, QuickPickItem, window } from 'vscode';
import { command, Command, Commands } from './common';
import { log } from '../system';
import { CherryPickQuickCommand } from './quick/cherry-pick';
import { QuickCommandBase, QuickPickStep } from './quick/quickCommand';
import { FetchQuickCommand } from './quick/fetch';
import { MergeQuickCommand } from './quick/merge';
import { PushQuickCommand } from './quick/push';
import { PullQuickCommand } from './quick/pull';
import { CheckoutQuickCommand } from './quick/checkout';
import { RebaseQuickCommand } from './quick/rebase';

@command()
export class GitCommandsCommand extends Command {
constructor() {
super(Commands.GitCommands);
}

@log({ args: false, correlate: true, singleLine: true, timed: false })
async execute() {
const commands: QuickCommandBase[] = [
new CheckoutQuickCommand(),
new CherryPickQuickCommand(),
new MergeQuickCommand(),
new FetchQuickCommand(),
new PullQuickCommand(),
new PushQuickCommand(),
new RebaseQuickCommand()
];

const quickpick = window.createQuickPick();
quickpick.ignoreFocusOut = true;

let inCommand: QuickCommandBase | undefined;

function showCommand(command: QuickPickStep | undefined) {
if (command === undefined) {
const previousLabel = inCommand && inCommand.label;
inCommand = undefined;

quickpick.buttons = [];
quickpick.title = 'GitLens';
quickpick.placeholder = 'Select command...';
quickpick.canSelectMany = false;
quickpick.items = commands;

if (previousLabel) {
const active = quickpick.items.find(i => i.label === previousLabel);
if (active) {
quickpick.activeItems = [active];
}
}
}
else {
quickpick.buttons = command.buttons || [QuickInputButtons.Back];
quickpick.title = command.title;
quickpick.placeholder = command.placeholder;
quickpick.canSelectMany = Boolean(command.multiselect);

quickpick.items = command.items;

if (quickpick.canSelectMany) {
quickpick.selectedItems = command.selectedItems || quickpick.items.filter(i => i.picked);
quickpick.activeItems = quickpick.selectedItems;
}
else {
quickpick.activeItems = command.selectedItems || quickpick.items.filter(i => i.picked);
}

// // BUG: https://github.com/microsoft/vscode/issues/75046
// // If we can multiselect, then ensure the selectedItems gets reset (otherwise it could end up included the current selected items)
// if (quickpick.canSelectMany && quickpick.selectedItems.length !== 0) {
// quickpick.selectedItems = [];
// }
}
}

async function next(command: QuickCommandBase, items: QuickPickItem[] | undefined) {
quickpick.busy = true;
// quickpick.enabled = false;

const next = await command.next(items);
if (next.done) {
return false;
}

quickpick.value = '';
showCommand(next.value);

// quickpick.enabled = true;
quickpick.busy = false;

return true;
}

showCommand(undefined);

const disposables: Disposable[] = [];

try {
void (await new Promise<void>(resolve => {
disposables.push(
quickpick.onDidHide(() => resolve()),
quickpick.onDidTriggerButton(async e => {
if (e === QuickInputButtons.Back) {
quickpick.value = '';
if (inCommand !== undefined) {
showCommand(await inCommand.previous());
}

return;
}

const step = inCommand && inCommand.value;
if (step === undefined || step.onDidClickButton === undefined) return;

step.onDidClickButton(quickpick, e);
}),
quickpick.onDidChangeValue(async e => {
if (quickpick.canSelectMany && e === ' ') {
quickpick.value = '';
quickpick.selectedItems =
quickpick.selectedItems.length === quickpick.items.length ? [] : quickpick.items;

return;
}

if (e.endsWith(' ')) {
if (quickpick.canSelectMany && quickpick.selectedItems.length !== 0) {
return;
}

const cmd = quickpick.value.toLowerCase().trim();

let items;
if (inCommand === undefined) {
const command = commands.find(c => c.label.toLowerCase() === cmd);
if (command === undefined) return;

inCommand = command;
}
else {
const step = inCommand.value;
if (step === undefined) return;

const item = step.items.find(i => i.label.toLowerCase() === cmd);
if (item === undefined) return;

items = [item];
}

if (!(await next(inCommand, items))) {
resolve();
}
}
}),
quickpick.onDidAccept(async () => {
let items = quickpick.selectedItems;
if (items.length === 0) {
if (!quickpick.canSelectMany || quickpick.activeItems.length === 0) return;

items = quickpick.activeItems;
}

if (inCommand === undefined) {
const command = items[0];
if (!QuickCommandBase.is(command)) return;

inCommand = command;
}

if (!(await next(inCommand, items as QuickPickItem[]))) {
resolve();
}
})
);

quickpick.show();
}));

quickpick.hide();
}
finally {
quickpick.dispose();
disposables.forEach(d => d.dispose());
}
}
}
118 changes: 118 additions & 0 deletions src/commands/quick/checkout.ts
@@ -0,0 +1,118 @@
'use strict';
/* eslint-disable no-loop-func */
import { ProgressLocation, QuickInputButtons, window } from 'vscode';
import { Container } from '../../container';
import { Repository } from '../../git/gitService';
import { GlyphChars } from '../../constants';
import { GitCommandBase } from './gitCommand';
import { QuickPickStep } from './quickCommand';
import { ReferencesQuickPickItem, RepositoryQuickPickItem } from '../../quickpicks';
import { Strings } from '../../system';

interface State {
repos: Repository[];
ref: string;
}

export class CheckoutQuickCommand extends GitCommandBase {
constructor() {
super('checkout', 'Checkout');
}

async execute(state: State) {
return void (await window.withProgress(
{
location: ProgressLocation.Notification,
title: `Checking out ${
state.repos.length === 1 ? state.repos[0].formattedName : `${state.repos.length} repositories`
} to ${state.ref}`
},
() => Promise.all(state.repos.map(r => r.checkout(state.ref, { progress: false })))
));
}

async *steps(): AsyncIterableIterator<QuickPickStep> {
const state: Partial<State> & { counter: number } = { counter: 0 };
let showTags = false;

while (true) {
if (state.repos === undefined || state.counter < 1) {
const repos = [...(await Container.git.getOrderedRepositories())];

const step = this.createStep<RepositoryQuickPickItem>({
multiselect: true,
title: this.title,
placeholder: 'Choose repositories',
items: await Promise.all(
repos.map(repo =>
RepositoryQuickPickItem.create(
repo,
state.repos ? state.repos.some(r => r.id === repo.id) : undefined,
{ branch: true, fetched: true, status: true }
)
)
)
});
const selection = yield step;

if (!this.canMoveNext(step, state, selection)) {
break;
}

state.repos = selection.map(i => i.item);
}

if (state.ref === undefined || state.counter < 2) {
const includeTags = showTags || state.repos.length === 1;

const items = await this.getBranchesAndOrTags(state.repos, includeTags);
const step = this.createStep<ReferencesQuickPickItem>({
title: `${this.title}${Strings.pad(GlyphChars.Dot, 2, 2)}${
state.repos.length === 1 ? state.repos[0].formattedName : `${state.repos.length} repositories`
}`,
placeholder: `Choose a branch${includeTags ? ' or tag' : ''} to checkout to`,
items: items,
selectedItems: state.ref ? items.filter(ref => ref.label === state.ref) : undefined,
buttons: includeTags
? [QuickInputButtons.Back]
: [
QuickInputButtons.Back,
{
iconPath: {
dark: Container.context.asAbsolutePath('images/dark/icon-tag.svg') as any,
light: Container.context.asAbsolutePath('images/light/icon-tag.svg') as any
},
tooltip: 'Show Tags'
}
],
onDidClickButton: async (quickpick, button) => {
quickpick.busy = true;
quickpick.enabled = false;

if (!showTags) {
showTags = true;
}

quickpick.placeholder = `Choose a branch${showTags ? ' or tag' : ''} to checkout to`;
quickpick.buttons = [QuickInputButtons.Back];

quickpick.items = await this.getBranchesAndOrTags(state.repos!, showTags);

quickpick.busy = false;
quickpick.enabled = true;
}
});
const selection = yield step;

if (!this.canMoveNext(step, state, selection)) {
continue;
}

state.ref = selection[0].item.ref;
}

this.execute(state as State);
break;
}
}
}

0 comments on commit ee4b5a1

Please sign in to comment.