commands.ts 55.4 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, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, CancellationTokenSource, StatusBarAlignment } from 'vscode';
J
Joao Moreno 已提交
9
import { Ref, RefType, Git, GitErrorCodes, Branch } from './git';
10
import { Repository, Resource, Status, CommitOptions, ResourceGroupType, RepositoryState } from './repository';
J
Joao Moreno 已提交
11
import { Model } from './model';
J
Joao Moreno 已提交
12
import { toGitUri, fromGitUri } from './uri';
13
import { grep, eventToPromise, isDescendant } from './util';
J
Joao Moreno 已提交
14
import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange, getModifiedRange } from './staging';
J
Joao Moreno 已提交
15
import * as path from 'path';
16
import { lstat, Stats } from 'fs';
J
Joao Moreno 已提交
17
import * as os from 'os';
J
Joao Moreno 已提交
18
import TelemetryReporter from 'vscode-extension-telemetry';
J
Joao Moreno 已提交
19 20 21
import * as nls from 'vscode-nls';

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

J
Joao Moreno 已提交
23 24 25 26 27 28 29
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 已提交
30
	constructor(protected ref: Ref) { }
J
Joao Moreno 已提交
31

J
Joao Moreno 已提交
32
	async run(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
33 34 35 36 37 38
		const ref = this.treeish;

		if (!ref) {
			return;
		}

J
Joao Moreno 已提交
39
		await repository.checkout(ref);
J
Joao Moreno 已提交
40 41 42 43 44
	}
}

class CheckoutTagItem extends CheckoutItem {

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

class CheckoutRemoteHeadItem extends CheckoutItem {

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

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

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

73
	constructor(private ref: Ref) { }
M
Maik Riechert 已提交
74

J
Joao Moreno 已提交
75
	async run(repository: Repository, force?: boolean): Promise<void> {
76
		if (!this.branchName) {
M
Maik Riechert 已提交
77 78
			return;
		}
J
Joao Moreno 已提交
79
		await repository.deleteBranch(this.branchName, force);
M
Maik Riechert 已提交
80 81 82
	}
}

83 84 85 86 87 88
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 已提交
89

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

J
Joao Moreno 已提交
95 96
class CreateBranchItem implements QuickPickItem {

J
Joao Moreno 已提交
97 98
	constructor(private cc: CommandCenter) { }

J
Joao Moreno 已提交
99 100 101
	get label(): string { return localize('create branch', '$(plus) Create new branch'); }
	get description(): string { return ''; }

J
Joao Moreno 已提交
102
	async run(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
103
		await this.cc.branch(repository);
J
Joao Moreno 已提交
104 105 106
	}
}

J
Joao Moreno 已提交
107
interface CommandOptions {
J
Joao Moreno 已提交
108
	repository?: boolean;
J
Joao Moreno 已提交
109 110 111
	diff?: boolean;
}

112 113 114 115
interface Command {
	commandId: string;
	key: string;
	method: Function;
J
Joao Moreno 已提交
116
	options: CommandOptions;
117 118 119
}

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

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

J
Joao Moreno 已提交
127
		Commands.push({ commandId, key, method: descriptor.value, options });
J
Joao Moreno 已提交
128 129
	};
}
J
Joao Moreno 已提交
130

J
Joao Moreno 已提交
131 132 133 134 135 136 137 138 139
const ImageMimetypes = [
	'image/png',
	'image/gif',
	'image/jpeg',
	'image/webp',
	'image/tiff',
	'image/bmp'
];

J
Joao Moreno 已提交
140
export class CommandCenter {
J
Joao Moreno 已提交
141 142

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

J
Joao Moreno 已提交
144
	constructor(
J
Joao Moreno 已提交
145
		private git: Git,
J
Joao Moreno 已提交
146
		private model: Model,
J
Joao Moreno 已提交
147 148
		private outputChannel: OutputChannel,
		private telemetryReporter: TelemetryReporter
J
Joao Moreno 已提交
149
	) {
J
Joao Moreno 已提交
150 151
		this.disposables = Commands.map(({ commandId, key, method, options }) => {
			const command = this.createCommand(commandId, key, method, options);
152

J
Joao Moreno 已提交
153
			if (options.diff) {
J
Joao Moreno 已提交
154 155 156 157 158
				return commands.registerDiffInformationCommand(commandId, command);
			} else {
				return commands.registerCommand(commandId, command);
			}
		});
J
Joao Moreno 已提交
159 160
	}

J
Joao Moreno 已提交
161 162
	@command('git.refresh', { repository: true })
	async refresh(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
163
		await repository.status();
J
Joao Moreno 已提交
164
	}
J
Joao Moreno 已提交
165

J
Joao Moreno 已提交
166 167
	@command('git.openResource')
	async openResource(resource: Resource): Promise<void> {
J
Joao 已提交
168
		await this._openResource(resource, undefined, true, false);
J
Joao Moreno 已提交
169 170
	}

J
Joao 已提交
171
	private async _openResource(resource: Resource, preview?: boolean, preserveFocus?: boolean, preserveSelection?: boolean): Promise<void> {
172
		let stat: Stats | undefined;
173

174 175 176 177
		try {
			stat = await new Promise<Stats>((c, e) => lstat(resource.resourceUri.fsPath, (err, stat) => err ? e(err) : c(stat)));
		} catch (err) {
			// noop
178
		}
179

180 181 182 183
		let left: Uri | undefined;
		let right: Uri | undefined;

		if (stat && stat.isDirectory()) {
184 185 186 187
			const repository = this.model.getRepositoryForSubmodule(resource.resourceUri);

			if (repository) {
				right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root });
188 189 190 191 192
			}
		} else {
			left = await this.getLeftResource(resource);
			right = await this.getRightResource(resource);
		}
193

J
Joao Moreno 已提交
194 195
		const title = this.getTitle(resource);

J
Joao Moreno 已提交
196 197 198 199 200
		if (!right) {
			// TODO
			console.error('oh no');
			return;
		}
J
Joao Moreno 已提交
201

J
Joao Moreno 已提交
202
		const opts: TextDocumentShowOptions = {
J
Joao Moreno 已提交
203 204
			preserveFocus,
			preview,
205
			viewColumn: ViewColumn.Active
J
Joao Moreno 已提交
206 207
		};

J
Joao Moreno 已提交
208 209
		const activeTextEditor = window.activeTextEditor;

210 211 212
		// Check if active text editor has same path as other editor. we cannot compare via
		// URI.toString() here because the schemas can be different. Instead we just go by path.
		if (preserveSelection && activeTextEditor && activeTextEditor.document.uri.path === right.path) {
J
Joao Moreno 已提交
213 214 215
			opts.selection = activeTextEditor.selection;
		}

216
		if (!left) {
J
Joao Moreno 已提交
217 218 219 220 221
			await commands.executeCommand<void>('vscode.open', right, opts);
		} else {
			await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
		}
	}
J
Joao Moreno 已提交
222

J
Joao Moreno 已提交
223 224
	private async getURI(uri: Uri, ref: string): Promise<Uri | undefined> {
		const repository = this.model.getRepository(uri);
J
Joao Moreno 已提交
225

J
Joao Moreno 已提交
226 227 228
		if (!repository) {
			return toGitUri(uri, ref);
		}
J
Joao Moreno 已提交
229

J
Joao Moreno 已提交
230
		try {
J
Joao Moreno 已提交
231 232 233
			let gitRef = ref;

			if (gitRef === '~') {
J
Joao Moreno 已提交
234
				const uriString = uri.toString();
J
Joao Moreno 已提交
235
				const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
J
Joao Moreno 已提交
236
				gitRef = indexStatus ? '' : 'HEAD';
J
Joao Moreno 已提交
237 238
			}

J
Joao Moreno 已提交
239
			const { size, object } = await repository.lstree(gitRef, uri.fsPath);
J
Joao Moreno 已提交
240
			const { mimetype, encoding } = await repository.detectObjectType(object);
J
Joao Moreno 已提交
241

J
Joao Moreno 已提交
242 243 244
			if (mimetype === 'text/plain') {
				return toGitUri(uri, ref);
			}
245

J
Joao Moreno 已提交
246
			if (size > 1000000) { // 1 MB
J
Joao Moreno 已提交
247
				return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`);
J
Joao Moreno 已提交
248 249
			}

J
Joao Moreno 已提交
250
			if (ImageMimetypes.indexOf(mimetype) > -1) {
J
Joao Moreno 已提交
251 252
				const contents = await repository.buffer(gitRef, uri.fsPath);
				return Uri.parse(`data:${mimetype};label:${path.basename(uri.fsPath)};description:${gitRef};size:${size};base64,${contents.toString('base64')}`);
J
Joao Moreno 已提交
253 254
			}

J
Joao Moreno 已提交
255
			return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`);
J
Joao Moreno 已提交
256 257 258 259

		} catch (err) {
			return toGitUri(uri, ref);
		}
J
Joao Moreno 已提交
260 261
	}

J
Joao Moreno 已提交
262
	private async getLeftResource(resource: Resource): Promise<Uri | undefined> {
J
Joao Moreno 已提交
263 264 265
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
266
				return this.getURI(resource.original, 'HEAD');
J
Joao Moreno 已提交
267 268

			case Status.MODIFIED:
J
Joao Moreno 已提交
269
				return this.getURI(resource.resourceUri, '~');
270 271

			case Status.DELETED_BY_THEM:
J
Joao Moreno 已提交
272
				return this.getURI(resource.resourceUri, '');
J
Joao Moreno 已提交
273
		}
J
Joao Moreno 已提交
274
	}
J
Joao Moreno 已提交
275

J
Joao Moreno 已提交
276
	private async getRightResource(resource: Resource): Promise<Uri | undefined> {
J
Joao Moreno 已提交
277 278 279 280 281
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_ADDED:
			case Status.INDEX_COPIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
282
				return this.getURI(resource.resourceUri, '');
J
Joao Moreno 已提交
283 284

			case Status.INDEX_DELETED:
285
			case Status.DELETED_BY_THEM:
J
Joao Moreno 已提交
286
			case Status.DELETED:
J
Joao Moreno 已提交
287
				return this.getURI(resource.resourceUri, 'HEAD');
J
Joao Moreno 已提交
288 289 290 291

			case Status.MODIFIED:
			case Status.UNTRACKED:
			case Status.IGNORED:
J
Joao Moreno 已提交
292 293 294 295 296 297
				const repository = this.model.getRepository(resource.resourceUri);

				if (!repository) {
					return;
				}

J
Joao Moreno 已提交
298
				const uriString = resource.resourceUri.toString();
J
Joao Moreno 已提交
299
				const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
J
Joao Moreno 已提交
300

J
Joao Moreno 已提交
301 302
				if (indexStatus && indexStatus.renameResourceUri) {
					return indexStatus.renameResourceUri;
J
Joao Moreno 已提交
303 304
				}

J
Joao Moreno 已提交
305
				return resource.resourceUri;
J
Joao Moreno 已提交
306

307
			case Status.BOTH_ADDED:
J
Joao Moreno 已提交
308
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
309
				return resource.resourceUri;
J
Joao Moreno 已提交
310 311 312 313
		}
	}

	private getTitle(resource: Resource): string {
J
Joao Moreno 已提交
314
		const basename = path.basename(resource.resourceUri.fsPath);
J
Joao Moreno 已提交
315 316 317 318

		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
M
Marc Kassay 已提交
319
			case Status.DELETED_BY_THEM:
J
Joao Moreno 已提交
320 321 322
				return `${basename} (Index)`;

			case Status.MODIFIED:
M
Marc Kassay 已提交
323 324
			case Status.BOTH_ADDED:
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
325 326 327 328 329 330
				return `${basename} (Working Tree)`;
		}

		return '';
	}

J
Joao Moreno 已提交
331 332
	private static cloneId = 0;

J
Joao Moreno 已提交
333
	@command('git.clone')
334 335 336 337 338 339 340
	async clone(url?: string): Promise<void> {
		if (!url) {
			url = await window.showInputBox({
				prompt: localize('repourl', "Repository URL"),
				ignoreFocusOut: true
			});
		}
J
Joao Moreno 已提交
341 342

		if (!url) {
K
kieferrm 已提交
343
			/* __GDPR__
K
kieferrm 已提交
344 345 346 347
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
348 349
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' });
			return;
J
Joao Moreno 已提交
350 351
		}

352
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
353
		let value = config.get<string>('defaultCloneDirectory') || os.homedir();
354

J
Joao Moreno 已提交
355 356
		const parentPath = await window.showInputBox({
			prompt: localize('parent', "Parent Directory"),
J
Joao Moreno 已提交
357
			value,
J
Joao Moreno 已提交
358
			ignoreFocusOut: true
J
Joao Moreno 已提交
359 360 361
		});

		if (!parentPath) {
K
kieferrm 已提交
362
			/* __GDPR__
K
kieferrm 已提交
363 364 365 366
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
367 368
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' });
			return;
J
Joao Moreno 已提交
369 370
		}

J
Joao Moreno 已提交
371 372 373 374 375 376 377 378 379
		const tokenSource = new CancellationTokenSource();
		const cancelCommandId = `cancelClone${CommandCenter.cloneId++}`;
		const commandDisposable = commands.registerCommand(cancelCommandId, () => tokenSource.cancel());

		const statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
		statusBarItem.text = localize('cancel', "$(sync~spin) Cloning repository... Click to cancel");
		statusBarItem.tooltip = localize('cancel tooltip', "Cancel clone");
		statusBarItem.command = cancelCommandId;
		statusBarItem.show();
M
Maryam Archie 已提交
380

J
Joao Moreno 已提交
381
		const clonePromise = this.git.clone(url, parentPath.replace(/^~/, os.homedir()), tokenSource.token);
J
Joao Moreno 已提交
382

383
		try {
M
Maryam Archie 已提交
384 385
			window.withProgress({ location: ProgressLocation.SourceControl, title: localize('cloning', "Cloning git repository...") }, () => clonePromise);

386 387 388 389 390 391
			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;
K
kieferrm 已提交
392
			/* __GDPR__
K
kieferrm 已提交
393 394 395 396 397
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
					"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
				}
			*/
398 399 400 401
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 });
			if (openFolder) {
				commands.executeCommand('vscode.openFolder', Uri.file(repositoryPath));
			}
402 403
		} catch (err) {
			if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
K
kieferrm 已提交
404
				/* __GDPR__
K
kieferrm 已提交
405 406 407 408
					"clone" : {
						"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
409
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
J
Joao Moreno 已提交
410 411
			} else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) {
				return;
412
			} else {
K
kieferrm 已提交
413
				/* __GDPR__
K
kieferrm 已提交
414 415 416 417
					"clone" : {
						"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
418
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
419
			}
J
Joao Moreno 已提交
420

421
			throw err;
J
Joao Moreno 已提交
422 423 424
		} finally {
			commandDisposable.dispose();
			statusBarItem.dispose();
J
Joao Moreno 已提交
425 426 427
		}
	}

J
Joao Moreno 已提交
428
	@command('git.init')
J
Joao Moreno 已提交
429
	async init(): Promise<void> {
J
Joao Moreno 已提交
430
		let path: string | undefined;
J
Joao Moreno 已提交
431

J
Joao Moreno 已提交
432 433 434 435 436 437 438 439
		if (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) {
			const placeHolder = localize('init', "Pick workspace folder to initialize git repo in");
			const items = workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder }));
			const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });

			if (!item) {
				return;
			}
J
Joao Moreno 已提交
440

J
Joao Moreno 已提交
441 442
			path = item.folder.uri.fsPath;
		}
J
Joao Moreno 已提交
443

J
Joao Moreno 已提交
444 445 446 447 448 449 450 451 452 453 454 455 456
		if (!path) {
			const homeUri = Uri.file(os.homedir());
			const defaultUri = workspace.workspaceFolders && workspace.workspaceFolders.length > 0
				? Uri.file(workspace.workspaceFolders[0].uri.fsPath)
				: homeUri;

			const result = await window.showOpenDialog({
				canSelectFiles: false,
				canSelectFolders: true,
				canSelectMany: false,
				defaultUri,
				openLabel: localize('init repo', "Initialize Repository")
			});
J
Joao Moreno 已提交
457

J
Joao Moreno 已提交
458
			if (!result || result.length === 0) {
J
Joao Moreno 已提交
459 460
				return;
			}
J
Joao Moreno 已提交
461 462 463 464 465 466 467 468 469 470 471 472 473

			const uri = result[0];

			if (homeUri.toString().startsWith(uri.toString())) {
				const yes = localize('create repo', "Initialize Repository");
				const answer = await window.showWarningMessage(localize('are you sure', "This will create a Git repository in '{0}'. Are you sure you want to continue?", uri.fsPath), yes);

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

			path = uri.fsPath;
J
Joao Moreno 已提交
474 475
		}

J
Joao Moreno 已提交
476 477
		await this.git.init(path);
		await this.model.tryOpenRepository(path);
J
Joao Moreno 已提交
478 479
	}

J
Joao Moreno 已提交
480 481 482 483 484
	@command('git.close', { repository: true })
	async close(repository: Repository): Promise<void> {
		this.model.close(repository);
	}

J
Joao Moreno 已提交
485 486
	@command('git.openFile')
	async openFile(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
487
		const preserveFocus = arg instanceof Resource;
J
Joao Moreno 已提交
488

489
		let uris: Uri[] | undefined;
490 491 492

		if (arg instanceof Uri) {
			if (arg.scheme === 'git') {
493
				uris = [Uri.file(fromGitUri(arg).path)];
494
			} else if (arg.scheme === 'file') {
495
				uris = [arg];
496 497 498 499 500 501
			}
		} else {
			let resource = arg;

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

			if (resource) {
J
Joao Moreno 已提交
506 507 508 509
				const resources = ([resource, ...resourceStates] as Resource[])
					.filter(r => r.type !== Status.DELETED && r.type !== Status.INDEX_DELETED);

				uris = resources.map(r => r.resourceUri);
510
			}
J
Joao Moreno 已提交
511 512
		}

513
		if (!uris) {
J
Joao Moreno 已提交
514
			return;
J
Joao Moreno 已提交
515 516
		}

517 518 519 520
		const preview = uris.length === 1 ? true : false;
		const activeTextEditor = window.activeTextEditor;
		for (const uri of uris) {
			const opts: TextDocumentShowOptions = {
J
Joao Moreno 已提交
521
				preserveFocus,
522
				preview,
523
				viewColumn: ViewColumn.Active
524 525
			};

526 527 528
			// Check if active text editor has same path as other editor. we cannot compare via
			// URI.toString() here because the schemas can be different. Instead we just go by path.
			if (activeTextEditor && activeTextEditor.document.uri.path === uri.path) {
B
Benjamin Pasero 已提交
529 530 531
				opts.selection = activeTextEditor.selection;
			}

532
			await commands.executeCommand<void>('vscode.open', uri, opts);
J
Joao Moreno 已提交
533
		}
J
Joao Moreno 已提交
534 535
	}

J
Joao Moreno 已提交
536 537 538 539 540
	@command('git.openFile2')
	async openFile2(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
		this.openFile(arg, ...resourceStates);
	}

J
Joao Moreno 已提交
541 542
	@command('git.openHEADFile')
	async openHEADFile(arg?: Resource | Uri): Promise<void> {
D
Duroktar 已提交
543 544 545 546 547
		let resource: Resource | undefined = undefined;

		if (arg instanceof Resource) {
			resource = arg;
		} else if (arg instanceof Uri) {
548
			resource = this.getSCMResource(arg);
D
Duroktar 已提交
549
		} else {
550
			resource = this.getSCMResource();
D
Duroktar 已提交
551 552 553 554 555 556
		}

		if (!resource) {
			return;
		}

J
Joao Moreno 已提交
557
		const HEAD = await this.getLeftResource(resource);
D
Duroktar 已提交
558

J
Joao Moreno 已提交
559 560 561
		if (!HEAD) {
			window.showWarningMessage(localize('HEAD not available', "HEAD version of '{0}' is not available.", path.basename(resource.resourceUri.fsPath)));
			return;
D
Duroktar 已提交
562
		}
J
Joao Moreno 已提交
563 564

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

J
Joao Moreno 已提交
567 568
	@command('git.openChange')
	async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
569
		const preserveFocus = arg instanceof Resource;
J
Joao 已提交
570
		const preserveSelection = arg instanceof Uri || !arg;
571
		let resources: Resource[] | undefined = undefined;
572

573
		if (arg instanceof Uri) {
574
			const resource = this.getSCMResource(arg);
575 576 577
			if (resource !== undefined) {
				resources = [resource];
			}
578
		} else {
579
			let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
580

581 582 583
			if (arg instanceof Resource) {
				resource = arg;
			} else {
584
				resource = this.getSCMResource();
585 586 587 588 589
			}

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

592
		if (!resources) {
J
Joao Moreno 已提交
593
			return;
J
Joao Moreno 已提交
594
		}
J
Joao Moreno 已提交
595

596 597
		const preview = resources.length === 1 ? undefined : false;
		for (const resource of resources) {
J
Joao 已提交
598
			await this._openResource(resource, preview, preserveFocus, preserveSelection);
599
		}
J
Joao Moreno 已提交
600 601
	}

602 603
	@command('git.stage')
	async stage(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
604
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
605
			const resource = this.getSCMResource();
606 607 608 609 610 611 612 613

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

614
		const selection = resourceStates.filter(s => s instanceof Resource) as Resource[];
J
Joao Moreno 已提交
615 616 617 618 619 620 621 622 623 624 625 626 627 628
		const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge);
		const bothModified = merge.filter(s => s.type === Status.BOTH_MODIFIED);
		const promises = bothModified.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/));
		const unresolvedBothModified = await Promise.all<boolean>(promises);
		const resolvedConflicts = bothModified.filter((s, i) => !unresolvedBothModified[i]);
		const unresolvedConflicts = [
			...merge.filter(s => s.type !== Status.BOTH_MODIFIED),
			...bothModified.filter((s, i) => unresolvedBothModified[i])
		];

		if (unresolvedConflicts.length > 0) {
			const message = unresolvedConflicts.length > 1
				? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", unresolvedConflicts.length)
				: localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(unresolvedConflicts[0].resourceUri.fsPath));
629

630 631 632 633 634 635 636 637
			const yes = localize('yes', "Yes");
			const pick = await window.showWarningMessage(message, { modal: true }, yes);

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

J
Joao Moreno 已提交
638 639
		const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree);
		const scmResources = [...workingTree, ...resolvedConflicts, ...unresolvedConflicts];
640

641
		if (!scmResources.length) {
J
Joao Moreno 已提交
642 643
			return;
		}
J
Joao Moreno 已提交
644

645
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
646
		await this.runByRepository(resources, async (repository, resources) => repository.add(resources));
J
Joao Moreno 已提交
647 648
	}

J
Joao Moreno 已提交
649 650
	@command('git.stageAll', { repository: true })
	async stageAll(repository: Repository): Promise<void> {
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
		const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[];
		const mergeConflicts = resources.filter(s => s.resourceGroupType === ResourceGroupType.Merge);

		if (mergeConflicts.length > 0) {
			const message = mergeConflicts.length > 1
				? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", mergeConflicts.length)
				: localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(mergeConflicts[0].resourceUri.fsPath));

			const yes = localize('yes', "Yes");
			const pick = await window.showWarningMessage(message, { modal: true }, yes);

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

J
Joao Moreno 已提交
667
		await repository.add([]);
J
Joao Moreno 已提交
668 669
	}

J
Joao Moreno 已提交
670
	@command('git.stageChange')
J
Joao Moreno 已提交
671 672 673 674 675 676 677 678
	async stageChange(uri: Uri, changes: LineChange[], index: number): Promise<void> {
		const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0];

		if (!textEditor) {
			return;
		}

		await this._stageChanges(textEditor, [changes[index]]);
J
Joao Moreno 已提交
679 680
	}

J
Joao Moreno 已提交
681
	@command('git.stageSelectedRanges', { diff: true })
J
Joao Moreno 已提交
682
	async stageSelectedChanges(changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
683 684 685 686 687 688 689
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
J
Joao Moreno 已提交
690 691 692 693
		const selectedLines = toLineRanges(textEditor.selections, modifiedDocument);
		const selectedChanges = changes
			.map(diff => selectedLines.reduce<LineChange | null>((result, range) => result || intersectDiffWithRange(modifiedDocument, diff, range), null))
			.filter(d => !!d) as LineChange[];
J
Joao Moreno 已提交
694

J
Joao Moreno 已提交
695
		if (!selectedChanges.length) {
J
Joao Moreno 已提交
696 697 698
			return;
		}

J
Joao Moreno 已提交
699 700
		await this._stageChanges(textEditor, selectedChanges);
	}
J
Joao Moreno 已提交
701

J
Joao Moreno 已提交
702
	private async _stageChanges(textEditor: TextEditor, changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
703 704 705
		const modifiedDocument = textEditor.document;
		const modifiedUri = modifiedDocument.uri;

J
Joao Moreno 已提交
706
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
707 708 709
			return;
		}

J
Joao Moreno 已提交
710
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
711
		const originalDocument = await workspace.openTextDocument(originalUri);
J
Joao Moreno 已提交
712
		const result = applyLineChanges(originalDocument, modifiedDocument, changes);
J
Joao Moreno 已提交
713

J
Joao Moreno 已提交
714
		await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
J
Joao Moreno 已提交
715
	}
J
Joao Moreno 已提交
716

J
Joao Moreno 已提交
717
	@command('git.revertChange')
J
Joao Moreno 已提交
718 719 720 721
	async revertChange(uri: Uri, changes: LineChange[], index: number): Promise<void> {
		const textEditor = window.visibleTextEditors.filter(e => e.document.uri.toString() === uri.toString())[0];

		if (!textEditor) {
J
Joao Moreno 已提交
722 723 724
			return;
		}

J
Joao Moreno 已提交
725
		await this._revertChanges(textEditor, [...changes.slice(0, index), ...changes.slice(index + 1)]);
J
Joao Moreno 已提交
726
	}
J
Joao Moreno 已提交
727

J
Joao Moreno 已提交
728
	@command('git.revertSelectedRanges', { diff: true })
J
Joao Moreno 已提交
729
	async revertSelectedRanges(changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
730 731 732 733 734 735 736
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
J
Joao Moreno 已提交
737 738
		const selections = textEditor.selections;
		const selectedChanges = changes.filter(change => {
J
Joao Moreno 已提交
739
			const modifiedRange = getModifiedRange(modifiedDocument, change);
J
Joao Moreno 已提交
740 741 742 743
			return selections.every(selection => !selection.intersection(modifiedRange));
		});

		if (selectedChanges.length === changes.length) {
J
Joao Moreno 已提交
744 745 746
			return;
		}

J
Joao Moreno 已提交
747 748
		await this._revertChanges(textEditor, selectedChanges);
	}
J
Joao Moreno 已提交
749

J
Joao Moreno 已提交
750 751 752
	private async _revertChanges(textEditor: TextEditor, changes: LineChange[]): Promise<void> {
		const modifiedDocument = textEditor.document;
		const modifiedUri = modifiedDocument.uri;
J
Joao Moreno 已提交
753

J
Joao Moreno 已提交
754
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
755 756 757
			return;
		}

J
Joao Moreno 已提交
758 759
		const originalUri = toGitUri(modifiedUri, '~');
		const originalDocument = await workspace.openTextDocument(originalUri);
J
Joao Moreno 已提交
760 761 762 763 764 765 766 767 768
		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;
		}

J
Joao Moreno 已提交
769
		const result = applyLineChanges(originalDocument, modifiedDocument, changes);
J
Joao Moreno 已提交
770 771 772
		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 已提交
773
		await modifiedDocument.save();
J
Joao Moreno 已提交
774 775
	}

J
Joao Moreno 已提交
776 777
	@command('git.unstage')
	async unstage(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
778
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
779
			const resource = this.getSCMResource();
780 781 782 783 784 785 786 787

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

788
		const scmResources = resourceStates
J
Joao Moreno 已提交
789
			.filter(s => s instanceof Resource && s.resourceGroupType === ResourceGroupType.Index) as Resource[];
790

791
		if (!scmResources.length) {
J
Joao Moreno 已提交
792 793 794
			return;
		}

795
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
796
		await this.runByRepository(resources, async (repository, resources) => repository.revert(resources));
J
Joao Moreno 已提交
797 798
	}

J
Joao Moreno 已提交
799 800
	@command('git.unstageAll', { repository: true })
	async unstageAll(repository: Repository): Promise<void> {
801
		await repository.revert([]);
J
Joao Moreno 已提交
802
	}
J
Joao Moreno 已提交
803

J
Joao Moreno 已提交
804 805
	@command('git.unstageSelectedRanges', { diff: true })
	async unstageSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
806 807 808 809 810 811 812 813 814
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

815 816 817 818 819 820 821
		if (modifiedUri.scheme !== 'git') {
			return;
		}

		const { ref } = fromGitUri(modifiedUri);

		if (ref !== '') {
J
Joao Moreno 已提交
822 823 824
			return;
		}

J
Joao Moreno 已提交
825
		const originalUri = toGitUri(modifiedUri, 'HEAD');
J
Joao Moreno 已提交
826
		const originalDocument = await workspace.openTextDocument(originalUri);
827 828 829 830
		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 已提交
831 832 833 834 835

		if (!selectedDiffs.length) {
			return;
		}

836 837
		const invertedDiffs = selectedDiffs.map(invertLineChange);
		const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs);
J
Joao Moreno 已提交
838

J
Joao Moreno 已提交
839
		await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
J
Joao Moreno 已提交
840 841
	}

J
Joao Moreno 已提交
842 843
	@command('git.clean')
	async clean(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
844
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
845
			const resource = this.getSCMResource();
846 847 848 849 850 851 852 853

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

J
Joao Moreno 已提交
854
		const scmResources = resourceStates
J
Joao Moreno 已提交
855
			.filter(s => s instanceof Resource && s.resourceGroupType === ResourceGroupType.WorkingTree) as Resource[];
856

J
Joao Moreno 已提交
857
		if (!scmResources.length) {
J
Joao Moreno 已提交
858 859
			return;
		}
J
Joao Moreno 已提交
860

J
Joao Moreno 已提交
861
		const untrackedCount = scmResources.reduce((s, r) => s + (r.type === Status.UNTRACKED ? 1 : 0), 0);
J
Joao Moreno 已提交
862 863 864
		let message: string;
		let yes = localize('discard', "Discard Changes");

J
Joao Moreno 已提交
865
		if (scmResources.length === 1) {
J
Joao Moreno 已提交
866
			if (untrackedCount > 0) {
J
Joao Moreno 已提交
867
				message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(scmResources[0].resourceUri.fsPath));
J
Joao Moreno 已提交
868 869
				yes = localize('delete file', "Delete file");
			} else {
J
Joao Moreno 已提交
870
				message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(scmResources[0].resourceUri.fsPath));
J
Joao Moreno 已提交
871 872
			}
		} else {
J
Joao Moreno 已提交
873
			message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", scmResources.length);
J
Joao Moreno 已提交
874 875 876 877 878

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

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

J
Joao Moreno 已提交
882 883 884 885
		if (pick !== yes) {
			return;
		}

J
Joao Moreno 已提交
886
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
887
		await this.runByRepository(resources, async (repository, resources) => repository.clean(resources));
J
Joao Moreno 已提交
888
	}
J
Joao Moreno 已提交
889

J
Joao Moreno 已提交
890 891
	@command('git.cleanAll', { repository: true })
	async cleanAll(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
892
		let resources = repository.workingTreeGroup.resourceStates;
J
Joao Moreno 已提交
893 894 895 896 897

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

J
Joao Moreno 已提交
898 899
		const trackedResources = resources.filter(r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED);
		const untrackedResources = resources.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED);
J
Joao Moreno 已提交
900

J
Joao Moreno 已提交
901 902 903 904 905 906
		if (untrackedResources.length === 0) {
			const message = resources.length === 1
				? localize('confirm discard all single', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath))
				: localize('confirm discard all', "Are you sure you want to discard ALL changes in {0} files?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST.", resources.length);
			const yes = resources.length === 1
				? localize('discardAll multiple', "Discard 1 File")
J
Joao Moreno 已提交
907
				: localize('discardAll', "Discard All {0} Files", resources.length);
J
Joao Moreno 已提交
908
			const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
909

J
Joao Moreno 已提交
910
			if (pick !== yes) {
J
Joao Moreno 已提交
911 912 913
				return;
			}

J
Joao 已提交
914
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
915 916 917 918 919 920 921 922
			return;
		} else if (resources.length === 1) {
			const message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(resources[0].resourceUri.fsPath));
			const yes = localize('delete file', "Delete file");
			const pick = await window.showWarningMessage(message, { modal: true }, yes);

			if (pick !== yes) {
				return;
J
Joao Moreno 已提交
923 924
			}

J
Joao 已提交
925
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
926 927 928 929
		} else if (trackedResources.length === 0) {
			const message = localize('confirm delete multiple', "Are you sure you want to DELETE {0} files?", resources.length);
			const yes = localize('delete files', "Delete Files");
			const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
930

J
Joao Moreno 已提交
931 932 933
			if (pick !== yes) {
				return;
			}
J
Joao Moreno 已提交
934

J
Joao 已提交
935
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
936

J
Joao Moreno 已提交
937 938 939 940
		} else { // resources.length > 1 && untrackedResources.length > 0 && trackedResources.length > 0
			const untrackedMessage = untrackedResources.length === 1
				? localize('there are untracked files single', "The following untracked file will be DELETED FROM DISK if discarded: {0}.", path.basename(untrackedResources[0].resourceUri.fsPath))
				: localize('there are untracked files', "There are {0} untracked files which will be DELETED FROM DISK if discarded.", untrackedResources.length);
J
Joao Moreno 已提交
941

J
Joao Moreno 已提交
942 943 944 945 946
			const message = localize('confirm discard all 2', "{0}\n\nThis is IRREVERSIBLE, your current working set will be FOREVER LOST.", untrackedMessage, resources.length);

			const yesTracked = trackedResources.length === 1
				? localize('yes discard tracked', "Discard 1 Tracked File", trackedResources.length)
				: localize('yes discard tracked multiple', "Discard {0} Tracked Files", trackedResources.length);
J
Joao Moreno 已提交
947

J
Joao Moreno 已提交
948
			const yesAll = localize('discardAll', "Discard All {0} Files", resources.length);
J
Joao Moreno 已提交
949 950 951 952 953 954 955 956
			const pick = await window.showWarningMessage(message, { modal: true }, yesTracked, yesAll);

			if (pick === yesTracked) {
				resources = trackedResources;
			} else if (pick !== yesAll) {
				return;
			}

J
Joao 已提交
957
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
958
		}
J
Joao Moreno 已提交
959 960
	}

J
Joao Moreno 已提交
961
	private async smartCommit(
J
Joao Moreno 已提交
962
		repository: Repository,
963
		getCommitMessage: () => Promise<string | undefined>,
J
Joao Moreno 已提交
964 965
		opts?: CommitOptions
	): Promise<boolean> {
966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984
		const unsavedTextDocuments = workspace.textDocuments
			.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));

		if (unsavedTextDocuments.length > 0) {
			const message = unsavedTextDocuments.length === 1
				? localize('unsaved files single', "The following file is unsaved: {0}.\n\nWould you like to save it before comitting?", path.basename(unsavedTextDocuments[0].uri.fsPath))
				: localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before comitting?", unsavedTextDocuments.length);
			const saveAndCommit = localize('save and commit', "Save All & Commit");
			const commit = localize('commit', "Commit Anyway");
			const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit);

			if (pick === saveAndCommit) {
				await Promise.all(unsavedTextDocuments.map(d => d.save()));
				await repository.status();
			} else if (pick !== commit) {
				return false; // do not commit on cancel
			}
		}

985 986
		const config = workspace.getConfiguration('git');
		const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
987
		const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
J
Joao Moreno 已提交
988 989
		const noStagedChanges = repository.indexGroup.resourceStates.length === 0;
		const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
990 991

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

J
Joao Moreno 已提交
994 995
			// 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?");
996 997 998 999 1000
			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 已提交
1001 1002 1003
				config.update('enableSmartCommit', true, true);
			} else if (pick !== yes) {
				return false; // do not commit on cancel
1004 1005 1006
			}
		}

J
Joao Moreno 已提交
1007
		if (!opts) {
1008
			opts = { all: noStagedChanges };
J
Joao Moreno 已提交
1009 1010
		} else if (!opts.all && noStagedChanges) {
			opts = { ...opts, all: true };
J
Joao Moreno 已提交
1011 1012
		}

1013 1014 1015
		// enable signing of commits if configurated
		opts.signCommit = enableCommitSigning;

J
Joao Moreno 已提交
1016 1017
		if (
			// no changes
1018
			(noStagedChanges && noUnstagedChanges)
J
Joao Moreno 已提交
1019
			// or no staged changes and not `all`
1020
			|| (!opts.all && noStagedChanges)
J
Joao Moreno 已提交
1021
		) {
J
Joao Moreno 已提交
1022 1023 1024 1025
			window.showInformationMessage(localize('no changes', "There are no changes to commit."));
			return false;
		}

J
Joao Moreno 已提交
1026
		const message = await getCommitMessage();
J
Joao Moreno 已提交
1027 1028 1029 1030 1031

		if (!message) {
			return false;
		}

J
Joao Moreno 已提交
1032
		await repository.commit(message, opts);
J
Joao Moreno 已提交
1033 1034 1035 1036

		return true;
	}

J
Joao Moreno 已提交
1037
	private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> {
J
Joao Moreno 已提交
1038
		const message = repository.inputBox.value;
J
Joao Moreno 已提交
1039
		const getCommitMessage = async () => {
J
Joao Moreno 已提交
1040 1041 1042 1043
			if (message) {
				return message;
			}

1044 1045 1046 1047 1048 1049
			let value: string | undefined = undefined;

			if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) {
				value = (await repository.getCommit(repository.HEAD.commit)).message;
			}

1050 1051 1052 1053 1054 1055 1056
			const getPreviousCommitMessage = async () => {
				//Only return the previous commit message if it's an amend commit and the repo already has a commit
				if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) {
					return (await repository.getCommit('HEAD')).message;
				}
			};

J
Joao Moreno 已提交
1057
			return await window.showInputBox({
1058
				value,
J
Joao Moreno 已提交
1059
				placeHolder: localize('commit message', "Commit message"),
J
Joao Moreno 已提交
1060 1061
				prompt: localize('provide commit message', "Please provide a commit message"),
				ignoreFocusOut: true
J
Joao Moreno 已提交
1062
			});
J
Joao Moreno 已提交
1063 1064
		};

J
Joao Moreno 已提交
1065
		const didCommit = await this.smartCommit(repository, getCommitMessage, opts);
J
Joao Moreno 已提交
1066 1067

		if (message && didCommit) {
J
Joao Moreno 已提交
1068
			repository.inputBox.value = await repository.getCommitTemplate();
J
Joao Moreno 已提交
1069
		}
J
Joao Moreno 已提交
1070 1071
	}

J
Joao Moreno 已提交
1072
	@command('git.commit', { repository: true })
J
Joao Moreno 已提交
1073 1074
	async commit(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository);
J
Joao Moreno 已提交
1075 1076
	}

J
Joao Moreno 已提交
1077
	@command('git.commitWithInput', { repository: true })
J
Joao Moreno 已提交
1078
	async commitWithInput(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1079
		if (!repository.inputBox.value) {
J
Joao Moreno 已提交
1080 1081 1082
			return;
		}

J
Joao Moreno 已提交
1083
		const didCommit = await this.smartCommit(repository, async () => repository.inputBox.value);
J
Joao Moreno 已提交
1084 1085

		if (didCommit) {
J
Joao Moreno 已提交
1086
			repository.inputBox.value = await repository.getCommitTemplate();
J
Joao Moreno 已提交
1087
		}
J
Joao Moreno 已提交
1088 1089
	}

J
Joao Moreno 已提交
1090
	@command('git.commitStaged', { repository: true })
J
Joao Moreno 已提交
1091 1092
	async commitStaged(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: false });
J
Joao Moreno 已提交
1093 1094
	}

J
Joao Moreno 已提交
1095
	@command('git.commitStagedSigned', { repository: true })
J
Joao Moreno 已提交
1096 1097
	async commitStagedSigned(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: false, signoff: true });
J
Joao Moreno 已提交
1098 1099
	}

J
Joao Moreno 已提交
1100
	@command('git.commitStagedAmend', { repository: true })
J
Joao Moreno 已提交
1101
	async commitStagedAmend(repository: Repository): Promise<void> {
1102
		await this.commitWithAnyInput(repository, { all: false, amend: true });
K
Krzysztof Cieślak 已提交
1103 1104
	}

J
Joao Moreno 已提交
1105
	@command('git.commitAll', { repository: true })
J
Joao Moreno 已提交
1106 1107
	async commitAll(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true });
J
Joao Moreno 已提交
1108 1109
	}

J
Joao Moreno 已提交
1110
	@command('git.commitAllSigned', { repository: true })
J
Joao Moreno 已提交
1111 1112
	async commitAllSigned(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true, signoff: true });
J
Joao Moreno 已提交
1113 1114
	}

J
Joao Moreno 已提交
1115
	@command('git.commitAllAmend', { repository: true })
J
Joao Moreno 已提交
1116 1117
	async commitAllAmend(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true, amend: true });
K
Krzysztof Cieślak 已提交
1118 1119
	}

J
Joao Moreno 已提交
1120
	@command('git.undoCommit', { repository: true })
J
Joao Moreno 已提交
1121 1122
	async undoCommit(repository: Repository): Promise<void> {
		const HEAD = repository.HEAD;
J
Joao Moreno 已提交
1123 1124 1125 1126 1127

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

J
Joao Moreno 已提交
1128 1129
		const commit = await repository.getCommit('HEAD');
		await repository.reset('HEAD~');
J
Joao Moreno 已提交
1130
		repository.inputBox.value = commit.message;
J
Joao Moreno 已提交
1131 1132
	}

J
Joao Moreno 已提交
1133
	@command('git.checkout', { repository: true })
J
Joao Moreno 已提交
1134
	async checkout(repository: Repository, treeish: string): Promise<void> {
J
Joao Moreno 已提交
1135
		if (typeof treeish === 'string') {
J
Joao Moreno 已提交
1136
			return await repository.checkout(treeish);
J
Joao Moreno 已提交
1137 1138
		}

J
Joao Moreno 已提交
1139
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
1140
		const checkoutType = config.get<string>('checkoutType') || 'all';
J
Joao Moreno 已提交
1141 1142 1143
		const includeTags = checkoutType === 'all' || checkoutType === 'tags';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
1144
		const createBranch = new CreateBranchItem(this);
J
Joao Moreno 已提交
1145

J
Joao Moreno 已提交
1146
		const heads = repository.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
1147 1148
			.map(ref => new CheckoutItem(ref));

J
Joao Moreno 已提交
1149
		const tags = (includeTags ? repository.refs.filter(ref => ref.type === RefType.Tag) : [])
J
Joao Moreno 已提交
1150 1151
			.map(ref => new CheckoutTagItem(ref));

J
Joao Moreno 已提交
1152
		const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
1153 1154
			.map(ref => new CheckoutRemoteHeadItem(ref));

J
Joao Moreno 已提交
1155
		const picks = [createBranch, ...heads, ...tags, ...remoteHeads];
1156
		const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout');
J
Joao Moreno 已提交
1157
		const choice = await window.showQuickPick(picks, { placeHolder });
J
Joao Moreno 已提交
1158 1159 1160 1161 1162

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
1163
		await choice.run(repository);
J
Joao Moreno 已提交
1164 1165
	}

J
Joao Moreno 已提交
1166
	@command('git.branch', { repository: true })
J
Joao Moreno 已提交
1167
	async branch(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1168
		const result = await window.showInputBox({
J
Joao Moreno 已提交
1169
			placeHolder: localize('branch name', "Branch name"),
J
Joao Moreno 已提交
1170 1171
			prompt: localize('provide branch name', "Please provide a branch name"),
			ignoreFocusOut: true
J
Joao Moreno 已提交
1172
		});
J
Joao Moreno 已提交
1173

J
Joao Moreno 已提交
1174 1175 1176
		if (!result) {
			return;
		}
J
Joao Moreno 已提交
1177

J
Joao Moreno 已提交
1178
		const name = result.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
J
Joao Moreno 已提交
1179
		await repository.branch(name);
J
Joao Moreno 已提交
1180 1181
	}

J
Joao Moreno 已提交
1182
	@command('git.deleteBranch', { repository: true })
J
Joao Moreno 已提交
1183
	async deleteBranch(repository: Repository, name: string, force?: boolean): Promise<void> {
1184 1185
		let run: (force?: boolean) => Promise<void>;
		if (typeof name === 'string') {
J
Joao Moreno 已提交
1186
			run = force => repository.deleteBranch(name, force);
1187
		} else {
J
Joao Moreno 已提交
1188 1189
			const currentHead = repository.HEAD && repository.HEAD.name;
			const heads = repository.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead)
1190
				.map(ref => new BranchDeleteItem(ref));
M
Maik Riechert 已提交
1191

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

M
Maik Riechert 已提交
1195
			if (!choice || !choice.branchName) {
1196 1197
				return;
			}
M
Maik Riechert 已提交
1198
			name = choice.branchName;
J
Joao Moreno 已提交
1199
			run = force => choice.run(repository, force);
M
Maik Riechert 已提交
1200 1201
		}

1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216
		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 已提交
1217 1218
	}

1219 1220
	@command('git.renameBranch', { repository: true })
	async renameBranch(repository: Repository): Promise<void> {
J
Justin Horner 已提交
1221 1222 1223 1224 1225 1226 1227 1228
		const placeHolder = localize('provide branch name', "Please provide a branch name");
		const name = await window.showInputBox({ placeHolder });

		if (!name || name.trim().length === 0) {
			return;
		}

		try {
1229
			await repository.renameBranch(name);
J
Justin Horner 已提交
1230 1231 1232
		} catch (err) {
			switch (err.gitErrorCode) {
				case GitErrorCodes.InvalidBranchName:
1233 1234
					window.showErrorMessage(localize('invalid branch name', 'Invalid branch name'));
					return;
J
Justin Horner 已提交
1235
				case GitErrorCodes.BranchAlreadyExists:
J
Joao Moreno 已提交
1236
					window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", name));
1237 1238 1239
					return;
				default:
					throw err;
J
Justin Horner 已提交
1240 1241 1242 1243
			}
		}
	}

J
Joao Moreno 已提交
1244
	@command('git.merge', { repository: true })
J
Joao Moreno 已提交
1245
	async merge(repository: Repository): Promise<void> {
1246 1247 1248 1249
		const config = workspace.getConfiguration('git');
		const checkoutType = config.get<string>('checkoutType') || 'all';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
1250
		const heads = repository.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
1251 1252
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
1253

J
Joao Moreno 已提交
1254
		const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
1255 1256
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
1257 1258

		const picks = [...heads, ...remoteHeads];
1259 1260
		const placeHolder = localize('select a branch to merge from', 'Select a branch to merge from');
		const choice = await window.showQuickPick<MergeItem>(picks, { placeHolder });
1261 1262 1263 1264 1265

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
1266
		try {
J
Joao Moreno 已提交
1267
			await choice.run(repository);
J
Joao Moreno 已提交
1268 1269 1270 1271 1272 1273 1274 1275
		} 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);
		}
1276 1277
	}

J
Joao Moreno 已提交
1278
	@command('git.createTag', { repository: true })
J
Joao Moreno 已提交
1279
	async createTag(repository: Repository): Promise<void> {
1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291
		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 已提交
1292
			prompt: localize('provide tag message', "Please provide a message to annotate the tag"),
1293 1294 1295 1296 1297
			ignoreFocusOut: true
		});

		const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
		const message = inputMessage || name;
J
Joao Moreno 已提交
1298
		await repository.tag(name, message);
1299 1300
	}

J
Joao Moreno 已提交
1301 1302 1303 1304 1305 1306 1307 1308 1309 1310
	@command('git.fetch', { repository: true })
	async fetch(repository: Repository): Promise<void> {
		if (repository.remotes.length === 0) {
			window.showWarningMessage(localize('no remotes to fetch', "This repository has no remotes configured to fetch from."));
			return;
		}

		await repository.fetch();
	}

J
Joao Moreno 已提交
1311
	@command('git.pullFrom', { repository: true })
J
Joao Moreno 已提交
1312 1313
	async pullFrom(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
1314 1315 1316 1317 1318 1319

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

D
Dozed12 已提交
1320
		const remotePicks = remotes.map(r => ({ label: r.name, description: r.url }));
1321
		const placeHolder = localize('pick remote pull repo', "Pick a remote to pull the branch from");
D
Dozed12 已提交
1322
		const remotePick = await window.showQuickPick(remotePicks, { placeHolder });
1323

D
Dozed12 已提交
1324
		if (!remotePick) {
1325 1326 1327
			return;
		}

D
Dozed12 已提交
1328 1329
		const remoteRefs = repository.refs;
		const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remotePick.label));
J
Joao Moreno 已提交
1330
		const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name })) as { label: string; description: string }[];
D
Dozed12 已提交
1331
		const branchPick = await window.showQuickPick(branchPicks, { placeHolder });
1332

D
Dozed12 已提交
1333
		if (!branchPick) {
1334 1335 1336
			return;
		}

F
Francisco Moreira 已提交
1337 1338
		const remoteCharCnt = remotePick.label.length;

J
Joao Moreno 已提交
1339
		repository.pull(false, remotePick.label, branchPick.label.slice(remoteCharCnt + 1));
1340 1341
	}

J
Joao Moreno 已提交
1342
	@command('git.pull', { repository: true })
J
Joao Moreno 已提交
1343 1344
	async pull(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1345 1346 1347 1348 1349 1350

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

J
Joao Moreno 已提交
1351
		await repository.pull();
J
Joao Moreno 已提交
1352 1353
	}

J
Joao Moreno 已提交
1354
	@command('git.pullRebase', { repository: true })
J
Joao Moreno 已提交
1355 1356
	async pullRebase(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1357 1358 1359 1360 1361 1362

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

J
Joao Moreno 已提交
1363
		await repository.pullWithRebase();
J
Joao Moreno 已提交
1364 1365
	}

J
Joao Moreno 已提交
1366
	@command('git.push', { repository: true })
J
Joao Moreno 已提交
1367 1368
	async push(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1369 1370 1371 1372 1373 1374

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

1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395
		if (!repository.HEAD || !repository.HEAD.name) {
			window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
			return;
		}

		try {
			await repository.push();
		} catch (err) {
			if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
				throw err;
			}

			const branchName = repository.HEAD.name;
			const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName);
			const yes = localize('ok', "OK");
			const pick = await window.showWarningMessage(message, { modal: true }, yes);

			if (pick === yes) {
				await this.publish(repository);
			}
		}
J
Joao Moreno 已提交
1396 1397
	}

J
Joao Moreno 已提交
1398
	@command('git.pushWithTags', { repository: true })
J
Joao Moreno 已提交
1399 1400
	async pushWithTags(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
1401 1402 1403 1404 1405 1406

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

J
Joao Moreno 已提交
1407
		await repository.pushTags();
1408 1409 1410 1411

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

J
Joao Moreno 已提交
1412
	@command('git.pushTo', { repository: true })
J
Joao Moreno 已提交
1413 1414
	async pushTo(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1415 1416 1417 1418 1419 1420

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

J
Joao Moreno 已提交
1421
		if (!repository.HEAD || !repository.HEAD.name) {
J
Joao Moreno 已提交
1422 1423 1424 1425
			window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
			return;
		}

J
Joao Moreno 已提交
1426
		const branchName = repository.HEAD.name;
J
Joao Moreno 已提交
1427 1428 1429 1430 1431 1432 1433 1434
		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 已提交
1435
		repository.pushTo(pick.label, branchName);
J
Joao Moreno 已提交
1436 1437
	}

J
Joao Moreno 已提交
1438
	private async _sync(repository: Repository, rebase: boolean): Promise<void> {
J
Joao Moreno 已提交
1439
		const HEAD = repository.HEAD;
J
Joao Moreno 已提交
1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450

		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");
B
Benjamin Pasero 已提交
1451
			const neverAgain = localize('never again', "OK, Don't Show Again");
J
Joao Moreno 已提交
1452 1453 1454 1455 1456 1457 1458 1459 1460
			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 已提交
1461 1462 1463 1464 1465 1466 1467 1468 1469 1470
		if (rebase) {
			await repository.syncRebase();
		} else {
			await repository.sync();
		}
	}

	@command('git.sync', { repository: true })
	sync(repository: Repository): Promise<void> {
		return this._sync(repository, false);
J
Joao Moreno 已提交
1471 1472
	}

J
Joao 已提交
1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485
	@command('git._syncAll')
	async syncAll(): Promise<void> {
		await Promise.all(this.model.repositories.map(async repository => {
			const HEAD = repository.HEAD;

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

			await repository.sync();
		}));
	}

1486
	@command('git.syncRebase', { repository: true })
J
Joao Moreno 已提交
1487 1488
	syncRebase(repository: Repository): Promise<void> {
		return this._sync(repository, true);
J
Joao Moreno 已提交
1489 1490
	}

J
Joao Moreno 已提交
1491
	@command('git.publish', { repository: true })
J
Joao Moreno 已提交
1492 1493
	async publish(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1494 1495 1496 1497 1498 1499

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

J
Joao Moreno 已提交
1500
		const branchName = repository.HEAD && repository.HEAD.name || '';
M
Markus Wolf 已提交
1501 1502 1503 1504 1505 1506
		const selectRemote = async () => {
			const picks = repository.remotes.map(r => r.name);
			const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
			return await window.showQuickPick(picks, { placeHolder });
		};
		const choice = remotes.length === 1 ? remotes[0].name : await selectRemote();
J
Joao Moreno 已提交
1507 1508 1509 1510 1511

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
1512
		await repository.pushTo(choice, branchName, true);
J
Joao Moreno 已提交
1513 1514
	}

1515 1516
	@command('git.ignore')
	async ignore(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
1517
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
1518
			const resource = this.getSCMResource();
N
NKumar2 已提交
1519

1520
			if (!resource) {
J
Joao Moreno 已提交
1521 1522 1523
				return;
			}

1524
			resourceStates = [resource];
J
Joao Moreno 已提交
1525 1526
		}

1527
		const resources = resourceStates
J
Joao Moreno 已提交
1528 1529 1530
			.filter(s => s instanceof Resource)
			.map(r => r.resourceUri);

1531
		if (!resources.length) {
N
NKumar2 已提交
1532 1533 1534
			return;
		}

1535
		await this.runByRepository(resources, async (repository, resources) => repository.ignore(resources));
N
NKumar2 已提交
1536 1537
	}

J
Joao Moreno 已提交
1538
	private async _stash(repository: Repository, includeUntracked = false): Promise<void> {
1539 1540
		const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
		const noStagedChanges = repository.indexGroup.resourceStates.length === 0;
1541

1542
		if (noUnstagedChanges && noStagedChanges) {
K
Krzysztof Cieślak 已提交
1543 1544 1545
			window.showInformationMessage(localize('no changes stash', "There are no changes to stash."));
			return;
		}
J
Joao Moreno 已提交
1546

1547
		const message = await this.getStashMessage();
J
Joao Moreno 已提交
1548 1549 1550 1551 1552

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

J
Joao Moreno 已提交
1553
		await repository.createStash(message, includeUntracked);
K
Krzysztof Cieślak 已提交
1554 1555
	}

1556 1557 1558 1559 1560 1561 1562
	private async getStashMessage(): Promise<string | undefined> {
		return await window.showInputBox({
			prompt: localize('provide stash message', "Optionally provide a stash message"),
			placeHolder: localize('stash message', "Stash message")
		});
	}

J
Joao Moreno 已提交
1563 1564 1565 1566 1567 1568 1569 1570 1571 1572
	@command('git.stash', { repository: true })
	stash(repository: Repository): Promise<void> {
		return this._stash(repository);
	}

	@command('git.stashIncludeUntracked', { repository: true })
	stashIncludeUntracked(repository: Repository): Promise<void> {
		return this._stash(repository, true);
	}

J
Joao Moreno 已提交
1573
	@command('git.stashPop', { repository: true })
J
Joao Moreno 已提交
1574 1575
	async stashPop(repository: Repository): Promise<void> {
		const stashes = await repository.getStashes();
J
Joao Moreno 已提交
1576 1577

		if (stashes.length === 0) {
K
Krzysztof Cieślak 已提交
1578 1579 1580 1581
			window.showInformationMessage(localize('no stashes', "There are no stashes to restore."));
			return;
		}

J
Joao Moreno 已提交
1582 1583
		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 已提交
1584 1585 1586 1587 1588
		const choice = await window.showQuickPick(picks, { placeHolder });

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

J
Joao Moreno 已提交
1590
		await repository.popStash(choice.id);
K
Krzysztof Cieślak 已提交
1591 1592
	}

J
Joao Moreno 已提交
1593
	@command('git.stashPopLatest', { repository: true })
J
Joao Moreno 已提交
1594 1595
	async stashPopLatest(repository: Repository): Promise<void> {
		const stashes = await repository.getStashes();
J
Joao Moreno 已提交
1596 1597

		if (stashes.length === 0) {
K
Krzysztof Cieślak 已提交
1598 1599 1600 1601
			window.showInformationMessage(localize('no stashes', "There are no stashes to restore."));
			return;
		}

J
Joao Moreno 已提交
1602
		await repository.popStash();
J
Joao Moreno 已提交
1603
	}
K
Krzysztof Cieślak 已提交
1604

J
Joao Moreno 已提交
1605
	private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any {
1606
		const result = (...args: any[]) => {
J
Joao Moreno 已提交
1607 1608
			let result: Promise<any>;

J
Joao Moreno 已提交
1609
			if (!options.repository) {
J
Joao Moreno 已提交
1610 1611
				result = Promise.resolve(method.apply(this, args));
			} else {
J
Joao Moreno 已提交
1612 1613
				// try to guess the repository based on the first argument
				const repository = this.model.getRepository(args[0]);
J
Joao Moreno 已提交
1614 1615 1616 1617 1618 1619 1620 1621 1622
				let repositoryPromise: Promise<Repository | undefined>;

				if (repository) {
					repositoryPromise = Promise.resolve(repository);
				} else if (this.model.repositories.length === 1) {
					repositoryPromise = Promise.resolve(this.model.repositories[0]);
				} else {
					repositoryPromise = this.model.pickRepository();
				}
1623

J
Joao Moreno 已提交
1624
				result = repositoryPromise.then(repository => {
J
Joao Moreno 已提交
1625
					if (!repository) {
J
Joao 已提交
1626
						return Promise.resolve();
J
Joao Moreno 已提交
1627 1628
					}

J
Joao Moreno 已提交
1629
					return Promise.resolve(method.apply(this, [repository, ...args]));
J
Joao Moreno 已提交
1630
				});
J
Joao Moreno 已提交
1631 1632
			}

K
kieferrm 已提交
1633
			/* __GDPR__
K
kieferrm 已提交
1634 1635 1636 1637
				"git.command" : {
					"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
J
Joao Moreno 已提交
1638 1639
			this.telemetryReporter.sendTelemetryEvent('git.command', { command: id });

J
Joao Moreno 已提交
1640 1641 1642 1643
			return result.catch(async err => {
				let message: string;

				switch (err.gitErrorCode) {
1644
					case GitErrorCodes.DirtyWorkTree:
J
Joao Moreno 已提交
1645 1646
						message = localize('clean repo', "Please clean your repository working tree before checkout.");
						break;
1647
					case GitErrorCodes.PushRejected:
J
Joao Moreno 已提交
1648
						message = localize('cant push', "Can't push refs to remote. Try running 'Pull' first to integrate your changes.");
1649
						break;
J
Joao Moreno 已提交
1650
					default:
1651 1652 1653
						const hint = (err.stderr || err.message || String(err))
							.replace(/^error: /mi, '')
							.replace(/^> husky.*$/mi, '')
J
Joao Moreno 已提交
1654
							.split(/[\r\n]/)
J
João Moreno 已提交
1655
							.filter((line: string) => !!line)
1656 1657 1658 1659 1660
						[0];

						message = hint
							? localize('git error details', "Git: {0}", hint)
							: localize('git error', "Git error");
J
Joao Moreno 已提交
1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678

						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();
				}
			});
		};
1679 1680

		// patch this object, so people can call methods directly
1681
		(this as any)[key] = result;
1682 1683

		return result;
J
Joao Moreno 已提交
1684 1685
	}

1686
	private getSCMResource(uri?: Uri): Resource | undefined {
1687
		uri = uri ? uri : window.activeTextEditor && window.activeTextEditor.document.uri;
J
Joao Moreno 已提交
1688 1689

		if (!uri) {
1690
			return undefined;
J
Joao Moreno 已提交
1691 1692 1693
		}

		if (uri.scheme === 'git') {
J
Joao Moreno 已提交
1694 1695
			const { path } = fromGitUri(uri);
			uri = Uri.file(path);
J
Joao Moreno 已提交
1696 1697 1698 1699
		}

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

J
Joao Moreno 已提交
1702
			if (!repository) {
1703 1704
				return undefined;
			}
J
Joao Moreno 已提交
1705

J
Joao Moreno 已提交
1706 1707
			return repository.workingTreeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]
				|| repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
J
Joao Moreno 已提交
1708 1709 1710
		}
	}

J
Joao Moreno 已提交
1711
	private runByRepository<T>(resource: Uri, fn: (repository: Repository, resource: Uri) => Promise<T>): Promise<T[]>;
J
Joao Moreno 已提交
1712 1713 1714 1715 1716 1717
	private runByRepository<T>(resources: Uri[], fn: (repository: Repository, resources: Uri[]) => Promise<T>): Promise<T[]>;
	private async runByRepository<T>(arg: Uri | Uri[], fn: (repository: Repository, resources: any) => Promise<T>): Promise<T[]> {
		const resources = arg instanceof Uri ? [arg] : arg;
		const isSingleResource = arg instanceof Uri;

		const groups = resources.reduce((result, resource) => {
1718
			let repository = this.model.getRepository(resource);
J
Joao Moreno 已提交
1719 1720 1721 1722 1723 1724

			if (!repository) {
				console.warn('Could not find git repository for ', resource);
				return result;
			}

1725 1726 1727 1728 1729
			// Could it be a submodule?
			if (resource.fsPath === repository.root) {
				repository = this.model.getRepositoryForSubmodule(resource) || repository;
			}

J
Joao Moreno 已提交
1730
			const tuple = result.filter(p => p.repository === repository)[0];
J
Joao Moreno 已提交
1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746

			if (tuple) {
				tuple.resources.push(resource);
			} else {
				result.push({ repository, resources: [resource] });
			}

			return result;
		}, [] as { repository: Repository, resources: Uri[] }[]);

		const promises = groups
			.map(({ repository, resources }) => fn(repository as Repository, isSingleResource ? resources[0] : resources));

		return Promise.all(promises);
	}

J
Joao Moreno 已提交
1747 1748 1749
	dispose(): void {
		this.disposables.forEach(d => d.dispose());
	}
J
João Moreno 已提交
1750
}