commands.ts 38.6 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';
J
Joao Moreno 已提交
10
import { Repository, Resource, Status, CommitOptions, WorkingTreeGroup, IndexGroup, MergeGroup } from './repository';
J
Joao Moreno 已提交
11
import { Model } from './model';
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

J
Joao Moreno 已提交
30
	async run(model: Repository): Promise<void> {
J
Joao Moreno 已提交
31 32 33 34 35 36 37 38 39 40 41 42
		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

J
Joao Moreno 已提交
73
	async run(model: Repository, force?: boolean): Promise<void> {
74
		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

J
Joao Moreno 已提交
88
	async run(model: Repository): 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
class CreateBranchItem implements QuickPickItem {

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

J
Joao Moreno 已提交
98
	async run(model: Repository): Promise<void> {
J
Joao Moreno 已提交
99 100 101 102
		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 model: Model,
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
	async refresh(model: Repository): Promise<void> {
J
Joao Moreno 已提交
150
		await model.status();
J
Joao Moreno 已提交
151
	}
J
Joao Moreno 已提交
152

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

J
Joao Moreno 已提交
158
	private async _openResource(model: Repository, 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: Repository, 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 })
J
Joao Moreno 已提交
310
	async openFile(model: Repository, 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
324
				resource = this.getSCMResource();
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: Repository, 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) {
366
			resource = this.getSCMResource(arg);
D
Duroktar 已提交
367
		} else {
368
			resource = this.getSCMResource();
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 })
J
Joao Moreno 已提交
386
	async openChange(model: Repository, arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
387
		let resources: Resource[] | undefined = undefined;
388

389
		if (arg instanceof Uri) {
390
			const resource = this.getSCMResource(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();
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
	}

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

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

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

437
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
438
		await this.model.add(resources);
J
Joao Moreno 已提交
439 440
	}

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

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

		if (!textEditor) {
			return;
		}

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

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

J
Joao Moreno 已提交
462
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
463
		const originalDocument = await workspace.openTextDocument(originalUri);
464 465 466 467
		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 已提交
468 469 470 471 472

		if (!selectedDiffs.length) {
			return;
		}

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

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

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

		if (!textEditor) {
			return;
		}

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

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

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

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

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

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

543 544
		const resources = scmResources.map(r => r.resourceUri);

J
Joao Moreno 已提交
545
		return await model.revert(resources);
J
Joao Moreno 已提交
546 547
	}

J
Joao Moreno 已提交
548
	@command('git.unstageAll', { model: true })
J
Joao Moreno 已提交
549
	async unstageAll(model: Repository): Promise<void> {
J
Joao Moreno 已提交
550
		return await model.revert([]);
J
Joao Moreno 已提交
551
	}
J
Joao Moreno 已提交
552

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

		if (!textEditor) {
			return;
		}

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

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

		const { ref } = fromGitUri(modifiedUri);

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

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

		if (!selectedDiffs.length) {
			return;
		}

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

J
Joao Moreno 已提交
589
		await model.stage(modifiedUri, result);
J
Joao Moreno 已提交
590 591
	}

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

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

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

J
Joao Moreno 已提交
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
		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)}`;
			}
		}
629

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

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

J
Joao Moreno 已提交
636
		await model.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
637
	}
J
Joao Moreno 已提交
638

J
Joao Moreno 已提交
639
	@command('git.cleanAll', { model: true })
J
Joao Moreno 已提交
640
	async cleanAll(model: Repository): Promise<void> {
J
Joao Moreno 已提交
641 642
		const config = workspace.getConfiguration('git');
		let scope = config.get<string>('discardAllScope') || 'prompt';
643
		let resources = model.workingTreeGroup.resources;
J
Joao Moreno 已提交
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

		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);
681
		const yes = localize('discardAll', "Discard ALL Changes");
J
Joao Moreno 已提交
682
		const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
683 684 685 686 687

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

J
Joao Moreno 已提交
688
		await model.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
689 690
	}

J
Joao Moreno 已提交
691
	private async smartCommit(
J
Joao Moreno 已提交
692
		model: Repository,
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;
J
Joao Moreno 已提交
699 700
		const noStagedChanges = model.indexGroup.resources.length === 0;
		const noUnstagedChanges = 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 model.commit(message, opts);
J
Joao Moreno 已提交
743 744 745 746

		return true;
	}

J
Joao Moreno 已提交
747
	private async commitWithAnyInput(model: Repository, 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
		};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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
		const heads = model.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
843 844
			.map(ref => new CheckoutItem(ref));

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

J
Joao Moreno 已提交
848
		const remoteHeads = (includeRemotes ? model.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
849 850
			.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

		if (!choice) {
			return;
		}

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

J
Joao Moreno 已提交
862
	@command('git.branch', { model: true })
J
Joao Moreno 已提交
863
	async branch(model: Repository): Promise<void> {
J
Joao Moreno 已提交
864
		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
		const name = result.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
J
Joao Moreno 已提交
875
		await model.branch(name);
J
Joao Moreno 已提交
876 877
	}

J
Joao Moreno 已提交
878
	@command('git.deleteBranch', { model: true })
J
Joao Moreno 已提交
879
	async deleteBranch(model: Repository, name: string, force?: boolean): Promise<void> {
880 881
		let run: (force?: boolean) => Promise<void>;
		if (typeof name === 'string') {
J
Joao Moreno 已提交
882
			run = force => model.deleteBranch(name, force);
883
		} else {
J
Joao Moreno 已提交
884 885
			const currentHead = model.HEAD && model.HEAD.name;
			const heads = model.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead)
886
				.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;
J
Joao Moreno 已提交
895
			run = force => choice.run(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
	}

J
Joao Moreno 已提交
915
	@command('git.merge', { model: true })
J
Joao Moreno 已提交
916
	async merge(model: Repository): Promise<void> {
917 918 919 920
		const config = workspace.getConfiguration('git');
		const checkoutType = config.get<string>('checkoutType') || 'all';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
921
		const heads = 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

J
Joao Moreno 已提交
925
		const remoteHeads = (includeRemotes ? 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
		try {
J
Joao Moreno 已提交
938
			await choice.run(model);
J
Joao Moreno 已提交
939 940 941 942 943 944 945 946
		} 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
	}

J
Joao Moreno 已提交
949
	@command('git.createTag', { model: true })
J
Joao Moreno 已提交
950
	async createTag(model: Repository): Promise<void> {
951 952 953 954 955 956 957 958 959 960 961 962
		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
			ignoreFocusOut: true
		});

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

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

		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 已提交
999
		model.pull(false, pick.label, branchName);
1000 1001
	}

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

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

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

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

		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 model.pullWithRebase();
J
Joao Moreno 已提交
1024 1025
	}

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

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

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

J
Joao Moreno 已提交
1038
	@command('git.pushWithTags', { model: true })
J
Joao Moreno 已提交
1039
	async pushWithTags(model: Repository): Promise<void> {
1040
		const remotes = model.remotes;
1041 1042 1043 1044 1045 1046

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

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

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

J
Joao Moreno 已提交
1052
	@command('git.pushTo', { model: true })
J
Joao Moreno 已提交
1053
	async pushTo(model: Repository): Promise<void> {
J
Joao Moreno 已提交
1054
		const remotes = model.remotes;
J
Joao Moreno 已提交
1055 1056 1057 1058 1059 1060

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

J
Joao Moreno 已提交
1061
		if (!model.HEAD || !model.HEAD.name) {
J
Joao Moreno 已提交
1062 1063 1064 1065
			window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
			return;
		}

J
Joao Moreno 已提交
1066
		const branchName = model.HEAD.name;
J
Joao Moreno 已提交
1067 1068 1069 1070 1071 1072 1073 1074
		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
		model.pushTo(pick.label, branchName);
J
Joao Moreno 已提交
1076 1077
	}

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

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

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

		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 = model.HEAD && model.HEAD.name || '';
		const picks = 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 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();
	}

J
Joao Moreno 已提交
1131
	@command('git.ignore', { model: true })
J
Joao Moreno 已提交
1132
	async ignore(model: Repository, ...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
			if (!uri) {
				return;
			}

J
Joao Moreno 已提交
1140
			return await model.ignore([uri]);
J
Joao Moreno 已提交
1141 1142 1143 1144 1145 1146 1147
		}

		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 model.ignore(uris);
N
NKumar2 已提交
1152 1153
	}

1154
	@command('git.stash', { model: true })
J
Joao Moreno 已提交
1155
	async stash(model: Repository): Promise<void> {
1156
		if (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

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

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

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

		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 model.popStash(choice.id);
K
Krzysztof Cieślak 已提交
1191 1192
	}

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

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

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

J
Joao Moreno 已提交
1205
	private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any {
1206
		const result = (...args) => {
J
Joao Moreno 已提交
1207 1208 1209 1210 1211 1212 1213
			// 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 已提交
1214
			if (!options.model) {
J
Joao Moreno 已提交
1215 1216
				result = Promise.resolve(method.apply(this, args));
			} else {
J
Joao Moreno 已提交
1217 1218
				result = this.model.pickRepository().then(repository => {
					if (!repository) {
J
Joao Moreno 已提交
1219 1220 1221
						return Promise.reject(localize('modelnotfound', "Git model not found"));
					}

J
Joao Moreno 已提交
1222
					return Promise.resolve(method.apply(this, [repository, ...args]));
J
Joao Moreno 已提交
1223
				});
J
Joao Moreno 已提交
1224 1225
			}

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

J
Joao Moreno 已提交
1228 1229 1230 1231
			return result.catch(async err => {
				let message: string;

				switch (err.gitErrorCode) {
1232
					case GitErrorCodes.DirtyWorkTree:
J
Joao Moreno 已提交
1233 1234
						message = localize('clean repo', "Please clean your repository working tree before checkout.");
						break;
1235 1236 1237
					case GitErrorCodes.PushRejected:
						message = localize('cant push', "Can't push refs to remote. Run 'Pull' first to integrate your changes.");
						break;
J
Joao Moreno 已提交
1238
					default:
1239 1240 1241
						const hint = (err.stderr || err.message || String(err))
							.replace(/^error: /mi, '')
							.replace(/^> husky.*$/mi, '')
J
Joao Moreno 已提交
1242
							.split(/[\r\n]/)
1243 1244 1245 1246 1247 1248
							.filter(line => !!line)
						[0];

						message = hint
							? localize('git error details', "Git: {0}", hint)
							: localize('git error', "Git error");
J
Joao Moreno 已提交
1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266

						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();
				}
			});
		};
1267 1268 1269 1270 1271

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

		return result;
J
Joao Moreno 已提交
1272 1273
	}

1274 1275
	// TODO@Joao: possibly remove? do we really need to return resources?
	private getSCMResource(uri?: Uri): Resource | undefined {
1276
		uri = uri ? uri : window.activeTextEditor && window.activeTextEditor.document.uri;
J
Joao Moreno 已提交
1277 1278

		if (!uri) {
1279
			return undefined;
J
Joao Moreno 已提交
1280 1281 1282
		}

		if (uri.scheme === 'git') {
J
Joao Moreno 已提交
1283 1284
			const { path } = fromGitUri(uri);
			uri = Uri.file(path);
J
Joao Moreno 已提交
1285 1286 1287 1288
		}

		if (uri.scheme === 'file') {
			const uriString = uri.toString();
J
Joao Moreno 已提交
1289
			const repository = this.model.getRepository(uri);
1290

J
Joao Moreno 已提交
1291
			if (!repository) {
1292 1293
				return undefined;
			}
J
Joao Moreno 已提交
1294

J
Joao Moreno 已提交
1295 1296
			return repository.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]
				|| repository.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0];
J
Joao Moreno 已提交
1297 1298 1299
		}
	}

J
Joao Moreno 已提交
1300 1301 1302
	dispose(): void {
		this.disposables.forEach(d => d.dispose());
	}
J
Joao Moreno 已提交
1303
}