commands.ts 34.8 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

J
Joao Moreno 已提交
8
import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn } from 'vscode';
J
Joao Moreno 已提交
9
import { Ref, RefType, Git, GitErrorCodes, Branch } from './git';
10
import { Model, Resource, Status, CommitOptions, WorkingTreeGroup, IndexGroup, MergeGroup } from './model';
J
Joao Moreno 已提交
11
import { ModelRegistry } from './modelRegistry';
J
Joao Moreno 已提交
12
import { toGitUri, fromGitUri } from './uri';
13
import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange } from './staging';
J
Joao Moreno 已提交
14
import * as path from 'path';
J
Joao Moreno 已提交
15
import * as os from 'os';
J
Joao Moreno 已提交
16
import TelemetryReporter from 'vscode-extension-telemetry';
J
Joao Moreno 已提交
17 18 19
import * as nls from 'vscode-nls';

const localize = nls.loadMessageBundle();
J
Joao Moreno 已提交
20

J
Joao Moreno 已提交
21 22 23 24 25 26 27
class CheckoutItem implements QuickPickItem {

	protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
	protected get treeish(): string | undefined { return this.ref.name; }
	get label(): string { return this.ref.name || this.shortCommit; }
	get description(): string { return this.shortCommit; }

J
Joao Moreno 已提交
28
	constructor(protected ref: Ref) { }
J
Joao Moreno 已提交
29 30 31 32 33 34 35 36 37 38 39 40 41 42

	async run(model: Model): Promise<void> {
		const ref = this.treeish;

		if (!ref) {
			return;
		}

		await model.checkout(ref);
	}
}

class CheckoutTagItem extends CheckoutItem {

J
Joao Moreno 已提交
43 44 45
	get description(): string {
		return localize('tag at', "Tag at {0}", this.shortCommit);
	}
J
Joao Moreno 已提交
46 47 48 49
}

class CheckoutRemoteHeadItem extends CheckoutItem {

J
Joao Moreno 已提交
50 51 52
	get description(): string {
		return localize('remote branch at', "Remote branch at {0}", this.shortCommit);
	}
J
Joao Moreno 已提交
53 54 55 56 57 58 59 60 61 62 63

	protected get treeish(): string | undefined {
		if (!this.ref.name) {
			return;
		}

		const match = /^[^/]+\/(.*)$/.exec(this.ref.name);
		return match ? match[1] : this.ref.name;
	}
}

M
Maik Riechert 已提交
64 65
class BranchDeleteItem implements QuickPickItem {

66 67 68
	private get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
	get branchName(): string | undefined { return this.ref.name; }
	get label(): string { return this.branchName || ''; }
M
Maik Riechert 已提交
69 70
	get description(): string { return this.shortCommit; }

71
	constructor(private ref: Ref) { }
M
Maik Riechert 已提交
72

73 74
	async run(model: Model, force?: boolean): Promise<void> {
		if (!this.branchName) {
M
Maik Riechert 已提交
75 76
			return;
		}
77
		await model.deleteBranch(this.branchName, force);
M
Maik Riechert 已提交
78 79 80
	}
}

81 82 83 84 85 86
class MergeItem implements QuickPickItem {

	get label(): string { return this.ref.name || ''; }
	get description(): string { return this.ref.name || ''; }

	constructor(protected ref: Ref) { }
J
Joao Moreno 已提交
87 88

	async run(model: Model): Promise<void> {
J
Joao Moreno 已提交
89
		await model.merge(this.ref.name! || this.ref.commit!);
J
Joao Moreno 已提交
90
	}
91 92
}

J
Joao Moreno 已提交
93 94 95 96 97 98 99 100 101 102
class CreateBranchItem implements QuickPickItem {

	get label(): string { return localize('create branch', '$(plus) Create new branch'); }
	get description(): string { return ''; }

	async run(model: Model): Promise<void> {
		await commands.executeCommand('git.branch');
	}
}

J
Joao Moreno 已提交
103 104 105 106 107
interface CommandOptions {
	model?: boolean;
	diff?: boolean;
}

108 109 110 111
interface Command {
	commandId: string;
	key: string;
	method: Function;
J
Joao Moreno 已提交
112
	options: CommandOptions;
113 114 115
}

const Commands: Command[] = [];
J
Joao Moreno 已提交
116

J
Joao Moreno 已提交
117
function command(commandId: string, options: CommandOptions = {}): Function {
J
Joao Moreno 已提交
118
	return (target: any, key: string, descriptor: any) => {
J
Joao Moreno 已提交
119 120 121 122
		if (!(typeof descriptor.value === 'function')) {
			throw new Error('not supported');
		}

J
Joao Moreno 已提交
123
		Commands.push({ commandId, key, method: descriptor.value, options });
J
Joao Moreno 已提交
124 125
	};
}
J
Joao Moreno 已提交
126

J
Joao Moreno 已提交
127
export class CommandCenter {
J
Joao Moreno 已提交
128 129

	private disposables: Disposable[];
J
Joao Moreno 已提交
130

J
Joao Moreno 已提交
131
	constructor(
J
Joao Moreno 已提交
132
		private git: Git,
J
Joao Moreno 已提交
133
		private modelRegistry: ModelRegistry,
J
Joao Moreno 已提交
134 135
		private outputChannel: OutputChannel,
		private telemetryReporter: TelemetryReporter
J
Joao Moreno 已提交
136
	) {
J
Joao Moreno 已提交
137 138
		this.disposables = Commands.map(({ commandId, key, method, options }) => {
			const command = this.createCommand(commandId, key, method, options);
139

J
Joao Moreno 已提交
140
			if (options.diff) {
J
Joao Moreno 已提交
141 142 143 144 145
				return commands.registerDiffInformationCommand(commandId, command);
			} else {
				return commands.registerCommand(commandId, command);
			}
		});
J
Joao Moreno 已提交
146 147
	}

J
Joao Moreno 已提交
148
	@command('git.refresh', { model: true })
J
Joao Moreno 已提交
149 150
	async refresh(model: Model): Promise<void> {
		await model.status();
J
Joao Moreno 已提交
151
	}
J
Joao Moreno 已提交
152

J
Joao Moreno 已提交
153
	@command('git.openResource', { model: true })
J
Joao Moreno 已提交
154 155
	async openResource(model: Model, resource: Resource): Promise<void> {
		await this._openResource(model, resource);
J
Joao Moreno 已提交
156 157
	}

158
	private async _openResource(model: Model, resource: Resource, preview?: boolean): Promise<void> {
J
Joao Moreno 已提交
159
		const left = this.getLeftResource(resource);
J
Joao Moreno 已提交
160
		const right = this.getRightResource(model, resource);
J
Joao Moreno 已提交
161 162
		const title = this.getTitle(resource);

J
Joao Moreno 已提交
163 164 165 166 167
		if (!right) {
			// TODO
			console.error('oh no');
			return;
		}
J
Joao Moreno 已提交
168

J
Joao Moreno 已提交
169
		const opts: TextDocumentShowOptions = {
170 171
			preserveFocus: true,
			preview: preview,
J
Joao Moreno 已提交
172
			viewColumn: window.activeTextEditor && window.activeTextEditor.viewColumn || ViewColumn.One
J
Joao Moreno 已提交
173 174
		};

J
Joao Moreno 已提交
175 176 177 178 179 180
		const activeTextEditor = window.activeTextEditor;

		if (activeTextEditor && activeTextEditor.document.uri.toString() === right.toString()) {
			opts.selection = activeTextEditor.selection;
		}

181 182 183 184 185 186
		if (!left) {
			const document = await workspace.openTextDocument(right);
			await window.showTextDocument(document, opts);
			return;
		}

J
Joao Moreno 已提交
187
		return await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
J
Joao Moreno 已提交
188 189 190 191 192 193
	}

	private getLeftResource(resource: Resource): Uri | undefined {
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
194
				return toGitUri(resource.original, 'HEAD');
J
Joao Moreno 已提交
195 196

			case Status.MODIFIED:
J
Joao Moreno 已提交
197
				return toGitUri(resource.resourceUri, '~');
198 199 200

			case Status.DELETED_BY_THEM:
				return toGitUri(resource.resourceUri, '');
J
Joao Moreno 已提交
201
		}
J
Joao Moreno 已提交
202
	}
J
Joao Moreno 已提交
203

J
Joao Moreno 已提交
204
	private getRightResource(model: Model, resource: Resource): Uri | undefined {
J
Joao Moreno 已提交
205 206 207 208 209
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_ADDED:
			case Status.INDEX_COPIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
210
				return toGitUri(resource.resourceUri, '');
J
Joao Moreno 已提交
211 212

			case Status.INDEX_DELETED:
213
			case Status.DELETED_BY_THEM:
J
Joao Moreno 已提交
214
			case Status.DELETED:
J
Joao Moreno 已提交
215
				return toGitUri(resource.resourceUri, 'HEAD');
J
Joao Moreno 已提交
216 217 218 219

			case Status.MODIFIED:
			case Status.UNTRACKED:
			case Status.IGNORED:
J
Joao Moreno 已提交
220
				const uriString = resource.resourceUri.toString();
J
Joao Moreno 已提交
221
				const [indexStatus] = model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString);
J
Joao Moreno 已提交
222

J
Joao Moreno 已提交
223 224
				if (indexStatus && indexStatus.renameResourceUri) {
					return indexStatus.renameResourceUri;
J
Joao Moreno 已提交
225 226
				}

J
Joao Moreno 已提交
227
				return resource.resourceUri;
J
Joao Moreno 已提交
228

229
			case Status.BOTH_ADDED:
J
Joao Moreno 已提交
230
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
231
				return resource.resourceUri;
J
Joao Moreno 已提交
232 233 234 235
		}
	}

	private getTitle(resource: Resource): string {
J
Joao Moreno 已提交
236
		const basename = path.basename(resource.resourceUri.fsPath);
J
Joao Moreno 已提交
237 238 239 240

		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
M
Marc Kassay 已提交
241
			case Status.DELETED_BY_THEM:
J
Joao Moreno 已提交
242 243 244
				return `${basename} (Index)`;

			case Status.MODIFIED:
M
Marc Kassay 已提交
245 246
			case Status.BOTH_ADDED:
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
247 248 249 250 251 252
				return `${basename} (Working Tree)`;
		}

		return '';
	}

J
Joao Moreno 已提交
253
	@command('git.clone')
254
	async clone(): Promise<void> {
J
Joao Moreno 已提交
255
		const url = await window.showInputBox({
J
Joao Moreno 已提交
256 257
			prompt: localize('repourl', "Repository URL"),
			ignoreFocusOut: true
J
Joao Moreno 已提交
258 259 260
		});

		if (!url) {
261 262
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' });
			return;
J
Joao Moreno 已提交
263 264
		}

265
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
266
		const value = config.get<string>('defaultCloneDirectory') || os.homedir();
267

J
Joao Moreno 已提交
268 269
		const parentPath = await window.showInputBox({
			prompt: localize('parent', "Parent Directory"),
J
Joao Moreno 已提交
270
			value,
J
Joao Moreno 已提交
271
			ignoreFocusOut: true
J
Joao Moreno 已提交
272 273 274
		});

		if (!parentPath) {
275 276
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' });
			return;
J
Joao Moreno 已提交
277 278
		}

J
Joao Moreno 已提交
279
		const clonePromise = this.git.clone(url, parentPath);
J
Joao Moreno 已提交
280 281
		window.setStatusBarMessage(localize('cloning', "Cloning git repository..."), clonePromise);

282
		try {
283 284 285 286 287 288 289 290 291 292
			const repositoryPath = await clonePromise;

			const open = localize('openrepo', "Open Repository");
			const result = await window.showInformationMessage(localize('proposeopen', "Would you like to open the cloned repository?"), open);

			const openFolder = result === open;
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 });
			if (openFolder) {
				commands.executeCommand('vscode.openFolder', Uri.file(repositoryPath));
			}
293 294
		} catch (err) {
			if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
295 296 297
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
			} else {
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
298 299
			}
			throw err;
J
Joao Moreno 已提交
300 301 302
		}
	}

J
Joao Moreno 已提交
303
	@command('git.init')
J
Joao Moreno 已提交
304
	async init(): Promise<void> {
J
Joao Moreno 已提交
305 306
		// TODO@joao
		// await model.init();
J
Joao Moreno 已提交
307 308
	}

J
Joao Moreno 已提交
309
	@command('git.openFile', { model: true })
310
	async openFile(model: Model, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
311
		let uris: Uri[] | undefined;
312 313 314

		if (arg instanceof Uri) {
			if (arg.scheme === 'git') {
315
				uris = [Uri.file(fromGitUri(arg).path)];
316
			} else if (arg.scheme === 'file') {
317
				uris = [arg];
318 319 320 321 322 323
			}
		} else {
			let resource = arg;

			if (!(resource instanceof Resource)) {
				// can happen when called from a keybinding
J
Joao Moreno 已提交
324
				resource = this.getSCMResource(model);
325 326 327
			}

			if (resource) {
328
				uris = [...resourceStates.map(r => r.resourceUri), resource.resourceUri];
329
			}
J
Joao Moreno 已提交
330 331
		}

332
		if (!uris) {
J
Joao Moreno 已提交
333
			return;
J
Joao Moreno 已提交
334 335
		}

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
		const preview = uris.length === 1 ? true : false;
		const activeTextEditor = window.activeTextEditor;
		for (const uri of uris) {
			// If the active editor matches the current uri, get its selection
			const selections = activeTextEditor && activeTextEditor.document.uri.toString() === uri.toString()
				? activeTextEditor.selections
				: undefined;

			const opts: TextDocumentShowOptions = {
				preserveFocus: true,
				preview: preview,
				viewColumn: activeTextEditor && activeTextEditor.viewColumn || ViewColumn.One
			};

			const document = await workspace.openTextDocument(uri);
			await window.showTextDocument(document, opts);

			if (selections && window.activeTextEditor) {
				window.activeTextEditor.selections = selections;
			}
J
Joao Moreno 已提交
356
		}
J
Joao Moreno 已提交
357 358
	}

J
Joao Moreno 已提交
359
	@command('git.openHEADFile', { model: true })
J
Joao Moreno 已提交
360
	async openHEADFile(model: Model, arg?: Resource | Uri): Promise<void> {
D
Duroktar 已提交
361 362 363 364 365
		let resource: Resource | undefined = undefined;

		if (arg instanceof Resource) {
			resource = arg;
		} else if (arg instanceof Uri) {
J
Joao Moreno 已提交
366
			resource = this.getSCMResource(model, arg);
D
Duroktar 已提交
367
		} else {
J
Joao Moreno 已提交
368
			resource = this.getSCMResource(model);
D
Duroktar 已提交
369 370 371 372 373 374
		}

		if (!resource) {
			return;
		}

J
Joao Moreno 已提交
375
		const HEAD = this.getLeftResource(resource);
D
Duroktar 已提交
376

J
Joao Moreno 已提交
377 378 379
		if (!HEAD) {
			window.showWarningMessage(localize('HEAD not available', "HEAD version of '{0}' is not available.", path.basename(resource.resourceUri.fsPath)));
			return;
D
Duroktar 已提交
380
		}
J
Joao Moreno 已提交
381 382

		return await commands.executeCommand<void>('vscode.open', HEAD);
D
Duroktar 已提交
383 384
	}

J
Joao Moreno 已提交
385
	@command('git.openChange', { model: true })
386
	async openChange(model: Model, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
387
		let resources: Resource[] | undefined = undefined;
388

389
		if (arg instanceof Uri) {
390
			const resource = this.getSCMResource(model, arg);
391 392 393
			if (resource !== undefined) {
				resources = [resource];
			}
394
		} else {
395
			let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
396

397 398 399
			if (arg instanceof Resource) {
				resource = arg;
			} else {
400
				resource = this.getSCMResource(model);
401 402 403 404 405
			}

			if (resource) {
				resources = [...resourceStates as Resource[], resource];
			}
J
Joao Moreno 已提交
406 407
		}

408
		if (!resources) {
J
Joao Moreno 已提交
409
			return;
J
Joao Moreno 已提交
410
		}
J
Joao Moreno 已提交
411

412 413
		const preview = resources.length === 1 ? undefined : false;
		for (const resource of resources) {
414
			await this._openResource(model, resource, preview);
415
		}
J
Joao Moreno 已提交
416 417
	}

J
Joao Moreno 已提交
418
	@command('git.stage', { model: true })
J
Joao Moreno 已提交
419
	async stage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
420
		if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) {
J
Joao Moreno 已提交
421
			const resource = this.getSCMResource(model);
422 423 424 425 426 427 428 429

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

430 431 432
		const resources = resourceStates
			.filter(s => s instanceof Resource && (s.resourceGroup instanceof WorkingTreeGroup || s.resourceGroup instanceof MergeGroup)) as Resource[];

433
		if (!resources.length) {
J
Joao Moreno 已提交
434 435
			return;
		}
J
Joao Moreno 已提交
436

J
Joao Moreno 已提交
437
		return await model.add(...resources);
J
Joao Moreno 已提交
438 439
	}

J
Joao Moreno 已提交
440
	@command('git.stageAll', { model: true })
J
Joao Moreno 已提交
441 442
	async stageAll(model: Model): Promise<void> {
		return await model.add();
J
Joao Moreno 已提交
443 444
	}

445
	// TODO@Joao does this command really receive a model?
J
Joao Moreno 已提交
446
	@command('git.stageSelectedRanges', { model: true, diff: true })
J
Joao Moreno 已提交
447
	async stageSelectedRanges(model: Model, diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
448 449 450 451 452 453 454 455 456
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
		const modifiedUri = modifiedDocument.uri;

J
Joao Moreno 已提交
457
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
458 459 460
			return;
		}

J
Joao Moreno 已提交
461
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
462
		const originalDocument = await workspace.openTextDocument(originalUri);
463 464 465 466
		const selectedLines = toLineRanges(textEditor.selections, modifiedDocument);
		const selectedDiffs = diffs
			.map(diff => selectedLines.reduce<LineChange | null>((result, range) => result || intersectDiffWithRange(modifiedDocument, diff, range), null))
			.filter(d => !!d) as LineChange[];
J
Joao Moreno 已提交
467 468 469 470 471

		if (!selectedDiffs.length) {
			return;
		}

472 473
		const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs);

J
Joao Moreno 已提交
474
		await model.stage(modifiedUri, result);
J
Joao Moreno 已提交
475
	}
J
Joao Moreno 已提交
476

477
	// TODO@Joao does this command really receive a model?
J
Joao Moreno 已提交
478
	@command('git.revertSelectedRanges', { model: true, diff: true })
J
Joao Moreno 已提交
479
	async revertSelectedRanges(model: Model, diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
480 481 482 483 484 485 486 487 488 489 490 491 492
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
		const modifiedUri = modifiedDocument.uri;

		if (modifiedUri.scheme !== 'file') {
			return;
		}

J
Joao Moreno 已提交
493
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
		const originalDocument = await workspace.openTextDocument(originalUri);
		const selections = textEditor.selections;
		const selectedDiffs = diffs.filter(diff => {
			const modifiedRange = diff.modifiedEndLineNumber === 0
				? new Range(modifiedDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, modifiedDocument.lineAt(diff.modifiedStartLineNumber).range.start)
				: new Range(modifiedDocument.lineAt(diff.modifiedStartLineNumber - 1).range.start, modifiedDocument.lineAt(diff.modifiedEndLineNumber - 1).range.end);

			return selections.every(selection => !selection.intersection(modifiedRange));
		});

		if (selectedDiffs.length === diffs.length) {
			return;
		}

		const basename = path.basename(modifiedUri.fsPath);
		const message = localize('confirm revert', "Are you sure you want to revert the selected changes in {0}?", basename);
		const yes = localize('revert', "Revert Changes");
		const pick = await window.showWarningMessage(message, { modal: true }, yes);

		if (pick !== yes) {
			return;
		}

517
		const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs);
J
Joao Moreno 已提交
518 519 520 521 522
		const edit = new WorkspaceEdit();
		edit.replace(modifiedUri, new Range(new Position(0, 0), modifiedDocument.lineAt(modifiedDocument.lineCount - 1).range.end), result);
		workspace.applyEdit(edit);
	}

J
Joao Moreno 已提交
523
	@command('git.unstage', { model: true })
J
Joao Moreno 已提交
524
	async unstage(model: Model, ...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
525
		if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) {
J
Joao Moreno 已提交
526
			const resource = this.getSCMResource(model);
527 528 529 530 531 532 533 534

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

535 536 537
		const resources = resourceStates
			.filter(s => s instanceof Resource && s.resourceGroup instanceof IndexGroup) as Resource[];

538
		if (!resources.length) {
J
Joao Moreno 已提交
539 540 541
			return;
		}

J
Joao Moreno 已提交
542
		return await model.revertFiles(...resources);
J
Joao Moreno 已提交
543 544
	}

J
Joao Moreno 已提交
545
	@command('git.unstageAll', { model: true })
J
Joao Moreno 已提交
546 547
	async unstageAll(model: Model): Promise<void> {
		return await model.revertFiles();
J
Joao Moreno 已提交
548
	}
J
Joao Moreno 已提交
549

550
	// TODO@Joao does this command really receive a model?
J
Joao Moreno 已提交
551
	@command('git.unstageSelectedRanges', { model: true, diff: true })
J
Joao Moreno 已提交
552
	async unstageSelectedRanges(model: Model, diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
553 554 555 556 557 558 559 560 561
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
		const modifiedUri = modifiedDocument.uri;

562 563 564 565 566 567 568
		if (modifiedUri.scheme !== 'git') {
			return;
		}

		const { ref } = fromGitUri(modifiedUri);

		if (ref !== '') {
J
Joao Moreno 已提交
569 570 571
			return;
		}

J
Joao Moreno 已提交
572
		const originalUri = toGitUri(modifiedUri, 'HEAD');
J
Joao Moreno 已提交
573
		const originalDocument = await workspace.openTextDocument(originalUri);
574 575 576 577
		const selectedLines = toLineRanges(textEditor.selections, modifiedDocument);
		const selectedDiffs = diffs
			.map(diff => selectedLines.reduce<LineChange | null>((result, range) => result || intersectDiffWithRange(modifiedDocument, diff, range), null))
			.filter(d => !!d) as LineChange[];
J
Joao Moreno 已提交
578 579 580 581 582

		if (!selectedDiffs.length) {
			return;
		}

583 584
		const invertedDiffs = selectedDiffs.map(invertLineChange);
		const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs);
J
Joao Moreno 已提交
585

J
Joao Moreno 已提交
586
		await model.stage(modifiedUri, result);
J
Joao Moreno 已提交
587 588
	}

J
Joao Moreno 已提交
589
	@command('git.clean', { model: true })
J
Joao Moreno 已提交
590
	async clean(model: Model, ...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
591
		if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) {
J
Joao Moreno 已提交
592
			const resource = this.getSCMResource(model);
593 594 595 596 597 598 599 600

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

601 602 603
		const resources = resourceStates
			.filter(s => s instanceof Resource && s.resourceGroup instanceof WorkingTreeGroup) as Resource[];

604
		if (!resources.length) {
J
Joao Moreno 已提交
605 606
			return;
		}
J
Joao Moreno 已提交
607

608
		const message = resources.length === 1
J
Joao Moreno 已提交
609
			? localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath))
610 611
			: localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", resources.length);

612
		const yes = localize('discard', "Discard Changes");
J
Joao Moreno 已提交
613
		const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
614

J
Joao Moreno 已提交
615 616 617 618
		if (pick !== yes) {
			return;
		}

J
Joao Moreno 已提交
619
		await model.clean(...resources);
J
Joao Moreno 已提交
620
	}
J
Joao Moreno 已提交
621

J
Joao Moreno 已提交
622
	@command('git.cleanAll', { model: true })
J
Joao Moreno 已提交
623
	async cleanAll(model: Model): Promise<void> {
624 625
		const message = localize('confirm discard all', "Are you sure you want to discard ALL changes? This is IRREVERSIBLE!");
		const yes = localize('discardAll', "Discard ALL Changes");
J
Joao Moreno 已提交
626
		const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
627 628 629 630 631

		if (pick !== yes) {
			return;
		}

J
Joao Moreno 已提交
632
		await model.clean(...model.workingTreeGroup.resources);
J
Joao Moreno 已提交
633 634
	}

J
Joao Moreno 已提交
635
	private async smartCommit(
J
Joao Moreno 已提交
636
		model: Model,
637
		getCommitMessage: () => Promise<string | undefined>,
J
Joao Moreno 已提交
638 639
		opts?: CommitOptions
	): Promise<boolean> {
640 641
		const config = workspace.getConfiguration('git');
		const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
642
		const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
J
Joao Moreno 已提交
643 644
		const noStagedChanges = model.indexGroup.resources.length === 0;
		const noUnstagedChanges = model.workingTreeGroup.resources.length === 0;
645 646

		// no changes, and the user has not configured to commit all in this case
647
		if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit) {
648

J
Joao Moreno 已提交
649 650
			// prompt the user if we want to commit all or not
			const message = localize('no staged changes', "There are no staged changes to commit.\n\nWould you like to automatically stage all your changes and commit them directly?");
651 652 653 654 655
			const yes = localize('yes', "Yes");
			const always = localize('always', "Always");
			const pick = await window.showWarningMessage(message, { modal: true }, yes, always);

			if (pick === always) {
J
Joao Moreno 已提交
656 657 658
				config.update('enableSmartCommit', true, true);
			} else if (pick !== yes) {
				return false; // do not commit on cancel
659 660 661
			}
		}

J
Joao Moreno 已提交
662
		if (!opts) {
663
			opts = { all: noStagedChanges };
J
Joao Moreno 已提交
664 665
		}

666 667 668
		// enable signing of commits if configurated
		opts.signCommit = enableCommitSigning;

J
Joao Moreno 已提交
669 670
		if (
			// no changes
671
			(noStagedChanges && noUnstagedChanges)
J
Joao Moreno 已提交
672
			// or no staged changes and not `all`
673
			|| (!opts.all && noStagedChanges)
J
Joao Moreno 已提交
674
		) {
J
Joao Moreno 已提交
675 676 677 678
			window.showInformationMessage(localize('no changes', "There are no changes to commit."));
			return false;
		}

J
Joao Moreno 已提交
679
		const message = await getCommitMessage();
J
Joao Moreno 已提交
680 681 682 683 684 685

		if (!message) {
			// TODO@joao: show modal dialog to confirm empty message commit
			return false;
		}

J
Joao Moreno 已提交
686
		await model.commit(message, opts);
J
Joao Moreno 已提交
687 688 689 690

		return true;
	}

J
Joao Moreno 已提交
691
	private async commitWithAnyInput(model: Model, opts?: CommitOptions): Promise<void> {
692
		const message = scm.inputBox.value;
J
Joao Moreno 已提交
693
		const getCommitMessage = async () => {
J
Joao Moreno 已提交
694 695 696 697 698 699
			if (message) {
				return message;
			}

			return await window.showInputBox({
				placeHolder: localize('commit message', "Commit message"),
J
Joao Moreno 已提交
700 701
				prompt: localize('provide commit message', "Please provide a commit message"),
				ignoreFocusOut: true
J
Joao Moreno 已提交
702
			});
J
Joao Moreno 已提交
703 704
		};

J
Joao Moreno 已提交
705
		const didCommit = await this.smartCommit(model, getCommitMessage, opts);
J
Joao Moreno 已提交
706 707

		if (message && didCommit) {
J
Joao Moreno 已提交
708
			scm.inputBox.value = await model.getCommitTemplate();
J
Joao Moreno 已提交
709
		}
J
Joao Moreno 已提交
710 711
	}

J
Joao Moreno 已提交
712
	@command('git.commit', { model: true })
J
Joao Moreno 已提交
713 714
	async commit(model: Model): Promise<void> {
		await this.commitWithAnyInput(model);
J
Joao Moreno 已提交
715 716
	}

J
Joao Moreno 已提交
717
	@command('git.commitWithInput', { model: true })
J
Joao Moreno 已提交
718
	async commitWithInput(model: Model): Promise<void> {
J
Joao Moreno 已提交
719 720 721 722
		if (!scm.inputBox.value) {
			return;
		}

J
Joao Moreno 已提交
723
		const didCommit = await this.smartCommit(model, async () => scm.inputBox.value);
J
Joao Moreno 已提交
724 725

		if (didCommit) {
J
Joao Moreno 已提交
726
			scm.inputBox.value = await model.getCommitTemplate();
J
Joao Moreno 已提交
727
		}
J
Joao Moreno 已提交
728 729
	}

J
Joao Moreno 已提交
730
	@command('git.commitStaged', { model: true })
J
Joao Moreno 已提交
731 732
	async commitStaged(model: Model): Promise<void> {
		await this.commitWithAnyInput(model, { all: false });
J
Joao Moreno 已提交
733 734
	}

J
Joao Moreno 已提交
735
	@command('git.commitStagedSigned', { model: true })
J
Joao Moreno 已提交
736 737
	async commitStagedSigned(model: Model): Promise<void> {
		await this.commitWithAnyInput(model, { all: false, signoff: true });
J
Joao Moreno 已提交
738 739
	}

J
Joao Moreno 已提交
740
	@command('git.commitStagedAmend', { model: true })
741 742
	async commitStagedAmend(model: Model): Promise<void> {
		await this.commitWithAnyInput(model, { all: false, amend: true });
K
Krzysztof Cieślak 已提交
743 744
	}

J
Joao Moreno 已提交
745
	@command('git.commitAll', { model: true })
J
Joao Moreno 已提交
746 747
	async commitAll(model: Model): Promise<void> {
		await this.commitWithAnyInput(model, { all: true });
J
Joao Moreno 已提交
748 749
	}

J
Joao Moreno 已提交
750
	@command('git.commitAllSigned', { model: true })
J
Joao Moreno 已提交
751 752
	async commitAllSigned(model: Model): Promise<void> {
		await this.commitWithAnyInput(model, { all: true, signoff: true });
J
Joao Moreno 已提交
753 754
	}

J
Joao Moreno 已提交
755
	@command('git.commitAllAmend', { model: true })
756 757
	async commitAllAmend(model: Model): Promise<void> {
		await this.commitWithAnyInput(model, { all: true, amend: true });
K
Krzysztof Cieślak 已提交
758 759
	}

J
Joao Moreno 已提交
760
	@command('git.undoCommit', { model: true })
J
Joao Moreno 已提交
761 762
	async undoCommit(model: Model): Promise<void> {
		const HEAD = model.HEAD;
J
Joao Moreno 已提交
763 764 765 766 767

		if (!HEAD || !HEAD.commit) {
			return;
		}

J
Joao Moreno 已提交
768 769
		const commit = await model.getCommit('HEAD');
		await model.reset('HEAD~');
J
Joao Moreno 已提交
770
		scm.inputBox.value = commit.message;
J
Joao Moreno 已提交
771 772
	}

J
Joao Moreno 已提交
773
	@command('git.checkout', { model: true })
J
Joao Moreno 已提交
774
	async checkout(model: Model, treeish: string): Promise<void> {
J
Joao Moreno 已提交
775
		if (typeof treeish === 'string') {
J
Joao Moreno 已提交
776
			return await model.checkout(treeish);
J
Joao Moreno 已提交
777 778
		}

J
Joao Moreno 已提交
779
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
780
		const checkoutType = config.get<string>('checkoutType') || 'all';
J
Joao Moreno 已提交
781 782 783
		const includeTags = checkoutType === 'all' || checkoutType === 'tags';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
784 785
		const createBranch = new CreateBranchItem();

J
Joao Moreno 已提交
786
		const heads = model.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
787 788
			.map(ref => new CheckoutItem(ref));

J
Joao Moreno 已提交
789
		const tags = (includeTags ? model.refs.filter(ref => ref.type === RefType.Tag) : [])
J
Joao Moreno 已提交
790 791
			.map(ref => new CheckoutTagItem(ref));

J
Joao Moreno 已提交
792
		const remoteHeads = (includeRemotes ? model.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
793 794
			.map(ref => new CheckoutRemoteHeadItem(ref));

J
Joao Moreno 已提交
795
		const picks = [createBranch, ...heads, ...tags, ...remoteHeads];
796
		const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout');
J
Joao Moreno 已提交
797
		const choice = await window.showQuickPick(picks, { placeHolder });
J
Joao Moreno 已提交
798 799 800 801 802

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
803
		await choice.run(model);
J
Joao Moreno 已提交
804 805
	}

J
Joao Moreno 已提交
806
	@command('git.branch', { model: true })
J
Joao Moreno 已提交
807
	async branch(model: Model): Promise<void> {
J
Joao Moreno 已提交
808
		const result = await window.showInputBox({
J
Joao Moreno 已提交
809
			placeHolder: localize('branch name', "Branch name"),
J
Joao Moreno 已提交
810 811
			prompt: localize('provide branch name', "Please provide a branch name"),
			ignoreFocusOut: true
J
Joao Moreno 已提交
812
		});
J
Joao Moreno 已提交
813

J
Joao Moreno 已提交
814 815 816
		if (!result) {
			return;
		}
J
Joao Moreno 已提交
817

J
Joao Moreno 已提交
818
		const name = result.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
J
Joao Moreno 已提交
819
		await model.branch(name);
J
Joao Moreno 已提交
820 821
	}

J
Joao Moreno 已提交
822
	@command('git.deleteBranch', { model: true })
J
Joao Moreno 已提交
823
	async deleteBranch(model: Model, name: string, force?: boolean): Promise<void> {
824 825
		let run: (force?: boolean) => Promise<void>;
		if (typeof name === 'string') {
J
Joao Moreno 已提交
826
			run = force => model.deleteBranch(name, force);
827
		} else {
J
Joao Moreno 已提交
828 829
			const currentHead = model.HEAD && model.HEAD.name;
			const heads = model.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead)
830
				.map(ref => new BranchDeleteItem(ref));
M
Maik Riechert 已提交
831

M
Maik Riechert 已提交
832
			const placeHolder = localize('select branch to delete', 'Select a branch to delete');
833
			const choice = await window.showQuickPick<BranchDeleteItem>(heads, { placeHolder });
M
Maik Riechert 已提交
834

M
Maik Riechert 已提交
835
			if (!choice || !choice.branchName) {
836 837
				return;
			}
M
Maik Riechert 已提交
838
			name = choice.branchName;
J
Joao Moreno 已提交
839
			run = force => choice.run(model, force);
M
Maik Riechert 已提交
840 841
		}

842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
		try {
			await run(force);
		} catch (err) {
			if (err.gitErrorCode !== GitErrorCodes.BranchNotFullyMerged) {
				throw err;
			}

			const message = localize('confirm force delete branch', "The branch '{0}' is not fully merged. Delete anyway?", name);
			const yes = localize('delete branch', "Delete Branch");
			const pick = await window.showWarningMessage(message, yes);

			if (pick === yes) {
				await run(true);
			}
		}
M
Maik Riechert 已提交
857 858
	}

J
Joao Moreno 已提交
859
	@command('git.merge', { model: true })
J
Joao Moreno 已提交
860
	async merge(model: Model): Promise<void> {
861 862 863 864
		const config = workspace.getConfiguration('git');
		const checkoutType = config.get<string>('checkoutType') || 'all';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
865
		const heads = model.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
866 867
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
868

J
Joao Moreno 已提交
869
		const remoteHeads = (includeRemotes ? model.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
870 871
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
872 873

		const picks = [...heads, ...remoteHeads];
874 875
		const placeHolder = localize('select a branch to merge from', 'Select a branch to merge from');
		const choice = await window.showQuickPick<MergeItem>(picks, { placeHolder });
876 877 878 879 880

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
881
		try {
J
Joao Moreno 已提交
882
			await choice.run(model);
J
Joao Moreno 已提交
883 884 885 886 887 888 889 890
		} catch (err) {
			if (err.gitErrorCode !== GitErrorCodes.Conflict) {
				throw err;
			}

			const message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing.");
			await window.showWarningMessage(message);
		}
891 892
	}

J
Joao Moreno 已提交
893
	@command('git.createTag', { model: true })
894
	async createTag(model: Model): Promise<void> {
895 896 897 898 899 900 901 902 903 904 905 906
		const inputTagName = await window.showInputBox({
			placeHolder: localize('tag name', "Tag name"),
			prompt: localize('provide tag name', "Please provide a tag name"),
			ignoreFocusOut: true
		});

		if (!inputTagName) {
			return;
		}

		const inputMessage = await window.showInputBox({
			placeHolder: localize('tag message', "Message"),
J
Joao Moreno 已提交
907
			prompt: localize('provide tag message', "Please provide a message to annotate the tag"),
908 909 910 911 912
			ignoreFocusOut: true
		});

		const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
		const message = inputMessage || name;
913
		await model.tag(name, message);
914 915
	}

J
Joao Moreno 已提交
916
	@command('git.pullFrom', { model: true })
J
Joao Moreno 已提交
917 918
	async pullFrom(model: Model): Promise<void> {
		const remotes = model.remotes;
919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942

		if (remotes.length === 0) {
			window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from."));
			return;
		}

		const picks = remotes.map(r => ({ label: r.name, description: r.url }));
		const placeHolder = localize('pick remote pull repo', "Pick a remote to pull the branch from");
		const pick = await window.showQuickPick(picks, { placeHolder });

		if (!pick) {
			return;
		}

		const branchName = await window.showInputBox({
			placeHolder: localize('branch name', "Branch name"),
			prompt: localize('provide branch name', "Please provide a branch name"),
			ignoreFocusOut: true
		});

		if (!branchName) {
			return;
		}

J
Joao Moreno 已提交
943
		model.pull(false, pick.label, branchName);
944 945
	}

J
Joao Moreno 已提交
946
	@command('git.pull', { model: true })
J
Joao Moreno 已提交
947 948
	async pull(model: Model): Promise<void> {
		const remotes = model.remotes;
J
Joao Moreno 已提交
949 950 951 952 953 954

		if (remotes.length === 0) {
			window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from."));
			return;
		}

J
Joao Moreno 已提交
955
		await model.pull();
J
Joao Moreno 已提交
956 957
	}

J
Joao Moreno 已提交
958
	@command('git.pullRebase', { model: true })
J
Joao Moreno 已提交
959 960
	async pullRebase(model: Model): Promise<void> {
		const remotes = model.remotes;
J
Joao Moreno 已提交
961 962 963 964 965 966

		if (remotes.length === 0) {
			window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from."));
			return;
		}

J
Joao Moreno 已提交
967
		await model.pullWithRebase();
J
Joao Moreno 已提交
968 969
	}

J
Joao Moreno 已提交
970
	@command('git.push', { model: true })
J
Joao Moreno 已提交
971 972
	async push(model: Model): Promise<void> {
		const remotes = model.remotes;
J
Joao Moreno 已提交
973 974 975 976 977 978

		if (remotes.length === 0) {
			window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
			return;
		}

J
Joao Moreno 已提交
979
		await model.push();
J
Joao Moreno 已提交
980 981
	}

J
Joao Moreno 已提交
982
	@command('git.pushWithTags', { model: true })
983 984
	async pushWithTags(model: Model): Promise<void> {
		const remotes = model.remotes;
985 986 987 988 989 990

		if (remotes.length === 0) {
			window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
			return;
		}

991
		await model.pushTags();
992 993 994 995

		window.showInformationMessage(localize('push with tags success', "Successfully pushed with tags."));
	}

J
Joao Moreno 已提交
996
	@command('git.pushTo', { model: true })
J
Joao Moreno 已提交
997 998
	async pushTo(model: Model): Promise<void> {
		const remotes = model.remotes;
J
Joao Moreno 已提交
999 1000 1001 1002 1003 1004

		if (remotes.length === 0) {
			window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
			return;
		}

J
Joao Moreno 已提交
1005
		if (!model.HEAD || !model.HEAD.name) {
J
Joao Moreno 已提交
1006 1007 1008 1009
			window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
			return;
		}

J
Joao Moreno 已提交
1010
		const branchName = model.HEAD.name;
J
Joao Moreno 已提交
1011 1012 1013 1014 1015 1016 1017 1018
		const picks = remotes.map(r => ({ label: r.name, description: r.url }));
		const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
		const pick = await window.showQuickPick(picks, { placeHolder });

		if (!pick) {
			return;
		}

J
Joao Moreno 已提交
1019
		model.pushTo(pick.label, branchName);
J
Joao Moreno 已提交
1020 1021
	}

J
Joao Moreno 已提交
1022
	@command('git.sync', { model: true })
J
Joao Moreno 已提交
1023 1024
	async sync(model: Model): Promise<void> {
		const HEAD = model.HEAD;
J
Joao Moreno 已提交
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045

		if (!HEAD || !HEAD.upstream) {
			return;
		}

		const config = workspace.getConfiguration('git');
		const shouldPrompt = config.get<boolean>('confirmSync') === true;

		if (shouldPrompt) {
			const message = localize('sync is unpredictable', "This action will push and pull commits to and from '{0}'.", HEAD.upstream);
			const yes = localize('ok', "OK");
			const neverAgain = localize('never again', "OK, Never Show Again");
			const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);

			if (pick === neverAgain) {
				await config.update('confirmSync', false, true);
			} else if (pick !== yes) {
				return;
			}
		}

J
Joao Moreno 已提交
1046
		await model.sync();
J
Joao Moreno 已提交
1047 1048
	}

J
Joao Moreno 已提交
1049
	@command('git.publish', { model: true })
J
Joao Moreno 已提交
1050 1051
	async publish(model: Model): Promise<void> {
		const remotes = model.remotes;
J
Joao Moreno 已提交
1052 1053 1054 1055 1056 1057

		if (remotes.length === 0) {
			window.showWarningMessage(localize('no remotes to publish', "Your repository has no remotes configured to publish to."));
			return;
		}

J
Joao Moreno 已提交
1058 1059
		const branchName = model.HEAD && model.HEAD.name || '';
		const picks = model.remotes.map(r => r.name);
J
Joao Moreno 已提交
1060
		const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
J
Joao Moreno 已提交
1061 1062 1063 1064 1065 1066
		const choice = await window.showQuickPick(picks, { placeHolder });

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
1067
		await model.pushTo(choice, branchName, true);
J
Joao Moreno 已提交
1068 1069
	}

J
Joao Moreno 已提交
1070
	@command('git.showOutput')
J
Joao Moreno 已提交
1071 1072 1073 1074
	showOutput(): void {
		this.outputChannel.show();
	}

J
Joao Moreno 已提交
1075
	@command('git.ignore', { model: true })
J
Joao Moreno 已提交
1076
	async ignore(model: Model, ...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
1077 1078
		if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) {
			const uri = window.activeTextEditor && window.activeTextEditor.document.uri;
N
NKumar2 已提交
1079

J
Joao Moreno 已提交
1080 1081 1082 1083
			if (!uri) {
				return;
			}

J
Joao Moreno 已提交
1084
			return await model.ignore([uri]);
J
Joao Moreno 已提交
1085 1086 1087 1088 1089 1090 1091
		}

		const uris = resourceStates
			.filter(s => s instanceof Resource)
			.map(r => r.resourceUri);

		if (!uris.length) {
N
NKumar2 已提交
1092 1093 1094
			return;
		}

J
Joao Moreno 已提交
1095
		await model.ignore(uris);
N
NKumar2 已提交
1096 1097
	}

J
Joao Moreno 已提交
1098
	private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any {
1099
		const result = (...args) => {
J
Joao Moreno 已提交
1100 1101 1102 1103 1104 1105 1106
			// if (!skipModelCheck && !this.model) {
			// 	window.showInformationMessage(localize('disabled', "Git is either disabled or not supported in this workspace"));
			// 	return;
			// }

			let result: Promise<any>;

J
Joao Moreno 已提交
1107
			if (!options.model) {
J
Joao Moreno 已提交
1108 1109 1110 1111 1112 1113 1114 1115 1116
				result = Promise.resolve(method.apply(this, args));
			} else {
				result = this.modelRegistry.pickModel().then(model => {
					if (!model) {
						return Promise.reject(localize('modelnotfound', "Git model not found"));
					}

					return Promise.resolve(method.apply(this, [model, ...args]));
				});
J
Joao Moreno 已提交
1117 1118
			}

J
Joao Moreno 已提交
1119 1120
			this.telemetryReporter.sendTelemetryEvent('git.command', { command: id });

J
Joao Moreno 已提交
1121 1122 1123 1124
			return result.catch(async err => {
				let message: string;

				switch (err.gitErrorCode) {
1125
					case GitErrorCodes.DirtyWorkTree:
J
Joao Moreno 已提交
1126 1127
						message = localize('clean repo', "Please clean your repository working tree before checkout.");
						break;
1128 1129 1130
					case GitErrorCodes.PushRejected:
						message = localize('cant push', "Can't push refs to remote. Run 'Pull' first to integrate your changes.");
						break;
J
Joao Moreno 已提交
1131
					default:
1132 1133 1134
						const hint = (err.stderr || err.message || String(err))
							.replace(/^error: /mi, '')
							.replace(/^> husky.*$/mi, '')
J
Joao Moreno 已提交
1135
							.split(/[\r\n]/)
1136 1137 1138 1139 1140 1141
							.filter(line => !!line)
						[0];

						message = hint
							? localize('git error details', "Git: {0}", hint)
							: localize('git error', "Git error");
J
Joao Moreno 已提交
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159

						break;
				}

				if (!message) {
					console.error(err);
					return;
				}

				const outputChannel = this.outputChannel as OutputChannel;
				const openOutputChannelChoice = localize('open git log', "Open Git Log");
				const choice = await window.showErrorMessage(message, openOutputChannelChoice);

				if (choice === openOutputChannelChoice) {
					outputChannel.show();
				}
			});
		};
1160 1161 1162 1163 1164

		// patch this object, so people can call methods directly
		this[key] = result;

		return result;
J
Joao Moreno 已提交
1165 1166
	}

J
Joao Moreno 已提交
1167
	private getSCMResource(model: Model, uri?: Uri): Resource | undefined {
1168
		uri = uri ? uri : window.activeTextEditor && window.activeTextEditor.document.uri;
J
Joao Moreno 已提交
1169 1170

		if (!uri) {
1171
			return undefined;
J
Joao Moreno 已提交
1172 1173 1174
		}

		if (uri.scheme === 'git') {
J
Joao Moreno 已提交
1175 1176
			const { path } = fromGitUri(uri);
			uri = Uri.file(path);
J
Joao Moreno 已提交
1177 1178 1179 1180 1181
		}

		if (uri.scheme === 'file') {
			const uriString = uri.toString();

J
Joao Moreno 已提交
1182 1183
			return model.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]
				|| model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0];
J
Joao Moreno 已提交
1184 1185 1186
		}
	}

J
Joao Moreno 已提交
1187 1188 1189
	dispose(): void {
		this.disposables.forEach(d => d.dispose());
	}
J
Joao Moreno 已提交
1190
}