commands.ts 37.0 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';

8
import { Uri, commands, scm, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation } 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 { toGitUri, fromGitUri } from './uri';
12
import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange } from './staging';
J
Joao Moreno 已提交
13
import * as path from 'path';
J
Joao Moreno 已提交
14
import * as os from 'os';
J
Joao Moreno 已提交
15
import TelemetryReporter from 'vscode-extension-telemetry';
J
Joao Moreno 已提交
16 17 18
import * as nls from 'vscode-nls';

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

J
Joao Moreno 已提交
20 21 22 23 24 25 26
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 已提交
27
	constructor(protected ref: Ref) { }
J
Joao Moreno 已提交
28 29 30 31 32 33 34 35 36 37 38 39 40 41

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

		if (!ref) {
			return;
		}

		await model.checkout(ref);
	}
}

class CheckoutTagItem extends CheckoutItem {

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

class CheckoutRemoteHeadItem extends CheckoutItem {

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

	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 已提交
63 64
class BranchDeleteItem implements QuickPickItem {

65 66 67
	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 已提交
68 69
	get description(): string { return this.shortCommit; }

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

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

80 81 82 83 84 85
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 已提交
86 87

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

J
Joao Moreno 已提交
92 93 94 95 96 97 98 99 100 101
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');
	}
}

102 103 104 105 106 107 108 109 110
interface Command {
	commandId: string;
	key: string;
	method: Function;
	skipModelCheck: boolean;
	requiresDiffInformation: boolean;
}

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

112
function command(commandId: string, skipModelCheck = false, requiresDiffInformation = false): Function {
J
Joao Moreno 已提交
113
	return (target: any, key: string, descriptor: any) => {
J
Joao Moreno 已提交
114 115 116 117
		if (!(typeof descriptor.value === 'function')) {
			throw new Error('not supported');
		}

118
		Commands.push({ commandId, key, method: descriptor.value, skipModelCheck, requiresDiffInformation });
J
Joao Moreno 已提交
119 120
	};
}
J
Joao Moreno 已提交
121

J
Joao Moreno 已提交
122
export class CommandCenter {
J
Joao Moreno 已提交
123

J
Joao Moreno 已提交
124
	private model: Model;
J
Joao Moreno 已提交
125
	private disposables: Disposable[];
J
Joao Moreno 已提交
126

J
Joao Moreno 已提交
127
	constructor(
J
Joao Moreno 已提交
128
		private git: Git,
J
Joao Moreno 已提交
129
		model: Model | undefined,
J
Joao Moreno 已提交
130 131
		private outputChannel: OutputChannel,
		private telemetryReporter: TelemetryReporter
J
Joao Moreno 已提交
132
	) {
J
Joao Moreno 已提交
133 134 135 136
		if (model) {
			this.model = model;
		}

J
Joao Moreno 已提交
137
		this.disposables = Commands
138 139 140 141 142 143 144 145 146
			.map(({ commandId, key, method, skipModelCheck, requiresDiffInformation }) => {
				const command = this.createCommand(commandId, key, method, skipModelCheck);

				if (requiresDiffInformation) {
					return commands.registerDiffInformationCommand(commandId, command);
				} else {
					return commands.registerCommand(commandId, command);
				}
			});
J
Joao Moreno 已提交
147 148
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return '';
	}

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

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

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

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

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

J
Joao Moreno 已提交
280
		const clonePromise = this.git.clone(url, parentPath);
M
Maryam Archie 已提交
281

J
Joao Moreno 已提交
282

283
		try {
M
Maryam Archie 已提交
284 285 286
			window.withProgress({ location: ProgressLocation.SourceControl, title: localize('cloning', "Cloning git repository...") }, () => clonePromise);
			window.withProgress({ location: ProgressLocation.Window, title: localize('cloning', "Cloning git repository...") }, () => clonePromise);

287 288 289 290 291 292 293 294 295 296
			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));
			}
297 298
		} catch (err) {
			if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
299 300 301
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
			} else {
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
302 303
			}
			throw err;
J
Joao Moreno 已提交
304 305 306
		}
	}

J
Joao Moreno 已提交
307 308 309 310 311
	@command('git.init')
	async init(): Promise<void> {
		await this.model.init();
	}

J
Joao Moreno 已提交
312
	@command('git.openFile')
313
	async openFile(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
314 315
		const preserveFocus = !!arg;

316
		let uris: Uri[] | undefined;
317 318 319

		if (arg instanceof Uri) {
			if (arg.scheme === 'git') {
320
				uris = [Uri.file(fromGitUri(arg).path)];
321
			} else if (arg.scheme === 'file') {
322
				uris = [arg];
323 324 325 326 327 328 329 330 331 332
			}
		} else {
			let resource = arg;

			if (!(resource instanceof Resource)) {
				// can happen when called from a keybinding
				resource = this.getSCMResource();
			}

			if (resource) {
333
				uris = [...resourceStates.map(r => r.resourceUri), resource.resourceUri];
334
			}
J
Joao Moreno 已提交
335 336
		}

337
		if (!uris) {
J
Joao Moreno 已提交
338
			return;
J
Joao Moreno 已提交
339 340
		}

341 342 343 344 345 346 347 348 349
		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 = {
J
Joao Moreno 已提交
350
				preserveFocus,
351 352 353 354 355 356 357 358 359 360
				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 已提交
361
		}
J
Joao Moreno 已提交
362 363
	}

J
Joao Moreno 已提交
364 365
	@command('git.openHEADFile')
	async openHEADFile(arg?: Resource | Uri): Promise<void> {
D
Duroktar 已提交
366 367 368 369 370 371 372 373 374 375 376 377 378 379
		let resource: Resource | undefined = undefined;

		if (arg instanceof Resource) {
			resource = arg;
		} else if (arg instanceof Uri) {
			resource = this.getSCMResource(arg);
		} else {
			resource = this.getSCMResource();
		}

		if (!resource) {
			return;
		}

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

J
Joao Moreno 已提交
382 383 384
		if (!HEAD) {
			window.showWarningMessage(localize('HEAD not available', "HEAD version of '{0}' is not available.", path.basename(resource.resourceUri.fsPath)));
			return;
D
Duroktar 已提交
385
		}
J
Joao Moreno 已提交
386 387

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

J
Joao Moreno 已提交
390
	@command('git.openChange')
391
	async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
392
		const preserveFocus = !!arg;
393
		let resources: Resource[] | undefined = undefined;
394

395 396 397 398 399 400 401 402
		if (arg instanceof Uri) {
			const resource = this.getSCMResource(arg);
			if (resource !== undefined) {
				resources = [resource];
			}
		}
		else {
			let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
403

404 405 406 407 408 409 410 411 412
			if (arg instanceof Resource) {
				resource = arg;
			} else {
				resource = this.getSCMResource();
			}

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

415
		if (!resources) {
J
Joao Moreno 已提交
416
			return;
J
Joao Moreno 已提交
417
		}
J
Joao Moreno 已提交
418

419 420
		const preview = resources.length === 1 ? undefined : false;
		for (const resource of resources) {
J
Joao Moreno 已提交
421
			await this._openResource(resource, preview, preserveFocus);
422
		}
J
Joao Moreno 已提交
423 424
	}

J
Joao Moreno 已提交
425
	@command('git.stage')
426
	async stage(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
427
		if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) {
428
			const resource = this.getSCMResource();
429 430 431 432 433 434 435 436

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

437 438 439
		const resources = resourceStates
			.filter(s => s instanceof Resource && (s.resourceGroup instanceof WorkingTreeGroup || s.resourceGroup instanceof MergeGroup)) as Resource[];

440
		if (!resources.length) {
J
Joao Moreno 已提交
441 442
			return;
		}
J
Joao Moreno 已提交
443

444
		return await this.model.add(...resources);
J
Joao Moreno 已提交
445 446
	}

J
Joao Moreno 已提交
447
	@command('git.stageAll')
J
Joao Moreno 已提交
448
	async stageAll(): Promise<void> {
J
Joao Moreno 已提交
449 450 451
		return await this.model.add();
	}

452 453
	@command('git.stageSelectedRanges', false, true)
	async stageSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
454 455 456 457 458 459 460 461 462
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

J
Joao Moreno 已提交
463
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
464 465 466
			return;
		}

J
Joao Moreno 已提交
467
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
468
		const originalDocument = await workspace.openTextDocument(originalUri);
469 470 471 472
		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 已提交
473 474 475 476 477

		if (!selectedDiffs.length) {
			return;
		}

478 479
		const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs);

J
Joao Moreno 已提交
480
		await this.model.stage(modifiedUri, result);
J
Joao Moreno 已提交
481
	}
J
Joao Moreno 已提交
482

483 484
	@command('git.revertSelectedRanges', false, true)
	async revertSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
485 486 487 488 489 490 491 492 493 494 495 496 497
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

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

J
Joao Moreno 已提交
498
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
		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;
		}

522
		const result = applyLineChanges(originalDocument, modifiedDocument, selectedDiffs);
J
Joao Moreno 已提交
523 524 525 526 527
		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 已提交
528
	@command('git.unstage')
529
	async unstage(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
530
		if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) {
531
			const resource = this.getSCMResource();
532 533 534 535 536 537 538 539

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

540 541 542
		const resources = resourceStates
			.filter(s => s instanceof Resource && s.resourceGroup instanceof IndexGroup) as Resource[];

543
		if (!resources.length) {
J
Joao Moreno 已提交
544 545 546
			return;
		}

547
		return await this.model.revertFiles(...resources);
J
Joao Moreno 已提交
548 549
	}

J
Joao Moreno 已提交
550
	@command('git.unstageAll')
J
Joao Moreno 已提交
551
	async unstageAll(): Promise<void> {
J
Joao Moreno 已提交
552
		return await this.model.revertFiles();
J
Joao Moreno 已提交
553
	}
J
Joao Moreno 已提交
554

555 556
	@command('git.unstageSelectedRanges', false, true)
	async unstageSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
557 558 559 560 561 562 563 564 565
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

566 567 568 569 570 571 572
		if (modifiedUri.scheme !== 'git') {
			return;
		}

		const { ref } = fromGitUri(modifiedUri);

		if (ref !== '') {
J
Joao Moreno 已提交
573 574 575
			return;
		}

J
Joao Moreno 已提交
576
		const originalUri = toGitUri(modifiedUri, 'HEAD');
J
Joao Moreno 已提交
577
		const originalDocument = await workspace.openTextDocument(originalUri);
578 579 580 581
		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 已提交
582 583 584 585 586

		if (!selectedDiffs.length) {
			return;
		}

587 588
		const invertedDiffs = selectedDiffs.map(invertLineChange);
		const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs);
J
Joao Moreno 已提交
589 590 591 592

		await this.model.stage(modifiedUri, result);
	}

J
Joao Moreno 已提交
593
	@command('git.clean')
594
	async clean(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
595
		if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) {
596
			const resource = this.getSCMResource();
597 598 599 600 601 602 603 604

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

605 606 607
		const resources = resourceStates
			.filter(s => s instanceof Resource && s.resourceGroup instanceof WorkingTreeGroup) as Resource[];

608
		if (!resources.length) {
J
Joao Moreno 已提交
609 610
			return;
		}
J
Joao Moreno 已提交
611

J
Joao Moreno 已提交
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
		const untrackedCount = resources.reduce((s, r) => s + (r.type === Status.UNTRACKED ? 1 : 0), 0);
		let message: string;
		let yes = localize('discard', "Discard Changes");

		if (resources.length === 1) {
			if (untrackedCount > 0) {
				message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(resources[0].resourceUri.fsPath));
				yes = localize('delete file', "Delete file");
			} else {
				message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath));
			}
		} else {
			message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", resources.length);

			if (untrackedCount > 0) {
				message = `${message}\n\n${localize('warn untracked', "This will DELETE {0} untracked files!", untrackedCount)}`;
			}
		}
630

J
Joao Moreno 已提交
631
		const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
632

J
Joao Moreno 已提交
633 634 635 636
		if (pick !== yes) {
			return;
		}

637
		await this.model.clean(...resources);
J
Joao Moreno 已提交
638
	}
J
Joao Moreno 已提交
639

J
Joao Moreno 已提交
640
	@command('git.cleanAll')
J
Joao Moreno 已提交
641
	async cleanAll(): Promise<void> {
J
Joao Moreno 已提交
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
		const config = workspace.getConfiguration('git');
		let scope = config.get<string>('discardAllScope') || 'prompt';
		let resources = this.model.workingTreeGroup.resources;

		if (resources.length === 0) {
			return;
		}

		const untrackedCount = resources.reduce((s, r) => s + (r.type === Status.UNTRACKED ? 1 : 0), 0);

		if (scope === 'prompt' && untrackedCount > 0) {
			const message = localize('there are untracked files', "There are untracked files ({0}) which will be DELETED if discarded.\n\nWould you like to delete untracked files when discarding all changes?", untrackedCount);
			const yes = localize('yes', "Yes");
			const always = localize('always', "Always");
			const no = localize('no', "No");
			const never = localize('never', "Never");
			const pick = await window.showWarningMessage(message, { modal: true }, yes, always, no, never);

			if (typeof pick === 'undefined') {
				return;
			} else if (pick === always) {
				await config.update('discardAllScope', 'all', true);
			} else if (pick === never) {
				await config.update('discardAllScope', 'tracked', true);
			}

			if (pick === never || pick === no) {
				scope = 'tracked';
			}
		}

		if (scope === 'tracked') {
			resources = resources.filter(r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED);
		}

		if (resources.length === 0) {
			return;
		}

		const message = localize('confirm discard all', "Are you sure you want to discard ALL ({0}) changes?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST.", resources.length);
682
		const yes = localize('discardAll', "Discard ALL Changes");
J
Joao Moreno 已提交
683
		const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
684 685 686 687 688

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

J
Joao Moreno 已提交
689
		await this.model.clean(...resources);
J
Joao Moreno 已提交
690 691
	}

J
Joao Moreno 已提交
692
	private async smartCommit(
693
		getCommitMessage: () => Promise<string | undefined>,
J
Joao Moreno 已提交
694 695
		opts?: CommitOptions
	): Promise<boolean> {
696 697
		const config = workspace.getConfiguration('git');
		const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
698
		const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
699
		const noStagedChanges = this.model.indexGroup.resources.length === 0;
700
		const noUnstagedChanges = this.model.workingTreeGroup.resources.length === 0;
701 702

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

J
Joao Moreno 已提交
705 706
			// 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?");
707 708 709 710 711
			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 已提交
712 713 714
				config.update('enableSmartCommit', true, true);
			} else if (pick !== yes) {
				return false; // do not commit on cancel
715 716 717
			}
		}

J
Joao Moreno 已提交
718
		if (!opts) {
719
			opts = { all: noStagedChanges };
J
Joao Moreno 已提交
720 721
		}

722 723 724
		// enable signing of commits if configurated
		opts.signCommit = enableCommitSigning;

J
Joao Moreno 已提交
725 726
		if (
			// no changes
727
			(noStagedChanges && noUnstagedChanges)
J
Joao Moreno 已提交
728
			// or no staged changes and not `all`
729
			|| (!opts.all && noStagedChanges)
J
Joao Moreno 已提交
730
		) {
J
Joao Moreno 已提交
731 732 733 734
			window.showInformationMessage(localize('no changes', "There are no changes to commit."));
			return false;
		}

J
Joao Moreno 已提交
735
		const message = await getCommitMessage();
J
Joao Moreno 已提交
736 737 738 739 740 741

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

J
Joao Moreno 已提交
742
		await this.model.commit(message, opts);
J
Joao Moreno 已提交
743 744 745 746

		return true;
	}

J
Joao Moreno 已提交
747
	private async commitWithAnyInput(opts?: CommitOptions): Promise<void> {
748
		const message = scm.inputBox.value;
J
Joao Moreno 已提交
749
		const getCommitMessage = async () => {
J
Joao Moreno 已提交
750 751 752 753 754 755
			if (message) {
				return message;
			}

			return await window.showInputBox({
				placeHolder: localize('commit message', "Commit message"),
J
Joao Moreno 已提交
756 757
				prompt: localize('provide commit message', "Please provide a commit message"),
				ignoreFocusOut: true
J
Joao Moreno 已提交
758
			});
J
Joao Moreno 已提交
759 760 761
		};

		const didCommit = await this.smartCommit(getCommitMessage, opts);
J
Joao Moreno 已提交
762 763

		if (message && didCommit) {
J
Joao Moreno 已提交
764
			scm.inputBox.value = await this.model.getCommitTemplate();
J
Joao Moreno 已提交
765
		}
J
Joao Moreno 已提交
766 767
	}

J
Joao Moreno 已提交
768
	@command('git.commit')
J
Joao Moreno 已提交
769 770 771 772
	async commit(): Promise<void> {
		await this.commitWithAnyInput();
	}

J
Joao Moreno 已提交
773
	@command('git.commitWithInput')
J
Joao Moreno 已提交
774
	async commitWithInput(): Promise<void> {
J
Joao Moreno 已提交
775 776 777 778
		if (!scm.inputBox.value) {
			return;
		}

J
Joao Moreno 已提交
779
		const didCommit = await this.smartCommit(async () => scm.inputBox.value);
J
Joao Moreno 已提交
780 781

		if (didCommit) {
J
Joao Moreno 已提交
782
			scm.inputBox.value = await this.model.getCommitTemplate();
J
Joao Moreno 已提交
783
		}
J
Joao Moreno 已提交
784 785
	}

J
Joao Moreno 已提交
786
	@command('git.commitStaged')
J
Joao Moreno 已提交
787
	async commitStaged(): Promise<void> {
J
Joao Moreno 已提交
788
		await this.commitWithAnyInput({ all: false });
J
Joao Moreno 已提交
789 790
	}

J
Joao Moreno 已提交
791
	@command('git.commitStagedSigned')
J
Joao Moreno 已提交
792
	async commitStagedSigned(): Promise<void> {
J
Joao Moreno 已提交
793
		await this.commitWithAnyInput({ all: false, signoff: true });
J
Joao Moreno 已提交
794 795
	}

K
Krzysztof Cieślak 已提交
796 797 798 799 800
	@command('git.commitStagedAmend')
	async commitStagedAmend(): Promise<void> {
		await this.commitWithAnyInput({ all: false, amend: true });
	}

J
Joao Moreno 已提交
801
	@command('git.commitAll')
J
Joao Moreno 已提交
802
	async commitAll(): Promise<void> {
J
Joao Moreno 已提交
803
		await this.commitWithAnyInput({ all: true });
J
Joao Moreno 已提交
804 805
	}

J
Joao Moreno 已提交
806
	@command('git.commitAllSigned')
J
Joao Moreno 已提交
807
	async commitAllSigned(): Promise<void> {
J
Joao Moreno 已提交
808
		await this.commitWithAnyInput({ all: true, signoff: true });
J
Joao Moreno 已提交
809 810
	}

K
Krzysztof Cieślak 已提交
811 812 813 814 815
	@command('git.commitAllAmend')
	async commitAllAmend(): Promise<void> {
		await this.commitWithAnyInput({ all: true, amend: true });
	}

J
Joao Moreno 已提交
816
	@command('git.undoCommit')
J
Joao Moreno 已提交
817
	async undoCommit(): Promise<void> {
J
Joao Moreno 已提交
818 819 820 821 822 823 824 825 826
		const HEAD = this.model.HEAD;

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

		const commit = await this.model.getCommit('HEAD');
		await this.model.reset('HEAD~');
		scm.inputBox.value = commit.message;
J
Joao Moreno 已提交
827 828
	}

J
Joao Moreno 已提交
829
	@command('git.checkout')
J
Joao Moreno 已提交
830 831 832 833 834
	async checkout(treeish: string): Promise<void> {
		if (typeof treeish === 'string') {
			return await this.model.checkout(treeish);
		}

J
Joao Moreno 已提交
835
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
836
		const checkoutType = config.get<string>('checkoutType') || 'all';
J
Joao Moreno 已提交
837 838 839
		const includeTags = checkoutType === 'all' || checkoutType === 'tags';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
840 841
		const createBranch = new CreateBranchItem();

J
Joao Moreno 已提交
842 843 844 845 846 847 848 849 850
		const heads = this.model.refs.filter(ref => ref.type === RefType.Head)
			.map(ref => new CheckoutItem(ref));

		const tags = (includeTags ? this.model.refs.filter(ref => ref.type === RefType.Tag) : [])
			.map(ref => new CheckoutTagItem(ref));

		const remoteHeads = (includeRemotes ? this.model.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
			.map(ref => new CheckoutRemoteHeadItem(ref));

J
Joao Moreno 已提交
851
		const picks = [createBranch, ...heads, ...tags, ...remoteHeads];
852
		const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout');
J
Joao Moreno 已提交
853
		const choice = await window.showQuickPick(picks, { placeHolder });
J
Joao Moreno 已提交
854 855 856 857 858 859

		if (!choice) {
			return;
		}

		await choice.run(this.model);
J
Joao Moreno 已提交
860 861
	}

J
Joao Moreno 已提交
862
	@command('git.branch')
J
Joao Moreno 已提交
863 864
	async branch(): Promise<void> {
		const result = await window.showInputBox({
J
Joao Moreno 已提交
865
			placeHolder: localize('branch name', "Branch name"),
J
Joao Moreno 已提交
866 867
			prompt: localize('provide branch name', "Please provide a branch name"),
			ignoreFocusOut: true
J
Joao Moreno 已提交
868
		});
J
Joao Moreno 已提交
869

J
Joao Moreno 已提交
870 871 872
		if (!result) {
			return;
		}
J
Joao Moreno 已提交
873

J
Joao Moreno 已提交
874 875
		const name = result.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
		await this.model.branch(name);
J
Joao Moreno 已提交
876 877
	}

M
Maik Riechert 已提交
878
	@command('git.deleteBranch')
879 880 881 882 883 884 885 886
	async deleteBranch(name: string, force?: boolean): Promise<void> {
		let run: (force?: boolean) => Promise<void>;
		if (typeof name === 'string') {
			run = force => this.model.deleteBranch(name, force);
		} else {
			const currentHead = this.model.HEAD && this.model.HEAD.name;
			const heads = this.model.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead)
				.map(ref => new BranchDeleteItem(ref));
M
Maik Riechert 已提交
887

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

M
Maik Riechert 已提交
891
			if (!choice || !choice.branchName) {
892 893
				return;
			}
M
Maik Riechert 已提交
894
			name = choice.branchName;
895
			run = force => choice.run(this.model, force);
M
Maik Riechert 已提交
896 897
		}

898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
		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 已提交
913 914
	}

915 916 917 918 919 920 921
	@command('git.merge')
	async merge(): Promise<void> {
		const config = workspace.getConfiguration('git');
		const checkoutType = config.get<string>('checkoutType') || 'all';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

		const heads = this.model.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
922 923
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
924 925

		const remoteHeads = (includeRemotes ? this.model.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
926 927
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
928 929

		const picks = [...heads, ...remoteHeads];
930 931
		const placeHolder = localize('select a branch to merge from', 'Select a branch to merge from');
		const choice = await window.showQuickPick<MergeItem>(picks, { placeHolder });
932 933 934 935 936

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
937 938 939 940 941 942 943 944 945 946
		try {
			await choice.run(this.model);
		} 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);
		}
947 948
	}

949 950 951 952 953 954 955 956 957 958 959 960 961 962
	@command('git.createTag')
	async createTag(): Promise<void> {
		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 已提交
963
			prompt: localize('provide tag message', "Please provide a message to annotate the tag"),
964 965 966 967 968 969 970 971
			ignoreFocusOut: true
		});

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

J
Joao Moreno 已提交
972
	@command('git.pullFrom')
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
	async pullFrom(): Promise<void> {
		const remotes = this.model.remotes;

		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;
		}

		this.model.pull(false, pick.label, branchName);
	}

J
Joao Moreno 已提交
1002
	@command('git.pull')
J
Joao Moreno 已提交
1003
	async pull(): Promise<void> {
J
Joao Moreno 已提交
1004 1005 1006 1007 1008 1009 1010 1011
		const remotes = this.model.remotes;

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

		await this.model.pull();
J
Joao Moreno 已提交
1012 1013
	}

J
Joao Moreno 已提交
1014
	@command('git.pullRebase')
J
Joao Moreno 已提交
1015
	async pullRebase(): Promise<void> {
J
Joao Moreno 已提交
1016 1017 1018 1019 1020 1021 1022
		const remotes = this.model.remotes;

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

J
Joao Moreno 已提交
1023
		await this.model.pullWithRebase();
J
Joao Moreno 已提交
1024 1025
	}

J
Joao Moreno 已提交
1026
	@command('git.push')
J
Joao Moreno 已提交
1027
	async push(): Promise<void> {
J
Joao Moreno 已提交
1028 1029 1030 1031 1032 1033 1034 1035
		const remotes = this.model.remotes;

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

		await this.model.push();
J
Joao Moreno 已提交
1036 1037
	}

1038 1039 1040 1041 1042 1043 1044 1045 1046
	@command('git.pushWithTags')
	async pushWithTags(): Promise<void> {
		const remotes = this.model.remotes;

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

1047
		await this.model.pushTags();
1048 1049 1050 1051

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

J
Joao Moreno 已提交
1052
	@command('git.pushTo')
J
Joao Moreno 已提交
1053
	async pushTo(): Promise<void> {
J
Joao Moreno 已提交
1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
		const remotes = this.model.remotes;

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

		if (!this.model.HEAD || !this.model.HEAD.name) {
			window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
			return;
		}

		const branchName = this.model.HEAD.name;
		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 已提交
1075
		this.model.pushTo(pick.label, branchName);
J
Joao Moreno 已提交
1076 1077
	}

J
Joao Moreno 已提交
1078
	@command('git.sync')
J
Joao Moreno 已提交
1079
	async sync(): Promise<void> {
J
Joao Moreno 已提交
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
		const HEAD = this.model.HEAD;

		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 已提交
1102 1103 1104
		await this.model.sync();
	}

J
Joao Moreno 已提交
1105
	@command('git.publish')
J
Joao Moreno 已提交
1106
	async publish(): Promise<void> {
J
Joao Moreno 已提交
1107 1108 1109 1110 1111 1112 1113
		const remotes = this.model.remotes;

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

J
Joao Moreno 已提交
1114 1115
		const branchName = this.model.HEAD && this.model.HEAD.name || '';
		const picks = this.model.remotes.map(r => r.name);
J
Joao Moreno 已提交
1116
		const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
J
Joao Moreno 已提交
1117 1118 1119 1120 1121 1122
		const choice = await window.showQuickPick(picks, { placeHolder });

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
1123
		await this.model.pushTo(choice, branchName, true);
J
Joao Moreno 已提交
1124 1125
	}

J
Joao Moreno 已提交
1126
	@command('git.showOutput')
J
Joao Moreno 已提交
1127 1128 1129 1130
	showOutput(): void {
		this.outputChannel.show();
	}

N
NKumar2 已提交
1131 1132
	@command('git.ignore')
	async ignore(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
1133 1134
		if (resourceStates.length === 0 || !(resourceStates[0].resourceUri instanceof Uri)) {
			const uri = window.activeTextEditor && window.activeTextEditor.document.uri;
N
NKumar2 已提交
1135

J
Joao Moreno 已提交
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
			if (!uri) {
				return;
			}

			return await this.model.ignore([uri]);
		}

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

		if (!uris.length) {
N
NKumar2 已提交
1148 1149 1150
			return;
		}

J
Joao Moreno 已提交
1151
		await this.model.ignore(uris);
N
NKumar2 已提交
1152 1153
	}

K
Krzysztof Cieślak 已提交
1154
	@command('git.stash')
J
Joao Moreno 已提交
1155 1156
	async stash(): Promise<void> {
		if (this.model.workingTreeGroup.resources.length === 0) {
K
Krzysztof Cieślak 已提交
1157 1158 1159
			window.showInformationMessage(localize('no changes stash', "There are no changes to stash."));
			return;
		}
J
Joao Moreno 已提交
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170

		const message = await window.showInputBox({
			prompt: localize('provide stash message', "Optionally provide a stash message"),
			placeHolder: localize('stash message', "Stash message")
		});

		if (typeof message === 'undefined') {
			return;
		}

		await this.model.createStash(message);
K
Krzysztof Cieślak 已提交
1171 1172 1173 1174
	}

	@command('git.stashPop')
	async stashPop(): Promise<void> {
J
Joao Moreno 已提交
1175 1176 1177
		const stashes = await this.model.getStashes();

		if (stashes.length === 0) {
K
Krzysztof Cieślak 已提交
1178 1179 1180 1181
			window.showInformationMessage(localize('no stashes', "There are no stashes to restore."));
			return;
		}

J
Joao Moreno 已提交
1182 1183
		const picks = stashes.map(r => ({ label: `#${r.index}:  ${r.description}`, description: '', details: '', id: r.index }));
		const placeHolder = localize('pick stash to pop', "Pick a stash to pop");
K
Krzysztof Cieślak 已提交
1184 1185 1186 1187 1188
		const choice = await window.showQuickPick(picks, { placeHolder });

		if (!choice) {
			return;
		}
J
Joao Moreno 已提交
1189 1190

		await this.model.popStash(choice.id);
K
Krzysztof Cieślak 已提交
1191 1192 1193 1194
	}

	@command('git.stashPopLatest')
	async stashPopLatest(): Promise<void> {
J
Joao Moreno 已提交
1195 1196 1197
		const stashes = await this.model.getStashes();

		if (stashes.length === 0) {
K
Krzysztof Cieślak 已提交
1198 1199 1200 1201
			window.showInformationMessage(localize('no stashes', "There are no stashes to restore."));
			return;
		}

J
Joao Moreno 已提交
1202 1203
		await this.model.popStash();
	}
K
Krzysztof Cieślak 已提交
1204

J
Joao Moreno 已提交
1205
	private createCommand(id: string, key: string, method: Function, skipModelCheck: boolean): (...args: any[]) => any {
1206
		const result = (...args) => {
J
Joao Moreno 已提交
1207
			if (!skipModelCheck && !this.model) {
J
Joao Moreno 已提交
1208 1209 1210 1211
				window.showInformationMessage(localize('disabled', "Git is either disabled or not supported in this workspace"));
				return;
			}

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

J
Joao Moreno 已提交
1214 1215 1216 1217 1218 1219
			const result = Promise.resolve(method.apply(this, args));

			return result.catch(async err => {
				let message: string;

				switch (err.gitErrorCode) {
1220
					case GitErrorCodes.DirtyWorkTree:
J
Joao Moreno 已提交
1221 1222
						message = localize('clean repo', "Please clean your repository working tree before checkout.");
						break;
1223 1224 1225
					case GitErrorCodes.PushRejected:
						message = localize('cant push', "Can't push refs to remote. Run 'Pull' first to integrate your changes.");
						break;
J
Joao Moreno 已提交
1226
					default:
1227 1228 1229
						const hint = (err.stderr || err.message || String(err))
							.replace(/^error: /mi, '')
							.replace(/^> husky.*$/mi, '')
J
Joao Moreno 已提交
1230
							.split(/[\r\n]/)
1231 1232 1233 1234 1235 1236
							.filter(line => !!line)
						[0];

						message = hint
							? localize('git error details', "Git: {0}", hint)
							: localize('git error', "Git error");
J
Joao Moreno 已提交
1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254

						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();
				}
			});
		};
1255 1256 1257 1258 1259

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

		return result;
J
Joao Moreno 已提交
1260 1261
	}

1262 1263
	private getSCMResource(uri?: Uri): Resource | undefined {
		uri = uri ? uri : window.activeTextEditor && window.activeTextEditor.document.uri;
J
Joao Moreno 已提交
1264 1265

		if (!uri) {
1266
			return undefined;
J
Joao Moreno 已提交
1267 1268 1269
		}

		if (uri.scheme === 'git') {
J
Joao Moreno 已提交
1270 1271
			const { path } = fromGitUri(uri);
			uri = Uri.file(path);
J
Joao Moreno 已提交
1272 1273 1274 1275 1276
		}

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

J
Joao Moreno 已提交
1277 1278
			return this.model.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]
				|| this.model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0];
J
Joao Moreno 已提交
1279 1280 1281
		}
	}

J
Joao Moreno 已提交
1282 1283 1284
	dispose(): void {
		this.disposables.forEach(d => d.dispose());
	}
J
Joao Moreno 已提交
1285
}