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

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

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

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

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

		if (!ref) {
			return;
		}

		await model.checkout(ref);
	}
}

class CheckoutTagItem extends CheckoutItem {

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

class CheckoutRemoteHeadItem extends CheckoutItem {

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

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

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

62 63 64 65 66 67 68 69 70
interface Command {
	commandId: string;
	key: string;
	method: Function;
	skipModelCheck: boolean;
	requiresDiffInformation: boolean;
}

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

72
function command(commandId: string, skipModelCheck = false, requiresDiffInformation = false): Function {
J
Joao Moreno 已提交
73
	return (target: any, key: string, descriptor: any) => {
J
Joao Moreno 已提交
74 75 76 77
		if (!(typeof descriptor.value === 'function')) {
			throw new Error('not supported');
		}

78
		Commands.push({ commandId, key, method: descriptor.value, skipModelCheck, requiresDiffInformation });
J
Joao Moreno 已提交
79 80
	};
}
J
Joao Moreno 已提交
81

J
Joao Moreno 已提交
82
export class CommandCenter {
J
Joao Moreno 已提交
83

J
Joao Moreno 已提交
84
	private model: Model;
J
Joao Moreno 已提交
85
	private disposables: Disposable[];
J
Joao Moreno 已提交
86

J
Joao Moreno 已提交
87
	constructor(
J
Joao Moreno 已提交
88
		private git: Git,
J
Joao Moreno 已提交
89
		model: Model | undefined,
J
Joao Moreno 已提交
90 91
		private outputChannel: OutputChannel,
		private telemetryReporter: TelemetryReporter
J
Joao Moreno 已提交
92
	) {
J
Joao Moreno 已提交
93 94 95 96
		if (model) {
			this.model = model;
		}

J
Joao Moreno 已提交
97
		this.disposables = Commands
98 99 100 101 102 103 104 105 106
			.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 已提交
107 108
	}

J
Joao Moreno 已提交
109
	@command('git.refresh')
J
Joao Moreno 已提交
110
	async refresh(): Promise<void> {
J
Joao Moreno 已提交
111
		await this.model.status();
J
Joao Moreno 已提交
112
	}
J
Joao Moreno 已提交
113

J
Joao Moreno 已提交
114 115 116 117 118 119
	@command('git.openResource')
	async openResource(resource: Resource): Promise<void> {
		await this._openResource(resource);
	}

	private async _openResource(resource: Resource): Promise<void> {
J
Joao Moreno 已提交
120 121 122 123
		const left = this.getLeftResource(resource);
		const right = this.getRightResource(resource);
		const title = this.getTitle(resource);

J
Joao Moreno 已提交
124 125 126 127 128
		if (!right) {
			// TODO
			console.error('oh no');
			return;
		}
J
Joao Moreno 已提交
129

J
Joao Moreno 已提交
130
		if (!left) {
J
Joao Moreno 已提交
131
			return await commands.executeCommand<void>('vscode.open', right);
J
Joao Moreno 已提交
132 133
		}

J
Joao Moreno 已提交
134
		return await commands.executeCommand<void>('vscode.diff', left, right, title);
J
Joao Moreno 已提交
135 136 137 138 139 140
	}

	private getLeftResource(resource: Resource): Uri | undefined {
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
141
				return resource.original.with({ scheme: 'git', query: 'HEAD' });
J
Joao Moreno 已提交
142 143

			case Status.MODIFIED:
J
Joao Moreno 已提交
144
				return resource.resourceUri.with({ scheme: 'git', query: '~' });
J
Joao Moreno 已提交
145
		}
J
Joao Moreno 已提交
146
	}
J
Joao Moreno 已提交
147

J
Joao Moreno 已提交
148 149 150 151 152
	private getRightResource(resource: Resource): Uri | undefined {
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_ADDED:
			case Status.INDEX_COPIED:
J
Joao Moreno 已提交
153
				return resource.resourceUri.with({ scheme: 'git' });
J
Joao Moreno 已提交
154

J
Joao Moreno 已提交
155
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
156
				return resource.resourceUri.with({ scheme: 'git' });
J
Joao Moreno 已提交
157 158 159

			case Status.INDEX_DELETED:
			case Status.DELETED:
J
Joao Moreno 已提交
160
				return resource.resourceUri.with({ scheme: 'git', query: 'HEAD' });
J
Joao Moreno 已提交
161 162 163 164

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

J
Joao Moreno 已提交
168 169
				if (indexStatus && indexStatus.renameResourceUri) {
					return indexStatus.renameResourceUri;
J
Joao Moreno 已提交
170 171
				}

J
Joao Moreno 已提交
172
				return resource.resourceUri;
J
Joao Moreno 已提交
173

J
Joao Moreno 已提交
174
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
175
				return resource.resourceUri;
J
Joao Moreno 已提交
176 177 178 179
		}
	}

	private getTitle(resource: Resource): string {
J
Joao Moreno 已提交
180
		const basename = path.basename(resource.resourceUri.fsPath);
J
Joao Moreno 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193

		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
				return `${basename} (Index)`;

			case Status.MODIFIED:
				return `${basename} (Working Tree)`;
		}

		return '';
	}

194 195
	@command('git.clone', true)
	async clone(): Promise<void> {
J
Joao Moreno 已提交
196
		const url = await window.showInputBox({
J
Joao Moreno 已提交
197 198
			prompt: localize('repourl', "Repository URL"),
			ignoreFocusOut: true
J
Joao Moreno 已提交
199 200 201
		});

		if (!url) {
202 203
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' });
			return;
J
Joao Moreno 已提交
204 205 206 207
		}

		const parentPath = await window.showInputBox({
			prompt: localize('parent', "Parent Directory"),
J
Joao Moreno 已提交
208 209
			value: os.homedir(),
			ignoreFocusOut: true
J
Joao Moreno 已提交
210 211 212
		});

		if (!parentPath) {
213 214
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' });
			return;
J
Joao Moreno 已提交
215 216
		}

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

220
		try {
221 222 223 224 225 226 227 228 229 230
			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));
			}
231 232
		} catch (err) {
			if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
233 234 235
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
			} else {
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
236 237
			}
			throw err;
J
Joao Moreno 已提交
238 239 240
		}
	}

J
Joao Moreno 已提交
241 242 243 244 245
	@command('git.init')
	async init(): Promise<void> {
		await this.model.init();
	}

J
Joao Moreno 已提交
246
	@command('git.openFile')
247
	async openFile(resource?: Resource): Promise<void> {
J
Joao Moreno 已提交
248 249
		if (!resource) {
			return;
J
Joao Moreno 已提交
250 251
		}

J
Joao Moreno 已提交
252
		return await commands.executeCommand<void>('vscode.open', resource.resourceUri);
J
Joao Moreno 已提交
253 254 255
	}

	@command('git.openChange')
256
	async openChange(resource?: Resource): Promise<void> {
J
Joao Moreno 已提交
257 258
		if (!resource) {
			return;
J
Joao Moreno 已提交
259 260
		}

J
Joao Moreno 已提交
261
		return await this._openResource(resource);
J
Joao Moreno 已提交
262 263
	}

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
	@command('git.openFileFromUri')
	async openFileFromUri(uri?: Uri): Promise<void> {
		const resource = this.getSCMResource(uri);

		if (!resource) {
			return;
		}

		return await commands.executeCommand<void>('vscode.open', resource.resourceUri);
	}

	@command('git.openChangeFromUri')
	async openChangeFromUri(uri?: Uri): Promise<void> {
		const resource = this.getSCMResource(uri);

		if (!resource) {
			return;
		}

		return await this._openResource(resource);
	}

J
Joao Moreno 已提交
286
	@command('git.stage')
287
	async stage(...resourceStates: SourceControlResourceState[]): Promise<void> {
288
		if (resourceStates.length === 0) {
289
			const resource = this.getSCMResource();
290 291 292 293 294 295 296 297

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

298 299 300
		const resources = resourceStates
			.filter(s => s instanceof Resource && (s.resourceGroup instanceof WorkingTreeGroup || s.resourceGroup instanceof MergeGroup)) as Resource[];

301
		if (!resources.length) {
J
Joao Moreno 已提交
302 303
			return;
		}
J
Joao Moreno 已提交
304

305
		return await this.model.add(...resources);
J
Joao Moreno 已提交
306 307
	}

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

313 314
	@command('git.stageSelectedRanges', false, true)
	async stageSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
315 316 317 318 319 320 321 322 323
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

J
Joao Moreno 已提交
324
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
325 326 327
			return;
		}

J
Joao Moreno 已提交
328
		const originalUri = modifiedUri.with({ scheme: 'git', query: '~' });
J
Joao Moreno 已提交
329 330 331 332
		const originalDocument = await workspace.openTextDocument(originalUri);
		const selections = textEditor.selections;
		const selectedDiffs = diffs.filter(diff => {
			const modifiedRange = diff.modifiedEndLineNumber === 0
333
				? new Range(modifiedDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, modifiedDocument.lineAt(diff.modifiedStartLineNumber).range.start)
J
Joao Moreno 已提交
334 335 336 337 338 339 340 341 342 343 344
				: new Range(modifiedDocument.lineAt(diff.modifiedStartLineNumber - 1).range.start, modifiedDocument.lineAt(diff.modifiedEndLineNumber - 1).range.end);

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

		if (!selectedDiffs.length) {
			return;
		}

		const result = staging.applyChanges(originalDocument, modifiedDocument, selectedDiffs);
		await this.model.stage(modifiedUri, result);
J
Joao Moreno 已提交
345
	}
J
Joao Moreno 已提交
346

347 348
	@command('git.revertSelectedRanges', false, true)
	async revertSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

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

		const originalUri = modifiedUri.with({ scheme: 'git', query: '~' });
		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;
		}

		const result = staging.applyChanges(originalDocument, modifiedDocument, selectedDiffs);
		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 已提交
392
	@command('git.unstage')
393
	async unstage(...resourceStates: SourceControlResourceState[]): Promise<void> {
394
		if (resourceStates.length === 0) {
395
			const resource = this.getSCMResource();
396 397 398 399 400 401 402 403

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

404 405 406
		const resources = resourceStates
			.filter(s => s instanceof Resource && s.resourceGroup instanceof IndexGroup) as Resource[];

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

411
		return await this.model.revertFiles(...resources);
J
Joao Moreno 已提交
412 413
	}

J
Joao Moreno 已提交
414
	@command('git.unstageAll')
J
Joao Moreno 已提交
415
	async unstageAll(): Promise<void> {
J
Joao Moreno 已提交
416
		return await this.model.revertFiles();
J
Joao Moreno 已提交
417
	}
J
Joao Moreno 已提交
418

419 420
	@command('git.unstageSelectedRanges', false, true)
	async unstageSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

		if (modifiedUri.scheme !== 'git' || modifiedUri.query !== '') {
			return;
		}

		const originalUri = modifiedUri.with({ scheme: 'git', query: 'HEAD' });
		const originalDocument = await workspace.openTextDocument(originalUri);
		const selections = textEditor.selections;
		const selectedDiffs = diffs.filter(diff => {
			const modifiedRange = diff.modifiedEndLineNumber === 0
				? new Range(diff.modifiedStartLineNumber - 1, 0, diff.modifiedStartLineNumber - 1, 0)
				: new Range(modifiedDocument.lineAt(diff.modifiedStartLineNumber - 1).range.start, modifiedDocument.lineAt(diff.modifiedEndLineNumber - 1).range.end);

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

		if (!selectedDiffs.length) {
			return;
		}

		const invertedDiffs = selectedDiffs.map(c => ({
			modifiedStartLineNumber: c.originalStartLineNumber,
			modifiedEndLineNumber: c.originalEndLineNumber,
			originalStartLineNumber: c.modifiedStartLineNumber,
			originalEndLineNumber: c.modifiedEndLineNumber
		}));

		const result = staging.applyChanges(modifiedDocument, originalDocument, invertedDiffs);
		await this.model.stage(modifiedUri, result);
	}

J
Joao Moreno 已提交
460
	@command('git.clean')
461
	async clean(...resourceStates: SourceControlResourceState[]): Promise<void> {
462
		if (resourceStates.length === 0) {
463
			const resource = this.getSCMResource();
464 465 466 467 468 469 470 471

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

472 473 474
		const resources = resourceStates
			.filter(s => s instanceof Resource && s.resourceGroup instanceof WorkingTreeGroup) as Resource[];

475
		if (!resources.length) {
J
Joao Moreno 已提交
476 477
			return;
		}
J
Joao Moreno 已提交
478

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

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

J
Joao Moreno 已提交
486 487 488 489
		if (pick !== yes) {
			return;
		}

490
		await this.model.clean(...resources);
J
Joao Moreno 已提交
491
	}
J
Joao Moreno 已提交
492

J
Joao Moreno 已提交
493
	@command('git.cleanAll')
J
Joao Moreno 已提交
494
	async cleanAll(): Promise<void> {
495 496
		const message = localize('confirm discard all', "Are you sure you want to discard ALL changes?");
		const yes = localize('discard', "Discard Changes");
J
Joao Moreno 已提交
497
		const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
498 499 500 501 502

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

J
Joao Moreno 已提交
503
		await this.model.clean(...this.model.workingTreeGroup.resources);
J
Joao Moreno 已提交
504 505
	}

J
Joao Moreno 已提交
506 507 508 509 510 511 512 513 514 515 516 517 518 519
	private async smartCommit(
		getCommitMessage: () => Promise<string>,
		opts?: CommitOptions
	): Promise<boolean> {
		if (!opts) {
			opts = { all: this.model.indexGroup.resources.length === 0 };
		}

		if (
			// no changes
			(this.model.indexGroup.resources.length === 0 && this.model.workingTreeGroup.resources.length === 0)
			// or no staged changes and not `all`
			|| (!opts.all && this.model.indexGroup.resources.length === 0)
		) {
J
Joao Moreno 已提交
520 521 522 523
			window.showInformationMessage(localize('no changes', "There are no changes to commit."));
			return false;
		}

J
Joao Moreno 已提交
524
		const message = await getCommitMessage();
J
Joao Moreno 已提交
525 526 527 528 529 530

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

J
Joao Moreno 已提交
531
		await this.model.commit(message, opts);
J
Joao Moreno 已提交
532 533 534 535

		return true;
	}

J
Joao Moreno 已提交
536
	private async commitWithAnyInput(opts?: CommitOptions): Promise<void> {
537
		const message = scm.inputBox.value;
J
Joao Moreno 已提交
538
		const getCommitMessage = async () => {
J
Joao Moreno 已提交
539 540 541 542 543 544
			if (message) {
				return message;
			}

			return await window.showInputBox({
				placeHolder: localize('commit message', "Commit message"),
J
Joao Moreno 已提交
545 546
				prompt: localize('provide commit message', "Please provide a commit message"),
				ignoreFocusOut: true
J
Joao Moreno 已提交
547
			});
J
Joao Moreno 已提交
548 549 550
		};

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

		if (message && didCommit) {
J
Joao Moreno 已提交
553
			scm.inputBox.value = await this.model.getCommitTemplate();
J
Joao Moreno 已提交
554
		}
J
Joao Moreno 已提交
555 556
	}

J
Joao Moreno 已提交
557
	@command('git.commit')
J
Joao Moreno 已提交
558 559 560 561
	async commit(): Promise<void> {
		await this.commitWithAnyInput();
	}

J
Joao Moreno 已提交
562
	@command('git.commitWithInput')
J
Joao Moreno 已提交
563
	async commitWithInput(): Promise<void> {
J
Joao Moreno 已提交
564
		const didCommit = await this.smartCommit(async () => scm.inputBox.value);
J
Joao Moreno 已提交
565 566

		if (didCommit) {
J
Joao Moreno 已提交
567
			scm.inputBox.value = await this.model.getCommitTemplate();
J
Joao Moreno 已提交
568
		}
J
Joao Moreno 已提交
569 570
	}

J
Joao Moreno 已提交
571
	@command('git.commitStaged')
J
Joao Moreno 已提交
572
	async commitStaged(): Promise<void> {
J
Joao Moreno 已提交
573
		await this.commitWithAnyInput({ all: false });
J
Joao Moreno 已提交
574 575
	}

J
Joao Moreno 已提交
576
	@command('git.commitStagedSigned')
J
Joao Moreno 已提交
577
	async commitStagedSigned(): Promise<void> {
J
Joao Moreno 已提交
578
		await this.commitWithAnyInput({ all: false, signoff: true });
J
Joao Moreno 已提交
579 580
	}

J
Joao Moreno 已提交
581
	@command('git.commitAll')
J
Joao Moreno 已提交
582
	async commitAll(): Promise<void> {
J
Joao Moreno 已提交
583
		await this.commitWithAnyInput({ all: true });
J
Joao Moreno 已提交
584 585
	}

J
Joao Moreno 已提交
586
	@command('git.commitAllSigned')
J
Joao Moreno 已提交
587
	async commitAllSigned(): Promise<void> {
J
Joao Moreno 已提交
588
		await this.commitWithAnyInput({ all: true, signoff: true });
J
Joao Moreno 已提交
589 590
	}

J
Joao Moreno 已提交
591
	@command('git.undoCommit')
J
Joao Moreno 已提交
592
	async undoCommit(): Promise<void> {
J
Joao Moreno 已提交
593 594 595 596 597 598 599 600 601
		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 已提交
602 603
	}

J
Joao Moreno 已提交
604
	@command('git.checkout')
J
Joao Moreno 已提交
605 606
	async checkout(): Promise<void> {
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
607
		const checkoutType = config.get<string>('checkoutType') || 'all';
J
Joao Moreno 已提交
608 609 610 611 612 613 614 615 616 617 618 619
		const includeTags = checkoutType === 'all' || checkoutType === 'tags';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

		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 已提交
620 621 622
		const picks = [...heads, ...tags, ...remoteHeads];
		const placeHolder = 'Select a ref to checkout';
		const choice = await window.showQuickPick<CheckoutItem>(picks, { placeHolder });
J
Joao Moreno 已提交
623 624 625 626 627 628

		if (!choice) {
			return;
		}

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

J
Joao Moreno 已提交
631
	@command('git.branch')
J
Joao Moreno 已提交
632 633
	async branch(): Promise<void> {
		const result = await window.showInputBox({
J
Joao Moreno 已提交
634
			placeHolder: localize('branch name', "Branch name"),
J
Joao Moreno 已提交
635 636
			prompt: localize('provide branch name', "Please provide a branch name"),
			ignoreFocusOut: true
J
Joao Moreno 已提交
637
		});
J
Joao Moreno 已提交
638

J
Joao Moreno 已提交
639 640 641
		if (!result) {
			return;
		}
J
Joao Moreno 已提交
642

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

J
Joao Moreno 已提交
647
	@command('git.pull')
J
Joao Moreno 已提交
648
	async pull(): Promise<void> {
J
Joao Moreno 已提交
649 650 651 652 653 654 655 656
		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 已提交
657 658
	}

J
Joao Moreno 已提交
659
	@command('git.pullRebase')
J
Joao Moreno 已提交
660
	async pullRebase(): Promise<void> {
J
Joao Moreno 已提交
661 662 663 664 665 666 667 668
		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(true);
J
Joao Moreno 已提交
669 670
	}

J
Joao Moreno 已提交
671
	@command('git.push')
J
Joao Moreno 已提交
672
	async push(): Promise<void> {
J
Joao Moreno 已提交
673 674 675 676 677 678 679 680
		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 已提交
681 682
	}

J
Joao Moreno 已提交
683
	@command('git.pushTo')
J
Joao Moreno 已提交
684
	async pushTo(): Promise<void> {
J
Joao Moreno 已提交
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
		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;
		}

		this.model.push(pick.label, branchName);
J
Joao Moreno 已提交
707 708
	}

J
Joao Moreno 已提交
709
	@command('git.sync')
J
Joao Moreno 已提交
710
	async sync(): Promise<void> {
J
Joao Moreno 已提交
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
		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 已提交
733 734 735
		await this.model.sync();
	}

J
Joao Moreno 已提交
736
	@command('git.publish')
J
Joao Moreno 已提交
737
	async publish(): Promise<void> {
J
Joao Moreno 已提交
738 739 740 741 742 743 744
		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 已提交
745 746
		const branchName = this.model.HEAD && this.model.HEAD.name || '';
		const picks = this.model.remotes.map(r => r.name);
J
Joao Moreno 已提交
747
		const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
J
Joao Moreno 已提交
748 749 750 751 752 753 754 755 756
		const choice = await window.showQuickPick(picks, { placeHolder });

		if (!choice) {
			return;
		}

		await this.model.push(choice, branchName, { setUpstream: true });
	}

J
Joao Moreno 已提交
757
	@command('git.showOutput')
J
Joao Moreno 已提交
758 759 760 761
	showOutput(): void {
		this.outputChannel.show();
	}

J
Joao Moreno 已提交
762
	private createCommand(id: string, key: string, method: Function, skipModelCheck: boolean): (...args: any[]) => any {
763
		const result = (...args) => {
J
Joao Moreno 已提交
764
			if (!skipModelCheck && !this.model) {
J
Joao Moreno 已提交
765 766 767 768
				window.showInformationMessage(localize('disabled', "Git is either disabled or not supported in this workspace"));
				return;
			}

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

J
Joao Moreno 已提交
771 772 773 774 775 776 777 778 779 780
			const result = Promise.resolve(method.apply(this, args));

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

				switch (err.gitErrorCode) {
					case 'DirtyWorkTree':
						message = localize('clean repo', "Please clean your repository working tree before checkout.");
						break;
					default:
781 782 783
						const hint = (err.stderr || err.message || String(err))
							.replace(/^error: /mi, '')
							.replace(/^> husky.*$/mi, '')
J
Joao Moreno 已提交
784
							.split(/[\r\n]/)
785 786 787 788 789 790
							.filter(line => !!line)
						[0];

						message = hint
							? localize('git error details', "Git: {0}", hint)
							: localize('git error', "Git error");
J
Joao Moreno 已提交
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808

						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();
				}
			});
		};
809 810 811 812 813

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

		return result;
J
Joao Moreno 已提交
814 815
	}

816 817
	private getSCMResource(uri?: Uri): Resource | undefined {
		uri = uri ? uri : window.activeTextEditor && window.activeTextEditor.document.uri;
J
Joao Moreno 已提交
818 819

		if (!uri) {
820
			return undefined;
J
Joao Moreno 已提交
821 822 823 824 825 826 827 828 829
		}

		if (uri.scheme === 'git') {
			uri = uri.with({ scheme: 'file' });
		}

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

J
Joao Moreno 已提交
830 831
			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 已提交
832 833 834
		}
	}

J
Joao Moreno 已提交
835 836 837
	dispose(): void {
		this.disposables.forEach(d => d.dispose());
	}
J
Joao Moreno 已提交
838
}