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

J
Joao Moreno 已提交
6
import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, MessageOptions, WorkspaceFolder } from 'vscode';
J
Joao Moreno 已提交
7
import { Git, CommitOptions, Stash, ForcePushMode } from './git';
I
Ilya Biryukov 已提交
8
import { Repository, Resource, ResourceGroupType } from './repository';
J
Joao Moreno 已提交
9
import { Model } from './model';
J
Joao Moreno 已提交
10
import { toGitUri, fromGitUri } from './uri';
11
import { grep, isDescendant, pathEquals } from './util';
J
Joao Moreno 已提交
12
import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange, getModifiedRange } from './staging';
J
Joao Moreno 已提交
13
import * as path from 'path';
14
import { lstat, Stats } from 'fs';
J
Joao Moreno 已提交
15
import * as os from 'os';
J
Joao Moreno 已提交
16
import TelemetryReporter from 'vscode-extension-telemetry';
J
Joao Moreno 已提交
17
import * as nls from 'vscode-nls';
I
Ilya Biryukov 已提交
18
import { Ref, RefType, Branch, GitErrorCodes, Status } from './api/git';
J
Joao Moreno 已提交
19 20

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

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

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

J
Joao Moreno 已提交
28
	constructor(protected ref: Ref) { }
J
Joao Moreno 已提交
29

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

		if (!ref) {
			return;
		}

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

class CheckoutTagItem extends CheckoutItem {

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

class CheckoutRemoteHeadItem extends CheckoutItem {

J
Joao Moreno 已提交
50 51 52
	get description(): string {
		return localize('remote branch at', "Remote branch at {0}", this.shortCommit);
	}
J
Joao Moreno 已提交
53

54
	async run(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
55 56 57 58
		if (!this.ref.name) {
			return;
		}

59
		await repository.checkoutTracking(this.ref.name);
J
Joao Moreno 已提交
60 61 62
	}
}

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

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

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

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

80 81 82 83 84 85
class MergeItem implements QuickPickItem {

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

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

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

J
Joao Moreno 已提交
92 93
class CreateBranchItem implements QuickPickItem {

J
Joao Moreno 已提交
94 95
	constructor(private cc: CommandCenter) { }

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

C
Christof Marti 已提交
99
	get alwaysShow(): boolean { return true; }
100

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

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

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

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

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

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

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

J
Joao Moreno 已提交
139
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[], deletionConflicts: Resource[] }> {
140 141 142
	const selection = resources.filter(s => s instanceof Resource) as Resource[];
	const merge = selection.filter(s => s.resourceGroupType === ResourceGroupType.Merge);
	const isBothAddedOrModified = (s: Resource) => s.type === Status.BOTH_MODIFIED || s.type === Status.BOTH_ADDED;
J
Joao Moreno 已提交
143
	const isAnyDeleted = (s: Resource) => s.type === Status.DELETED_BY_THEM || s.type === Status.DELETED_BY_US;
144 145 146
	const possibleUnresolved = merge.filter(isBothAddedOrModified);
	const promises = possibleUnresolved.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/));
	const unresolvedBothModified = await Promise.all<boolean>(promises);
147
	const resolved = possibleUnresolved.filter((_s, i) => !unresolvedBothModified[i]);
J
Joao Moreno 已提交
148
	const deletionConflicts = merge.filter(s => isAnyDeleted(s));
149
	const unresolved = [
J
Joao Moreno 已提交
150
		...merge.filter(s => !isBothAddedOrModified(s) && !isAnyDeleted(s)),
151
		...possibleUnresolved.filter((_s, i) => unresolvedBothModified[i])
152 153
	];

J
Joao Moreno 已提交
154
	return { merge, resolved, unresolved, deletionConflicts };
155 156
}

157 158 159 160 161 162 163 164 165
enum PushType {
	Push,
	PushTo,
	PushTags,
}

interface PushOptions {
	pushType: PushType;
	forcePush?: boolean;
J
Joao Moreno 已提交
166
	silent?: boolean;
167 168
}

J
Joao Moreno 已提交
169
export class CommandCenter {
J
Joao Moreno 已提交
170 171

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

J
Joao Moreno 已提交
173
	constructor(
J
Joao Moreno 已提交
174
		private git: Git,
J
Joao Moreno 已提交
175
		private model: Model,
J
Joao Moreno 已提交
176 177
		private outputChannel: OutputChannel,
		private telemetryReporter: TelemetryReporter
J
Joao Moreno 已提交
178
	) {
J
Joao Moreno 已提交
179 180
		this.disposables = Commands.map(({ commandId, key, method, options }) => {
			const command = this.createCommand(commandId, key, method, options);
181

J
Joao Moreno 已提交
182
			if (options.diff) {
J
Joao Moreno 已提交
183 184 185 186 187
				return commands.registerDiffInformationCommand(commandId, command);
			} else {
				return commands.registerCommand(commandId, command);
			}
		});
J
Joao Moreno 已提交
188 189
	}

J
Joao Moreno 已提交
190 191
	@command('git.refresh', { repository: true })
	async refresh(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
192
		await repository.status();
J
Joao Moreno 已提交
193
	}
J
Joao Moreno 已提交
194

J
Joao Moreno 已提交
195 196
	@command('git.openResource')
	async openResource(resource: Resource): Promise<void> {
J
Joao Moreno 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210
		const repository = this.model.getRepository(resource.resourceUri);

		if (!repository) {
			return;
		}

		const config = workspace.getConfiguration('git', Uri.file(repository.root));
		const openDiffOnClick = config.get<boolean>('openDiffOnClick');

		if (openDiffOnClick) {
			await this._openResource(resource, undefined, true, false);
		} else {
			await this.openFile(resource);
		}
J
Joao Moreno 已提交
211 212
	}

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

216 217 218 219
		try {
			stat = await new Promise<Stats>((c, e) => lstat(resource.resourceUri.fsPath, (err, stat) => err ? e(err) : c(stat)));
		} catch (err) {
			// noop
220
		}
221

222 223 224 225
		let left: Uri | undefined;
		let right: Uri | undefined;

		if (stat && stat.isDirectory()) {
226 227 228 229
			const repository = this.model.getRepositoryForSubmodule(resource.resourceUri);

			if (repository) {
				right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root });
230 231
			}
		} else {
J
Joao Moreno 已提交
232 233 234 235
			if (resource.type !== Status.DELETED_BY_THEM) {
				left = await this.getLeftResource(resource);
			}

236 237
			right = await this.getRightResource(resource);
		}
238

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

J
Joao Moreno 已提交
241 242 243 244 245
		if (!right) {
			// TODO
			console.error('oh no');
			return;
		}
J
Joao Moreno 已提交
246

J
Joao Moreno 已提交
247
		const opts: TextDocumentShowOptions = {
J
Joao Moreno 已提交
248 249
			preserveFocus,
			preview,
250
			viewColumn: ViewColumn.Active
J
Joao Moreno 已提交
251 252
		};

J
Joao Moreno 已提交
253 254
		const activeTextEditor = window.activeTextEditor;

255 256 257
		// 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 已提交
258 259 260
			opts.selection = activeTextEditor.selection;
		}

261
		if (!left) {
J
Joao Moreno 已提交
262
			await commands.executeCommand<void>('vscode.open', right, opts, title);
J
Joao Moreno 已提交
263 264 265 266
		} else {
			await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
		}
	}
J
Joao Moreno 已提交
267

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

J
Joao Moreno 已提交
271 272 273
		if (!repository) {
			return toGitUri(uri, ref);
		}
J
Joao Moreno 已提交
274

J
Joao Moreno 已提交
275
		try {
J
Joao Moreno 已提交
276 277 278
			let gitRef = ref;

			if (gitRef === '~') {
J
Joao Moreno 已提交
279
				const uriString = uri.toString();
J
Joao Moreno 已提交
280
				const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
J
Joao Moreno 已提交
281
				gitRef = indexStatus ? '' : 'HEAD';
J
Joao Moreno 已提交
282 283
			}

J
Joao Moreno 已提交
284
			const { size, object } = await repository.getObjectDetails(gitRef, uri.fsPath);
J
Joao Moreno 已提交
285
			const { mimetype } = await repository.detectObjectType(object);
J
Joao Moreno 已提交
286

J
Joao Moreno 已提交
287 288 289
			if (mimetype === 'text/plain') {
				return toGitUri(uri, ref);
			}
290

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

J
Joao Moreno 已提交
295
			if (ImageMimetypes.indexOf(mimetype) > -1) {
J
Joao Moreno 已提交
296 297
				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 已提交
298 299
			}

J
Joao Moreno 已提交
300
			return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`);
J
Joao Moreno 已提交
301 302 303 304

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

J
Joao Moreno 已提交
307
	private async getLeftResource(resource: Resource): Promise<Uri | undefined> {
J
Joao Moreno 已提交
308 309 310
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
311
				return this.getURI(resource.original, 'HEAD');
J
Joao Moreno 已提交
312 313

			case Status.MODIFIED:
J
Joao Moreno 已提交
314
				return this.getURI(resource.resourceUri, '~');
315 316

			case Status.DELETED_BY_THEM:
J
Joao Moreno 已提交
317
				return this.getURI(resource.resourceUri, '');
J
Joao Moreno 已提交
318
		}
319
		return undefined;
J
Joao Moreno 已提交
320
	}
J
Joao Moreno 已提交
321

J
Joao Moreno 已提交
322
	private async getRightResource(resource: Resource): Promise<Uri | undefined> {
J
Joao Moreno 已提交
323 324 325 326 327
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_ADDED:
			case Status.INDEX_COPIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
328
				return this.getURI(resource.resourceUri, '');
J
Joao Moreno 已提交
329 330 331

			case Status.INDEX_DELETED:
			case Status.DELETED:
J
Joao Moreno 已提交
332
				return this.getURI(resource.resourceUri, 'HEAD');
J
Joao Moreno 已提交
333

J
Joao Moreno 已提交
334 335 336 337 338 339
			case Status.DELETED_BY_US:
				return this.getURI(resource.resourceUri, '~3');

			case Status.DELETED_BY_THEM:
				return this.getURI(resource.resourceUri, '~2');

J
Joao Moreno 已提交
340 341 342
			case Status.MODIFIED:
			case Status.UNTRACKED:
			case Status.IGNORED:
J
Joao Moreno 已提交
343 344 345 346 347 348
				const repository = this.model.getRepository(resource.resourceUri);

				if (!repository) {
					return;
				}

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

J
Joao Moreno 已提交
352 353
				if (indexStatus && indexStatus.renameResourceUri) {
					return indexStatus.renameResourceUri;
J
Joao Moreno 已提交
354 355
				}

J
Joao Moreno 已提交
356
				return resource.resourceUri;
J
Joao Moreno 已提交
357

358
			case Status.BOTH_ADDED:
J
Joao Moreno 已提交
359
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
360
				return resource.resourceUri;
J
Joao Moreno 已提交
361
		}
362
		return undefined;
J
Joao Moreno 已提交
363 364 365
	}

	private getTitle(resource: Resource): string {
J
Joao Moreno 已提交
366
		const basename = path.basename(resource.resourceUri.fsPath);
J
Joao Moreno 已提交
367 368 369 370 371 372 373

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

			case Status.MODIFIED:
M
Marc Kassay 已提交
374 375
			case Status.BOTH_ADDED:
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
376
				return `${basename} (Working Tree)`;
J
Joao Moreno 已提交
377 378 379 380 381 382

			case Status.DELETED_BY_US:
				return `${basename} (Theirs)`;

			case Status.DELETED_BY_THEM:
				return `${basename} (Ours)`;
J
Joao Moreno 已提交
383 384 385 386 387
		}

		return '';
	}

J
Joao Moreno 已提交
388
	@command('git.clone')
389 390 391 392 393 394 395
	async clone(url?: string): Promise<void> {
		if (!url) {
			url = await window.showInputBox({
				prompt: localize('repourl', "Repository URL"),
				ignoreFocusOut: true
			});
		}
J
Joao Moreno 已提交
396 397

		if (!url) {
K
kieferrm 已提交
398
			/* __GDPR__
K
kieferrm 已提交
399 400 401 402
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
403 404
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' });
			return;
J
Joao Moreno 已提交
405 406
		}

407
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
408 409 410 411 412 413 414 415 416
		let defaultCloneDirectory = config.get<string>('defaultCloneDirectory') || os.homedir();
		defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir());

		const uris = await window.showOpenDialog({
			canSelectFiles: false,
			canSelectFolders: true,
			canSelectMany: false,
			defaultUri: Uri.file(defaultCloneDirectory),
			openLabel: localize('selectFolder', "Select Repository Location")
J
Joao Moreno 已提交
417 418
		});

J
Joao Moreno 已提交
419
		if (!uris || uris.length === 0) {
K
kieferrm 已提交
420
			/* __GDPR__
K
kieferrm 已提交
421 422 423 424
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
425 426
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' });
			return;
J
Joao Moreno 已提交
427 428
		}

J
Joao Moreno 已提交
429 430 431
		const uri = uris[0];
		const parentPath = uri.fsPath;

432
		try {
433 434 435 436 437
			const opts = {
				location: ProgressLocation.Notification,
				title: localize('cloning', "Cloning git repository '{0}'...", url),
				cancellable: true
			};
M
Maryam Archie 已提交
438

439 440
			const repositoryPath = await window.withProgress(
				opts,
J
Joao Moreno 已提交
441
				(_, token) => this.git.clone(url!, parentPath, token)
442
			);
443

444 445
			const choices = [];
			let message = localize('proposeopen', "Would you like to open the cloned repository?");
446
			const open = localize('openrepo', "Open Repository");
447 448 449 450 451 452 453 454 455
			choices.push(open);

			const addToWorkspace = localize('add', "Add to Workspace");
			if (workspace.workspaceFolders) {
				message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?");
				choices.push(addToWorkspace);
			}

			const result = await window.showInformationMessage(message, ...choices);
456 457

			const openFolder = result === open;
K
kieferrm 已提交
458
			/* __GDPR__
K
kieferrm 已提交
459 460 461 462 463
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
					"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
				}
			*/
464
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 });
465 466 467

			const uri = Uri.file(repositoryPath);

468
			if (openFolder) {
469 470 471
				commands.executeCommand('vscode.openFolder', uri);
			} else if (result === addToWorkspace) {
				workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
472
			}
473 474
		} catch (err) {
			if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
K
kieferrm 已提交
475
				/* __GDPR__
K
kieferrm 已提交
476 477 478 479
					"clone" : {
						"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
480
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
J
Joao Moreno 已提交
481 482
			} else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) {
				return;
483
			} else {
K
kieferrm 已提交
484
				/* __GDPR__
K
kieferrm 已提交
485 486 487 488
					"clone" : {
						"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
489
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
490
			}
J
Joao Moreno 已提交
491

492
			throw err;
J
Joao Moreno 已提交
493 494 495
		}
	}

J
Joao Moreno 已提交
496
	@command('git.init')
J
Joao Moreno 已提交
497
	async init(): Promise<void> {
J
Joao Moreno 已提交
498
		let repositoryPath: string | undefined = undefined;
J
Joao Moreno 已提交
499

J
Joao Moreno 已提交
500
		if (workspace.workspaceFolders) {
J
Joao Moreno 已提交
501
			const placeHolder = localize('init', "Pick workspace folder to initialize git repo in");
J
Joao Moreno 已提交
502 503 504 505 506
			const pick = { label: localize('choose', "Choose Folder...") };
			const items: { label: string, folder?: WorkspaceFolder }[] = [
				...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })),
				pick
			];
J
Joao Moreno 已提交
507 508 509 510
			const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });

			if (!item) {
				return;
J
Joao Moreno 已提交
511 512
			} else if (item.folder) {
				repositoryPath = item.folder.uri.fsPath;
J
Joao Moreno 已提交
513 514
			}
		}
J
Joao Moreno 已提交
515

J
Joao Moreno 已提交
516
		if (!repositoryPath) {
J
Joao Moreno 已提交
517 518 519 520 521 522 523 524 525 526 527 528
			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 已提交
529

J
Joao Moreno 已提交
530
			if (!result || result.length === 0) {
J
Joao Moreno 已提交
531 532
				return;
			}
J
Joao Moreno 已提交
533 534 535 536 537 538 539 540 541 542 543 544

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

J
Joao Moreno 已提交
545
			repositoryPath = uri.fsPath;
J
Joao Moreno 已提交
546 547
		}

J
Joao Moreno 已提交
548
		await this.git.init(repositoryPath);
I
Ivan Sučić 已提交
549 550

		const choices = [];
J
Joao Moreno 已提交
551 552
		let message = localize('proposeopen init', "Would you like to open the initialized repository?");
		const open = localize('openrepo', "Open Repository");
I
Ivan Sučić 已提交
553
		choices.push(open);
J
Joao Moreno 已提交
554 555 556 557 558 559

		const addToWorkspace = localize('add', "Add to Workspace");
		if (workspace.workspaceFolders) {
			message = localize('proposeopen2 init', "Would you like to open the initialized repository, or add it to the current workspace?");
			choices.push(addToWorkspace);
		}
I
Ivan Sučić 已提交
560 561

		const result = await window.showInformationMessage(message, ...choices);
J
Joao Moreno 已提交
562
		const uri = Uri.file(repositoryPath);
I
Ivan Sučić 已提交
563

J
Joao Moreno 已提交
564 565 566 567 568 569
		if (result === open) {
			commands.executeCommand('vscode.openFolder', uri);
		} else if (result === addToWorkspace) {
			workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
		} else {
			await this.model.openRepository(repositoryPath);
I
Ivan Sučić 已提交
570
		}
J
Joao Moreno 已提交
571 572
	}

J
Joao Moreno 已提交
573 574
	@command('git.openRepository', { repository: false })
	async openRepository(path?: string): Promise<void> {
575
		if (!path) {
J
Joao Moreno 已提交
576 577 578 579 580 581
			const result = await window.showOpenDialog({
				canSelectFiles: false,
				canSelectFolders: true,
				canSelectMany: false,
				defaultUri: Uri.file(os.homedir()),
				openLabel: localize('open repo', "Open Repository")
582 583
			});

J
Joao Moreno 已提交
584
			if (!result || result.length === 0) {
585 586 587
				return;
			}

J
Joao Moreno 已提交
588
			path = result[0].fsPath;
589
		}
J
Joao Moreno 已提交
590 591

		await this.model.openRepository(path);
592 593
	}

J
Joao Moreno 已提交
594 595 596 597 598
	@command('git.close', { repository: true })
	async close(repository: Repository): Promise<void> {
		this.model.close(repository);
	}

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

603
		let uris: Uri[] | undefined;
604 605 606

		if (arg instanceof Uri) {
			if (arg.scheme === 'git') {
607
				uris = [Uri.file(fromGitUri(arg).path)];
608
			} else if (arg.scheme === 'file') {
609
				uris = [arg];
610 611 612 613 614 615
			}
		} else {
			let resource = arg;

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

			if (resource) {
J
Joao Moreno 已提交
620 621 622 623
				const resources = ([resource, ...resourceStates] as Resource[])
					.filter(r => r.type !== Status.DELETED && r.type !== Status.INDEX_DELETED);

				uris = resources.map(r => r.resourceUri);
624
			}
J
Joao Moreno 已提交
625 626
		}

627
		if (!uris) {
J
Joao Moreno 已提交
628
			return;
J
Joao Moreno 已提交
629 630
		}

631 632 633
		const activeTextEditor = window.activeTextEditor;
		for (const uri of uris) {
			const opts: TextDocumentShowOptions = {
J
Joao Moreno 已提交
634
				preserveFocus,
J
Joao Moreno 已提交
635
				preview: false,
636
				viewColumn: ViewColumn.Active
637 638
			};

E
Eric Gang 已提交
639 640
			const document = await workspace.openTextDocument(uri);

641 642 643
			// 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) {
E
Eric Gang 已提交
644
				// preserve not only selection but also visible range
B
Benjamin Pasero 已提交
645
				opts.selection = activeTextEditor.selection;
E
Eric Gang 已提交
646 647 648 649 650
				const previousVisibleRanges = activeTextEditor.visibleRanges;
				const editor = await window.showTextDocument(document, opts);
				editor.revealRange(previousVisibleRanges[0]);
			} else {
				await window.showTextDocument(document, opts);
B
Benjamin Pasero 已提交
651
			}
J
Joao Moreno 已提交
652
		}
J
Joao Moreno 已提交
653 654
	}

J
Joao Moreno 已提交
655 656 657 658 659
	@command('git.openFile2')
	async openFile2(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
		this.openFile(arg, ...resourceStates);
	}

J
Joao Moreno 已提交
660 661
	@command('git.openHEADFile')
	async openHEADFile(arg?: Resource | Uri): Promise<void> {
D
Duroktar 已提交
662
		let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
663
		const preview = !(arg instanceof Resource);
D
Duroktar 已提交
664 665 666 667

		if (arg instanceof Resource) {
			resource = arg;
		} else if (arg instanceof Uri) {
668
			resource = this.getSCMResource(arg);
D
Duroktar 已提交
669
		} else {
670
			resource = this.getSCMResource();
D
Duroktar 已提交
671 672 673 674 675 676
		}

		if (!resource) {
			return;
		}

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

J
Joao Moreno 已提交
679 680 681
		if (!HEAD) {
			window.showWarningMessage(localize('HEAD not available', "HEAD version of '{0}' is not available.", path.basename(resource.resourceUri.fsPath)));
			return;
D
Duroktar 已提交
682
		}
J
Joao Moreno 已提交
683

J
Joao Moreno 已提交
684 685 686 687 688
		const opts: TextDocumentShowOptions = {
			preview
		};

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

J
Joao Moreno 已提交
691 692
	@command('git.openChange')
	async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
693
		const preserveFocus = arg instanceof Resource;
J
Joao Moreno 已提交
694 695
		const preview = !(arg instanceof Resource);

J
Joao 已提交
696
		const preserveSelection = arg instanceof Uri || !arg;
697
		let resources: Resource[] | undefined = undefined;
698

699
		if (arg instanceof Uri) {
700
			const resource = this.getSCMResource(arg);
701 702 703
			if (resource !== undefined) {
				resources = [resource];
			}
704
		} else {
705
			let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
706

707 708 709
			if (arg instanceof Resource) {
				resource = arg;
			} else {
710
				resource = this.getSCMResource();
711 712 713 714 715
			}

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

718
		if (!resources) {
J
Joao Moreno 已提交
719
			return;
J
Joao Moreno 已提交
720
		}
J
Joao Moreno 已提交
721

722
		for (const resource of resources) {
J
Joao 已提交
723
			await this._openResource(resource, preview, preserveFocus, preserveSelection);
724
		}
J
Joao Moreno 已提交
725 726
	}

727 728
	@command('git.stage')
	async stage(...resourceStates: SourceControlResourceState[]): Promise<void> {
J
Joao Moreno 已提交
729 730
		this.outputChannel.appendLine(`git.stage ${resourceStates.length}`);

731 732
		resourceStates = resourceStates.filter(s => !!s);

J
Joao Moreno 已提交
733
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
734
			const resource = this.getSCMResource();
735

J
Joao Moreno 已提交
736 737
			this.outputChannel.appendLine(`git.stage.getSCMResource ${resource ? resource.resourceUri.toString() : null}`);

738 739 740 741 742 743 744
			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

745
		const selection = resourceStates.filter(s => s instanceof Resource) as Resource[];
J
Joao Moreno 已提交
746
		const { resolved, unresolved, deletionConflicts } = await categorizeResourceByResolution(selection);
747 748 749 750 751

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

753 754 755 756 757 758 759 760
			const yes = localize('yes', "Yes");
			const pick = await window.showWarningMessage(message, { modal: true }, yes);

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

J
Joao Moreno 已提交
761 762 763 764 765 766 767 768 769 770 771 772 773 774
		try {
			await this.runByRepository(deletionConflicts.map(r => r.resourceUri), async (repository, resources) => {
				for (const resource of resources) {
					await this._stageDeletionConflict(repository, resource);
				}
			});
		} catch (err) {
			if (/Cancelled/.test(err.message)) {
				return;
			}

			throw err;
		}

J
Joao Moreno 已提交
775
		const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree);
776
		const scmResources = [...workingTree, ...resolved, ...unresolved];
777

J
Joao Moreno 已提交
778
		this.outputChannel.appendLine(`git.stage.scmResources ${scmResources.length}`);
779
		if (!scmResources.length) {
J
Joao Moreno 已提交
780 781
			return;
		}
J
Joao Moreno 已提交
782

783
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
784
		await this.runByRepository(resources, async (repository, resources) => repository.add(resources));
J
Joao Moreno 已提交
785 786
	}

J
Joao Moreno 已提交
787 788
	@command('git.stageAll', { repository: true })
	async stageAll(repository: Repository): Promise<void> {
789
		const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[];
J
Joao Moreno 已提交
790 791 792 793 794 795 796 797 798 799 800 801 802
		const { merge, unresolved, deletionConflicts } = await categorizeResourceByResolution(resources);

		try {
			for (const deletionConflict of deletionConflicts) {
				await this._stageDeletionConflict(repository, deletionConflict.resourceUri);
			}
		} catch (err) {
			if (/Cancelled/.test(err.message)) {
				return;
			}

			throw err;
		}
803 804 805

		if (unresolved.length > 0) {
			const message = unresolved.length > 1
J
Joao Moreno 已提交
806 807
				? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", merge.length)
				: localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(merge[0].resourceUri.fsPath));
808 809 810 811 812 813 814 815 816

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

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

J
Joao Moreno 已提交
817
		await repository.add([]);
J
Joao Moreno 已提交
818 819
	}

J
Joao Moreno 已提交
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
	private async _stageDeletionConflict(repository: Repository, uri: Uri): Promise<void> {
		const uriString = uri.toString();
		const resource = repository.mergeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];

		if (!resource) {
			return;
		}

		if (resource.type === Status.DELETED_BY_THEM) {
			const keepIt = localize('keep ours', "Keep Our Version");
			const deleteIt = localize('delete', "Delete File");
			const result = await window.showInformationMessage(localize('deleted by them', "File '{0}' was deleted by them and modified by us.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt);

			if (result === keepIt) {
				await repository.add([uri]);
			} else if (result === deleteIt) {
				await repository.rm([uri]);
			} else {
				throw new Error('Cancelled');
			}
		} else if (resource.type === Status.DELETED_BY_US) {
			const keepIt = localize('keep theirs', "Keep Their Version");
			const deleteIt = localize('delete', "Delete File");
			const result = await window.showInformationMessage(localize('deleted by us', "File '{0}' was deleted by us and modified by them.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt);

			if (result === keepIt) {
				await repository.add([uri]);
			} else if (result === deleteIt) {
				await repository.rm([uri]);
			} else {
				throw new Error('Cancelled');
			}
		}
	}

J
Joao Moreno 已提交
855
	@command('git.stageChange')
J
Joao Moreno 已提交
856 857 858 859 860 861 862 863
	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 已提交
864 865
	}

J
Joao Moreno 已提交
866
	@command('git.stageSelectedRanges', { diff: true })
J
Joao Moreno 已提交
867
	async stageSelectedChanges(changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
868 869 870 871 872 873 874
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
J
Joao Moreno 已提交
875 876 877 878
		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 已提交
879

J
Joao Moreno 已提交
880
		if (!selectedChanges.length) {
J
Joao Moreno 已提交
881 882 883
			return;
		}

J
Joao Moreno 已提交
884 885
		await this._stageChanges(textEditor, selectedChanges);
	}
J
Joao Moreno 已提交
886

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

J
Joao Moreno 已提交
891
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
892 893 894
			return;
		}

J
Joao Moreno 已提交
895
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
896
		const originalDocument = await workspace.openTextDocument(originalUri);
J
Joao Moreno 已提交
897
		const result = applyLineChanges(originalDocument, modifiedDocument, changes);
J
Joao Moreno 已提交
898

J
Joao Moreno 已提交
899
		await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
J
Joao Moreno 已提交
900
	}
J
Joao Moreno 已提交
901

J
Joao Moreno 已提交
902
	@command('git.revertChange')
J
Joao Moreno 已提交
903 904 905 906
	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 已提交
907 908 909
			return;
		}

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

J
Joao Moreno 已提交
913
	@command('git.revertSelectedRanges', { diff: true })
J
Joao Moreno 已提交
914
	async revertSelectedRanges(changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
915 916 917 918 919 920 921
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
J
Joao Moreno 已提交
922 923
		const selections = textEditor.selections;
		const selectedChanges = changes.filter(change => {
J
Joao Moreno 已提交
924
			const modifiedRange = getModifiedRange(modifiedDocument, change);
J
Joao Moreno 已提交
925 926 927 928
			return selections.every(selection => !selection.intersection(modifiedRange));
		});

		if (selectedChanges.length === changes.length) {
J
Joao Moreno 已提交
929 930 931
			return;
		}

J
Joao Moreno 已提交
932 933
		await this._revertChanges(textEditor, selectedChanges);
	}
J
Joao Moreno 已提交
934

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

J
Joao Moreno 已提交
939
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
940 941 942
			return;
		}

J
Joao Moreno 已提交
943 944
		const originalUri = toGitUri(modifiedUri, '~');
		const originalDocument = await workspace.openTextDocument(originalUri);
945 946
		const selectionsBeforeRevert = textEditor.selections;
		const visibleRangesBeforeRevert = textEditor.visibleRanges;
J
Joao Moreno 已提交
947
		const result = applyLineChanges(originalDocument, modifiedDocument, changes);
948

J
Joao Moreno 已提交
949 950 951
		const edit = new WorkspaceEdit();
		edit.replace(modifiedUri, new Range(new Position(0, 0), modifiedDocument.lineAt(modifiedDocument.lineCount - 1).range.end), result);
		workspace.applyEdit(edit);
952

J
Joao Moreno 已提交
953 954
		await modifiedDocument.save();

955 956
		textEditor.selections = selectionsBeforeRevert;
		textEditor.revealRange(visibleRangesBeforeRevert[0]);
J
Joao Moreno 已提交
957 958
	}

J
Joao Moreno 已提交
959 960
	@command('git.unstage')
	async unstage(...resourceStates: SourceControlResourceState[]): Promise<void> {
961 962
		resourceStates = resourceStates.filter(s => !!s);

J
Joao Moreno 已提交
963
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
964
			const resource = this.getSCMResource();
965 966 967 968 969 970 971 972

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

976
		if (!scmResources.length) {
J
Joao Moreno 已提交
977 978 979
			return;
		}

980
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
981
		await this.runByRepository(resources, async (repository, resources) => repository.revert(resources));
J
Joao Moreno 已提交
982 983
	}

J
Joao Moreno 已提交
984 985
	@command('git.unstageAll', { repository: true })
	async unstageAll(repository: Repository): Promise<void> {
986
		await repository.revert([]);
J
Joao Moreno 已提交
987
	}
J
Joao Moreno 已提交
988

J
Joao Moreno 已提交
989 990
	@command('git.unstageSelectedRanges', { diff: true })
	async unstageSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
991 992 993 994 995 996 997 998 999
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

1000 1001 1002 1003 1004 1005 1006
		if (modifiedUri.scheme !== 'git') {
			return;
		}

		const { ref } = fromGitUri(modifiedUri);

		if (ref !== '') {
J
Joao Moreno 已提交
1007 1008 1009
			return;
		}

J
Joao Moreno 已提交
1010
		const originalUri = toGitUri(modifiedUri, 'HEAD');
J
Joao Moreno 已提交
1011
		const originalDocument = await workspace.openTextDocument(originalUri);
1012 1013 1014 1015
		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 已提交
1016 1017 1018 1019 1020

		if (!selectedDiffs.length) {
			return;
		}

1021 1022
		const invertedDiffs = selectedDiffs.map(invertLineChange);
		const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs);
J
Joao Moreno 已提交
1023

J
Joao Moreno 已提交
1024
		await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
J
Joao Moreno 已提交
1025 1026
	}

J
Joao Moreno 已提交
1027 1028
	@command('git.clean')
	async clean(...resourceStates: SourceControlResourceState[]): Promise<void> {
1029 1030
		resourceStates = resourceStates.filter(s => !!s);

J
Joao Moreno 已提交
1031
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
1032
			const resource = this.getSCMResource();
1033 1034 1035 1036 1037 1038 1039 1040

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

J
Joao Moreno 已提交
1044
		if (!scmResources.length) {
J
Joao Moreno 已提交
1045 1046
			return;
		}
J
Joao Moreno 已提交
1047

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

J
Joao Moreno 已提交
1052
		if (scmResources.length === 1) {
J
Joao Moreno 已提交
1053
			if (untrackedCount > 0) {
J
Joao Moreno 已提交
1054
				message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(scmResources[0].resourceUri.fsPath));
J
Joao Moreno 已提交
1055 1056
				yes = localize('delete file', "Delete file");
			} else {
1057 1058 1059 1060 1061 1062
				if (scmResources[0].type === Status.DELETED) {
					yes = localize('restore file', "Restore file");
					message = localize('confirm restore', "Are you sure you want to restore {0}?", path.basename(scmResources[0].resourceUri.fsPath));
				} else {
					message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(scmResources[0].resourceUri.fsPath));
				}
J
Joao Moreno 已提交
1063 1064
			}
		} else {
1065 1066 1067 1068 1069 1070
			if (scmResources.every(resource => resource.type === Status.DELETED)) {
				yes = localize('restore files', "Restore files");
				message = localize('confirm restore multiple', "Are you sure you want to restore {0} files?", scmResources.length);
			} else {
				message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", scmResources.length);
			}
J
Joao Moreno 已提交
1071 1072 1073 1074 1075

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

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

J
Joao Moreno 已提交
1079 1080 1081 1082
		if (pick !== yes) {
			return;
		}

J
Joao Moreno 已提交
1083
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
1084
		await this.runByRepository(resources, async (repository, resources) => repository.clean(resources));
J
Joao Moreno 已提交
1085
	}
J
Joao Moreno 已提交
1086

J
Joao Moreno 已提交
1087 1088
	@command('git.cleanAll', { repository: true })
	async cleanAll(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1089
		let resources = repository.workingTreeGroup.resourceStates;
J
Joao Moreno 已提交
1090 1091 1092 1093 1094

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

J
Joao Moreno 已提交
1095 1096
		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 已提交
1097

J
Joao Moreno 已提交
1098 1099 1100 1101 1102 1103
		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 已提交
1104
				: localize('discardAll', "Discard All {0} Files", resources.length);
J
Joao Moreno 已提交
1105
			const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
1106

J
Joao Moreno 已提交
1107
			if (pick !== yes) {
J
Joao Moreno 已提交
1108 1109 1110
				return;
			}

J
Joao 已提交
1111
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
1112 1113 1114 1115 1116 1117 1118 1119
			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 已提交
1120 1121
			}

J
Joao 已提交
1122
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
1123 1124 1125 1126
		} 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 已提交
1127

J
Joao Moreno 已提交
1128 1129 1130
			if (pick !== yes) {
				return;
			}
J
Joao Moreno 已提交
1131

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

J
Joao Moreno 已提交
1134 1135 1136 1137
		} 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 已提交
1138

J
Joao Moreno 已提交
1139 1140 1141 1142 1143
			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 已提交
1144

J
Joao Moreno 已提交
1145
			const yesAll = localize('discardAll', "Discard All {0} Files", resources.length);
J
Joao Moreno 已提交
1146 1147 1148 1149 1150 1151 1152 1153
			const pick = await window.showWarningMessage(message, { modal: true }, yesTracked, yesAll);

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

J
Joao 已提交
1154
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
1155
		}
J
Joao Moreno 已提交
1156 1157
	}

J
Joao Moreno 已提交
1158
	private async smartCommit(
J
Joao Moreno 已提交
1159
		repository: Repository,
1160
		getCommitMessage: () => Promise<string | undefined>,
J
Joao Moreno 已提交
1161 1162
		opts?: CommitOptions
	): Promise<boolean> {
1163
		const config = workspace.getConfiguration('git', Uri.file(repository.root));
J
Joao Moreno 已提交
1164 1165 1166 1167 1168 1169 1170 1171
		const promptToSaveFilesBeforeCommit = config.get<boolean>('promptToSaveFilesBeforeCommit') === true;

		if (promptToSaveFilesBeforeCommit) {
			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
J
Joao Moreno 已提交
1172 1173
					? localize('unsaved files single', "The following file is unsaved: {0}.\n\nWould you like to save it before committing?", path.basename(unsavedTextDocuments[0].uri.fsPath))
					: localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before committing?", unsavedTextDocuments.length);
J
Joao Moreno 已提交
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
				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
				}
1184 1185 1186
			}
		}

1187
		const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
1188
		const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
J
Joao Moreno 已提交
1189 1190
		const noStagedChanges = repository.indexGroup.resourceStates.length === 0;
		const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
1191 1192

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

J
Joao Moreno 已提交
1195 1196
			// 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?");
1197 1198 1199 1200 1201
			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 已提交
1202 1203 1204
				config.update('enableSmartCommit', true, true);
			} else if (pick !== yes) {
				return false; // do not commit on cancel
1205 1206 1207
			}
		}

J
Joao Moreno 已提交
1208
		if (!opts) {
1209
			opts = { all: noStagedChanges };
J
Joao Moreno 已提交
1210 1211
		} else if (!opts.all && noStagedChanges) {
			opts = { ...opts, all: true };
J
Joao Moreno 已提交
1212 1213
		}

1214 1215 1216
		// enable signing of commits if configurated
		opts.signCommit = enableCommitSigning;

1217 1218 1219 1220
		if (config.get<boolean>('alwaysSignOff')) {
			opts.signoff = true;
		}

J
Joao Moreno 已提交
1221
		if (
1222
			(
J
Joao Moreno 已提交
1223 1224 1225 1226 1227
				// no changes
				(noStagedChanges && noUnstagedChanges)
				// or no staged changes and not `all`
				|| (!opts.all && noStagedChanges)
			)
1228
			&& !opts.empty
J
Joao Moreno 已提交
1229
		) {
J
Joao Moreno 已提交
1230 1231 1232 1233
			window.showInformationMessage(localize('no changes', "There are no changes to commit."));
			return false;
		}

J
Joao Moreno 已提交
1234
		const message = await getCommitMessage();
J
Joao Moreno 已提交
1235 1236 1237 1238 1239

		if (!message) {
			return false;
		}

J
Joao Moreno 已提交
1240
		await repository.commit(message, opts);
J
Joao Moreno 已提交
1241

J
Joao Moreno 已提交
1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252
		const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand');

		switch (postCommitCommand) {
			case 'push':
				await this._push(repository, { pushType: PushType.Push, silent: true });
				break;
			case 'sync':
				await this.sync(repository);
				break;
		}

J
Joao Moreno 已提交
1253 1254 1255
		return true;
	}

J
Joao Moreno 已提交
1256
	private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> {
J
Joao Moreno 已提交
1257
		const message = repository.inputBox.value;
J
Joao Moreno 已提交
1258
		const getCommitMessage = async () => {
J
Joao Moreno 已提交
1259 1260 1261 1262
			if (message) {
				return message;
			}

1263 1264 1265 1266 1267 1268
			let value: string | undefined = undefined;

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

J
Joao Moreno 已提交
1269
			return await window.showInputBox({
1270
				value,
J
Joao Moreno 已提交
1271
				placeHolder: localize('commit message', "Commit message"),
J
Joao Moreno 已提交
1272 1273
				prompt: localize('provide commit message', "Please provide a commit message"),
				ignoreFocusOut: true
J
Joao Moreno 已提交
1274
			});
J
Joao Moreno 已提交
1275 1276
		};

J
Joao Moreno 已提交
1277
		const didCommit = await this.smartCommit(repository, getCommitMessage, opts);
J
Joao Moreno 已提交
1278 1279

		if (message && didCommit) {
J
Joao Moreno 已提交
1280
			repository.inputBox.value = await repository.getCommitTemplate();
J
Joao Moreno 已提交
1281
		}
J
Joao Moreno 已提交
1282 1283
	}

J
Joao Moreno 已提交
1284
	@command('git.commit', { repository: true })
J
Joao Moreno 已提交
1285 1286
	async commit(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository);
J
Joao Moreno 已提交
1287 1288
	}

J
Joao Moreno 已提交
1289
	@command('git.commitWithInput', { repository: true })
J
Joao Moreno 已提交
1290
	async commitWithInput(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1291
		if (!repository.inputBox.value) {
J
Joao Moreno 已提交
1292 1293 1294
			return;
		}

J
Joao Moreno 已提交
1295
		const didCommit = await this.smartCommit(repository, async () => repository.inputBox.value);
J
Joao Moreno 已提交
1296 1297

		if (didCommit) {
J
Joao Moreno 已提交
1298
			repository.inputBox.value = await repository.getCommitTemplate();
J
Joao Moreno 已提交
1299
		}
J
Joao Moreno 已提交
1300 1301
	}

J
Joao Moreno 已提交
1302
	@command('git.commitStaged', { repository: true })
J
Joao Moreno 已提交
1303 1304
	async commitStaged(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: false });
J
Joao Moreno 已提交
1305 1306
	}

J
Joao Moreno 已提交
1307
	@command('git.commitStagedSigned', { repository: true })
J
Joao Moreno 已提交
1308 1309
	async commitStagedSigned(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: false, signoff: true });
J
Joao Moreno 已提交
1310 1311
	}

J
Joao Moreno 已提交
1312
	@command('git.commitStagedAmend', { repository: true })
J
Joao Moreno 已提交
1313
	async commitStagedAmend(repository: Repository): Promise<void> {
1314
		await this.commitWithAnyInput(repository, { all: false, amend: true });
K
Krzysztof Cieślak 已提交
1315 1316
	}

J
Joao Moreno 已提交
1317
	@command('git.commitAll', { repository: true })
J
Joao Moreno 已提交
1318 1319
	async commitAll(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true });
J
Joao Moreno 已提交
1320 1321
	}

J
Joao Moreno 已提交
1322
	@command('git.commitAllSigned', { repository: true })
J
Joao Moreno 已提交
1323 1324
	async commitAllSigned(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true, signoff: true });
J
Joao Moreno 已提交
1325 1326
	}

J
Joao Moreno 已提交
1327
	@command('git.commitAllAmend', { repository: true })
J
Joao Moreno 已提交
1328 1329
	async commitAllAmend(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true, amend: true });
K
Krzysztof Cieślak 已提交
1330 1331
	}

J
Joao Moreno 已提交
1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353
	@command('git.commitEmpty', { repository: true })
	async commitEmpty(repository: Repository): Promise<void> {
		const root = Uri.file(repository.root);
		const config = workspace.getConfiguration('git', root);
		const shouldPrompt = config.get<boolean>('confirmEmptyCommits') === true;

		if (shouldPrompt) {
			const message = localize('confirm emtpy commit', "Are you sure you want to create an empty commit?");
			const yes = localize('yes', "Yes");
			const neverAgain = localize('yes never again', "Yes, Don't Show Again");
			const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);

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

		await this.commitWithAnyInput(repository, { empty: true });
	}

J
Joao Moreno 已提交
1354 1355 1356 1357 1358
	@command('git.restoreCommitTemplate', { repository: true })
	async restoreCommitTemplate(repository: Repository): Promise<void> {
		repository.inputBox.value = await repository.getCommitTemplate();
	}

J
Joao Moreno 已提交
1359
	@command('git.undoCommit', { repository: true })
J
Joao Moreno 已提交
1360 1361
	async undoCommit(repository: Repository): Promise<void> {
		const HEAD = repository.HEAD;
J
Joao Moreno 已提交
1362 1363

		if (!HEAD || !HEAD.commit) {
J
Joao Moreno 已提交
1364
			window.showWarningMessage(localize('no more', "Can't undo because HEAD doesn't point to any commit."));
J
Joao Moreno 已提交
1365 1366 1367
			return;
		}

J
Joao Moreno 已提交
1368
		const commit = await repository.getCommit('HEAD');
J
Joao Moreno 已提交
1369 1370

		if (commit.parents.length > 0) {
1371 1372 1373 1374 1375
			await repository.reset('HEAD~');
		} else {
			await repository.deleteRef('HEAD');
			await this.unstageAll(repository);
		}
J
Joao Moreno 已提交
1376

J
Joao Moreno 已提交
1377
		repository.inputBox.value = commit.message;
J
Joao Moreno 已提交
1378 1379
	}

J
Joao Moreno 已提交
1380
	@command('git.checkout', { repository: true })
1381
	async checkout(repository: Repository, treeish: string): Promise<boolean> {
J
Joao Moreno 已提交
1382
		if (typeof treeish === 'string') {
1383 1384
			await repository.checkout(treeish);
			return true;
J
Joao Moreno 已提交
1385 1386
		}

J
Joao Moreno 已提交
1387
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
1388
		const checkoutType = config.get<string>('checkoutType') || 'all';
J
Joao Moreno 已提交
1389 1390 1391
		const includeTags = checkoutType === 'all' || checkoutType === 'tags';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
1392
		const createBranch = new CreateBranchItem(this);
J
Joao Moreno 已提交
1393

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

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

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

J
Joao Moreno 已提交
1403
		const picks = [createBranch, ...heads, ...tags, ...remoteHeads];
1404
		const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout');
J
Joao Moreno 已提交
1405
		const choice = await window.showQuickPick(picks, { placeHolder });
J
Joao Moreno 已提交
1406 1407

		if (!choice) {
1408
			return false;
J
Joao Moreno 已提交
1409 1410
		}

J
Joao Moreno 已提交
1411
		await choice.run(repository);
1412
		return true;
J
Joao Moreno 已提交
1413 1414
	}

J
Joao Moreno 已提交
1415
	@command('git.branch', { repository: true })
J
Joao Moreno 已提交
1416
	async branch(repository: Repository): Promise<void> {
1417
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
1418 1419 1420 1421 1422 1423 1424 1425 1426 1427
		const branchValidationRegex = config.get<string>('branchValidationRegex')!;
		const branchWhitespaceChar = config.get<string>('branchWhitespaceChar')!;
		const validateName = new RegExp(branchValidationRegex);
		const sanitize = (name: string) => {
			name = name.trim();

			if (!name) {
				return name;
			}

1428
			return name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar);
J
Joao Moreno 已提交
1429 1430
		};

J
Joao Moreno 已提交
1431
		const result = await window.showInputBox({
J
Joao Moreno 已提交
1432
			placeHolder: localize('branch name', "Branch name"),
J
Joao Moreno 已提交
1433
			prompt: localize('provide branch name', "Please provide a branch name"),
1434 1435
			ignoreFocusOut: true,
			validateInput: (name: string) => {
J
Joao Moreno 已提交
1436
				if (validateName.test(sanitize(name))) {
1437 1438
					return null;
				}
J
Joao Moreno 已提交
1439 1440

				return localize('branch name format invalid', "Branch name needs to match regex: {0}", branchValidationRegex);
1441
			}
J
Joao Moreno 已提交
1442
		});
J
Joao Moreno 已提交
1443

J
Joao Moreno 已提交
1444 1445 1446
		const name = sanitize(result || '');

		if (!name) {
J
Joao Moreno 已提交
1447 1448
			return;
		}
J
Joao Moreno 已提交
1449

J
Joao Moreno 已提交
1450
		await repository.branch(name, true);
J
Joao Moreno 已提交
1451 1452
	}

J
Joao Moreno 已提交
1453
	@command('git.deleteBranch', { repository: true })
J
Joao Moreno 已提交
1454
	async deleteBranch(repository: Repository, name: string, force?: boolean): Promise<void> {
1455 1456
		let run: (force?: boolean) => Promise<void>;
		if (typeof name === 'string') {
J
Joao Moreno 已提交
1457
			run = force => repository.deleteBranch(name, force);
1458
		} else {
J
Joao Moreno 已提交
1459 1460
			const currentHead = repository.HEAD && repository.HEAD.name;
			const heads = repository.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead)
1461
				.map(ref => new BranchDeleteItem(ref));
M
Maik Riechert 已提交
1462

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

M
Maik Riechert 已提交
1466
			if (!choice || !choice.branchName) {
1467 1468
				return;
			}
M
Maik Riechert 已提交
1469
			name = choice.branchName;
J
Joao Moreno 已提交
1470
			run = force => choice.run(repository, force);
M
Maik Riechert 已提交
1471 1472
		}

1473 1474 1475 1476 1477 1478 1479 1480 1481
		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");
1482
			const pick = await window.showWarningMessage(message, { modal: true }, yes);
1483 1484 1485 1486 1487

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

1490 1491
	@command('git.renameBranch', { repository: true })
	async renameBranch(repository: Repository): Promise<void> {
J
Justin Horner 已提交
1492 1493 1494 1495 1496 1497 1498 1499
		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 {
1500
			await repository.renameBranch(name);
J
Justin Horner 已提交
1501 1502 1503
		} catch (err) {
			switch (err.gitErrorCode) {
				case GitErrorCodes.InvalidBranchName:
1504 1505
					window.showErrorMessage(localize('invalid branch name', 'Invalid branch name'));
					return;
J
Justin Horner 已提交
1506
				case GitErrorCodes.BranchAlreadyExists:
J
Joao Moreno 已提交
1507
					window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", name));
1508 1509 1510
					return;
				default:
					throw err;
J
Justin Horner 已提交
1511 1512 1513 1514
			}
		}
	}

J
Joao Moreno 已提交
1515
	@command('git.merge', { repository: true })
J
Joao Moreno 已提交
1516
	async merge(repository: Repository): Promise<void> {
1517 1518 1519 1520
		const config = workspace.getConfiguration('git');
		const checkoutType = config.get<string>('checkoutType') || 'all';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
1521
		const heads = repository.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
1522 1523
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
1524

J
Joao Moreno 已提交
1525
		const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
1526 1527
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
1528 1529

		const picks = [...heads, ...remoteHeads];
1530 1531
		const placeHolder = localize('select a branch to merge from', 'Select a branch to merge from');
		const choice = await window.showQuickPick<MergeItem>(picks, { placeHolder });
1532 1533 1534 1535 1536

		if (!choice) {
			return;
		}

1537
		await choice.run(repository);
1538 1539
	}

J
Joao Moreno 已提交
1540
	@command('git.createTag', { repository: true })
J
Joao Moreno 已提交
1541
	async createTag(repository: Repository): Promise<void> {
1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553
		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 已提交
1554
			prompt: localize('provide tag message', "Please provide a message to annotate the tag"),
1555 1556 1557 1558 1559
			ignoreFocusOut: true
		});

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

J
Joao Moreno 已提交
1563 1564 1565 1566 1567 1568 1569
	@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;
		}

J
Joao Moreno 已提交
1570
		await repository.fetchDefault();
J
Joao Moreno 已提交
1571 1572
	}

R
Ryan Scott 已提交
1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583
	@command('git.fetchPrune', { repository: true })
	async fetchPrune(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.fetchPrune();
	}


J
Joao Moreno 已提交
1584 1585 1586 1587 1588 1589 1590 1591 1592 1593
	@command('git.fetchAll', { repository: true })
	async fetchAll(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.fetchAll();
	}

J
Joao Moreno 已提交
1594
	@command('git.pullFrom', { repository: true })
J
Joao Moreno 已提交
1595 1596
	async pullFrom(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
1597 1598 1599 1600 1601 1602

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

1603
		const remotePicks = remotes.filter(r => r.fetchUrl !== undefined).map(r => ({ label: r.name, description: r.fetchUrl! }));
1604
		const placeHolder = localize('pick remote pull repo', "Pick a remote to pull the branch from");
D
Dozed12 已提交
1605
		const remotePick = await window.showQuickPick(remotePicks, { placeHolder });
1606

D
Dozed12 已提交
1607
		if (!remotePick) {
1608 1609 1610
			return;
		}

D
Dozed12 已提交
1611 1612
		const remoteRefs = repository.refs;
		const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remotePick.label));
J
Joao Moreno 已提交
1613
		const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name })) as { label: string; description: string }[];
J
Joao Moreno 已提交
1614 1615
		const branchPlaceHolder = localize('pick branch pull', "Pick a branch to pull from");
		const branchPick = await window.showQuickPick(branchPicks, { placeHolder: branchPlaceHolder });
1616

D
Dozed12 已提交
1617
		if (!branchPick) {
1618 1619 1620
			return;
		}

F
Francisco Moreira 已提交
1621 1622
		const remoteCharCnt = remotePick.label.length;

1623
		await repository.pullFrom(false, remotePick.label, branchPick.label.slice(remoteCharCnt + 1));
1624 1625
	}

J
Joao Moreno 已提交
1626
	@command('git.pull', { repository: true })
J
Joao Moreno 已提交
1627 1628
	async pull(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1629 1630 1631 1632 1633 1634

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

J
Joao Moreno 已提交
1635
		await repository.pull(repository.HEAD);
J
Joao Moreno 已提交
1636 1637
	}

J
Joao Moreno 已提交
1638
	@command('git.pullRebase', { repository: true })
J
Joao Moreno 已提交
1639 1640
	async pullRebase(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1641 1642 1643 1644 1645 1646

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

J
Joao Moreno 已提交
1647
		await repository.pullWithRebase(repository.HEAD);
J
Joao Moreno 已提交
1648 1649
	}

J
Joao Moreno 已提交
1650
	private async _push(repository: Repository, pushOptions: PushOptions) {
J
Joao Moreno 已提交
1651
		const remotes = repository.remotes;
1652

J
Joao Moreno 已提交
1653
		if (remotes.length === 0) {
J
Joao Moreno 已提交
1654 1655 1656
			if (!pushOptions.silent) {
				window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
			}
J
Joao Moreno 已提交
1657 1658
			return;
		}
1659

J
Joao Moreno 已提交
1660 1661 1662 1663 1664 1665
		const config = workspace.getConfiguration('git', Uri.file(repository.root));
		let forcePushMode: ForcePushMode | undefined = undefined;

		if (pushOptions.forcePush) {
			if (!config.get<boolean>('allowForcePush')) {
				await window.showErrorMessage(localize('force push not allowed', "Force push is not allowed, please enable it with the 'git.allowForcePush' setting."));
1666 1667
				return;
			}
J
Joao Moreno 已提交
1668

J
Joao Moreno 已提交
1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682
			forcePushMode = config.get<boolean>('useForcePushWithLease') === true ? ForcePushMode.ForceWithLease : ForcePushMode.Force;

			if (config.get<boolean>('confirmForcePush')) {
				const message = localize('confirm force push', "You are about to force push your changes, this can be destructive and could inadvertedly overwrite changes made by others.\n\nAre you sure to continue?");
				const yes = localize('ok', "OK");
				const neverAgain = localize('never ask again', "OK, Don't Ask Again");
				const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);

				if (pick === neverAgain) {
					config.update('confirmForcePush', false, true);
				} else if (pick !== yes) {
					return;
				}
			}
J
Joao Moreno 已提交
1683 1684
		}

1685 1686 1687 1688 1689 1690 1691
		if (pushOptions.pushType === PushType.PushTags) {
			await repository.pushTags(undefined, forcePushMode);

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

1692
		if (!repository.HEAD || !repository.HEAD.name) {
J
Joao Moreno 已提交
1693 1694 1695
			if (!pushOptions.silent) {
				window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
			}
1696 1697 1698
			return;
		}

1699 1700 1701 1702 1703 1704 1705
		if (pushOptions.pushType === PushType.Push) {
			try {
				await repository.push(repository.HEAD, forcePushMode);
			} catch (err) {
				if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
					throw err;
				}
1706

J
Joao Moreno 已提交
1707 1708 1709 1710
				if (pushOptions.silent) {
					return;
				}

1711 1712 1713 1714 1715 1716 1717 1718 1719 1720
				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);
				}
			}
		} else {
1721
			const branchName = repository.HEAD.name;
1722 1723 1724
			const picks = remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl! }));
			const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName);
			const pick = await window.showQuickPick(picks, { placeHolder });
1725

1726 1727
			if (!pick) {
				return;
1728
			}
1729 1730

			await repository.pushTo(pick.label, branchName, undefined, forcePushMode);
1731
		}
J
Joao Moreno 已提交
1732 1733
	}

1734 1735
	@command('git.push', { repository: true })
	async push(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1736
		await this._push(repository, { pushType: PushType.Push });
1737
	}
1738

1739 1740
	@command('git.pushForce', { repository: true })
	async pushForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1741
		await this._push(repository, { pushType: PushType.Push, forcePush: true });
1742
	}
1743

1744 1745
	@command('git.pushWithTags', { repository: true })
	async pushWithTags(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1746
		await this._push(repository, { pushType: PushType.PushTags });
1747
	}
1748

1749 1750
	@command('git.pushWithTagsForce', { repository: true })
	async pushWithTagsForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1751
		await this._push(repository, { pushType: PushType.PushTags, forcePush: true });
1752 1753
	}

J
Joao Moreno 已提交
1754
	@command('git.pushTo', { repository: true })
J
Joao Moreno 已提交
1755
	async pushTo(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1756
		await this._push(repository, { pushType: PushType.PushTo });
1757
	}
J
Joao Moreno 已提交
1758

1759 1760
	@command('git.pushToForce', { repository: true })
	async pushToForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1761
		await this._push(repository, { pushType: PushType.PushTo, forcePush: true });
J
Joao Moreno 已提交
1762 1763
	}

J
Joao Moreno 已提交
1764
	private async _sync(repository: Repository, rebase: boolean): Promise<void> {
J
Joao Moreno 已提交
1765
		const HEAD = repository.HEAD;
J
Joao Moreno 已提交
1766 1767 1768 1769 1770

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

J
Joao Moreno 已提交
1771 1772 1773 1774
		const remoteName = HEAD.remote || HEAD.upstream.remote;
		const remote = repository.remotes.find(r => r.name === remoteName);
		const isReadonly = remote && remote.isReadOnly;

J
Joao Moreno 已提交
1775
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
1776
		const shouldPrompt = !isReadonly && config.get<boolean>('confirmSync') === true;
J
Joao Moreno 已提交
1777 1778

		if (shouldPrompt) {
J
Joao Moreno 已提交
1779
			const message = localize('sync is unpredictable', "This action will push and pull commits to and from '{0}/{1}'.", HEAD.upstream.remote, HEAD.upstream.name);
J
Joao Moreno 已提交
1780
			const yes = localize('ok', "OK");
B
Benjamin Pasero 已提交
1781
			const neverAgain = localize('never again', "OK, Don't Show Again");
J
Joao Moreno 已提交
1782 1783 1784 1785 1786 1787 1788 1789 1790
			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 已提交
1791
		if (rebase) {
J
Joao Moreno 已提交
1792
			await repository.syncRebase(HEAD);
J
Joao Moreno 已提交
1793
		} else {
J
Joao Moreno 已提交
1794
			await repository.sync(HEAD);
J
Joao Moreno 已提交
1795 1796 1797 1798 1799 1800
		}
	}

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

J
Joao 已提交
1803 1804 1805 1806 1807 1808 1809 1810 1811
	@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;
			}

J
Joao Moreno 已提交
1812
			await repository.sync(HEAD);
J
Joao 已提交
1813 1814 1815
		}));
	}

1816
	@command('git.syncRebase', { repository: true })
J
Joao Moreno 已提交
1817 1818
	syncRebase(repository: Repository): Promise<void> {
		return this._sync(repository, true);
J
Joao Moreno 已提交
1819 1820
	}

J
Joao Moreno 已提交
1821
	@command('git.publish', { repository: true })
J
Joao Moreno 已提交
1822 1823
	async publish(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1824 1825 1826 1827 1828 1829

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

J
Joao Moreno 已提交
1830
		const branchName = repository.HEAD && repository.HEAD.name || '';
M
Markus Wolf 已提交
1831 1832 1833 1834 1835 1836
		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 已提交
1837 1838 1839 1840 1841

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
1842
		await repository.pushTo(choice, branchName, true);
J
Joao Moreno 已提交
1843 1844
	}

1845 1846
	@command('git.ignore')
	async ignore(...resourceStates: SourceControlResourceState[]): Promise<void> {
1847 1848
		resourceStates = resourceStates.filter(s => !!s);

J
Joao Moreno 已提交
1849
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
1850
			const resource = this.getSCMResource();
N
NKumar2 已提交
1851

1852
			if (!resource) {
J
Joao Moreno 已提交
1853 1854 1855
				return;
			}

1856
			resourceStates = [resource];
J
Joao Moreno 已提交
1857 1858
		}

1859
		const resources = resourceStates
J
Joao Moreno 已提交
1860 1861 1862
			.filter(s => s instanceof Resource)
			.map(r => r.resourceUri);

1863
		if (!resources.length) {
N
NKumar2 已提交
1864 1865 1866
			return;
		}

1867
		await this.runByRepository(resources, async (repository, resources) => repository.ignore(resources));
N
NKumar2 已提交
1868 1869
	}

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

1874
		if (noUnstagedChanges && noStagedChanges) {
K
Krzysztof Cieślak 已提交
1875 1876 1877
			window.showInformationMessage(localize('no changes stash', "There are no changes to stash."));
			return;
		}
J
Joao Moreno 已提交
1878

1879
		const message = await this.getStashMessage();
J
Joao Moreno 已提交
1880 1881 1882 1883 1884

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

J
Joao Moreno 已提交
1885
		await repository.createStash(message, includeUntracked);
K
Krzysztof Cieślak 已提交
1886 1887
	}

1888 1889 1890 1891 1892 1893 1894
	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 已提交
1895 1896 1897 1898 1899 1900 1901 1902 1903 1904
	@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 已提交
1905
	@command('git.stashPop', { repository: true })
J
Joao Moreno 已提交
1906
	async stashPop(repository: Repository): Promise<void> {
1907
		const placeHolder = localize('pick stash to pop', "Pick a stash to pop");
J
Joao Moreno 已提交
1908
		const stash = await this.pickStash(repository, placeHolder);
1909

J
Joao Moreno 已提交
1910
		if (!stash) {
1911 1912 1913
			return;
		}

J
Joao Moreno 已提交
1914
		await repository.popStash(stash.index);
1915 1916 1917 1918
	}

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

J
Joao Moreno 已提交
1921 1922
		if (stashes.length === 0) {
			window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
K
Krzysztof Cieślak 已提交
1923 1924 1925
			return;
		}

1926 1927 1928 1929 1930 1931
		await repository.popStash();
	}

	@command('git.stashApply', { repository: true })
	async stashApply(repository: Repository): Promise<void> {
		const placeHolder = localize('pick stash to apply', "Pick a stash to apply");
J
Joao Moreno 已提交
1932
		const stash = await this.pickStash(repository, placeHolder);
K
Krzysztof Cieślak 已提交
1933

J
Joao Moreno 已提交
1934
		if (!stash) {
K
Krzysztof Cieślak 已提交
1935 1936
			return;
		}
J
Joao Moreno 已提交
1937

J
Joao Moreno 已提交
1938
		await repository.applyStash(stash.index);
K
Krzysztof Cieślak 已提交
1939 1940
	}

1941 1942
	@command('git.stashApplyLatest', { repository: true })
	async stashApplyLatest(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1943
		const stashes = await repository.getStashes();
J
Joao Moreno 已提交
1944

J
Joao Moreno 已提交
1945 1946
		if (stashes.length === 0) {
			window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
1947 1948 1949 1950 1951 1952
			return;
		}

		await repository.applyStash();
	}

J
Joao Moreno 已提交
1953
	private async pickStash(repository: Repository, placeHolder: string): Promise<Stash | undefined> {
1954 1955
		const stashes = await repository.getStashes();

J
Joao Moreno 已提交
1956 1957
		if (stashes.length === 0) {
			window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
K
Krzysztof Cieślak 已提交
1958 1959 1960
			return;
		}

J
Joao Moreno 已提交
1961 1962 1963
		const picks = stashes.map(stash => ({ label: `#${stash.index}:  ${stash.description}`, description: '', details: '', stash }));
		const result = await window.showQuickPick(picks, { placeHolder });
		return result && result.stash;
J
Joao Moreno 已提交
1964
	}
K
Krzysztof Cieślak 已提交
1965

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

J
Joao Moreno 已提交
1970
			if (!options.repository) {
J
Joao Moreno 已提交
1971 1972
				result = Promise.resolve(method.apply(this, args));
			} else {
J
Joao Moreno 已提交
1973 1974
				// try to guess the repository based on the first argument
				const repository = this.model.getRepository(args[0]);
J
Joao Moreno 已提交
1975 1976 1977 1978 1979 1980 1981 1982 1983
				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();
				}
1984

J
Joao Moreno 已提交
1985
				result = repositoryPromise.then(repository => {
J
Joao Moreno 已提交
1986
					if (!repository) {
J
Joao 已提交
1987
						return Promise.resolve();
J
Joao Moreno 已提交
1988 1989
					}

J
Joao Moreno 已提交
1990
					return Promise.resolve(method.apply(this, [repository, ...args]));
J
Joao Moreno 已提交
1991
				});
J
Joao Moreno 已提交
1992 1993
			}

K
kieferrm 已提交
1994
			/* __GDPR__
K
kieferrm 已提交
1995 1996 1997 1998
				"git.command" : {
					"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
J
Joao Moreno 已提交
1999 2000
			this.telemetryReporter.sendTelemetryEvent('git.command', { command: id });

J
Joao Moreno 已提交
2001
			return result.catch(async err => {
J
Joao Moreno 已提交
2002
				const options: MessageOptions = {
2003
					modal: true
J
Joao Moreno 已提交
2004 2005
				};

J
Joao Moreno 已提交
2006
				let message: string;
2007
				let type: 'error' | 'warning' = 'error';
J
Joao Moreno 已提交
2008

J
Joao Moreno 已提交
2009 2010 2011 2012 2013
				const choices = new Map<string, () => void>();
				const openOutputChannelChoice = localize('open git log', "Open Git Log");
				const outputChannel = this.outputChannel as OutputChannel;
				choices.set(openOutputChannelChoice, () => outputChannel.show());

J
Joao Moreno 已提交
2014
				switch (err.gitErrorCode) {
2015
					case GitErrorCodes.DirtyWorkTree:
J
Joao Moreno 已提交
2016 2017
						message = localize('clean repo', "Please clean your repository working tree before checkout.");
						break;
2018
					case GitErrorCodes.PushRejected:
J
Joao Moreno 已提交
2019
						message = localize('cant push', "Can't push refs to remote. Try running 'Pull' first to integrate your changes.");
2020
						break;
2021 2022 2023 2024 2025
					case GitErrorCodes.Conflict:
						message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing.");
						type = 'warning';
						options.modal = false;
						break;
J
Joao Moreno 已提交
2026 2027 2028 2029 2030
					case GitErrorCodes.StashConflict:
						message = localize('stash merge conflicts', "There were merge conflicts while applying the stash.");
						type = 'warning';
						options.modal = false;
						break;
2031 2032
					case GitErrorCodes.NoUserNameConfigured:
					case GitErrorCodes.NoUserEmailConfigured:
J
Joao Moreno 已提交
2033 2034
						message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git.");
						choices.set(localize('learn more', "Learn More"), () => commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup')));
2035
						break;
J
Joao Moreno 已提交
2036
					default:
2037 2038 2039
						const hint = (err.stderr || err.message || String(err))
							.replace(/^error: /mi, '')
							.replace(/^> husky.*$/mi, '')
J
Joao Moreno 已提交
2040
							.split(/[\r\n]/)
J
João Moreno 已提交
2041
							.filter((line: string) => !!line)
2042 2043 2044 2045 2046
						[0];

						message = hint
							? localize('git error details', "Git: {0}", hint)
							: localize('git error', "Git error");
J
Joao Moreno 已提交
2047 2048 2049 2050 2051 2052 2053 2054 2055

						break;
				}

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

J
Joao Moreno 已提交
2056 2057 2058 2059 2060 2061 2062
				const allChoices = Array.from(choices.keys());
				const result = type === 'error'
					? await window.showErrorMessage(message, options, ...allChoices)
					: await window.showWarningMessage(message, options, ...allChoices);

				if (result) {
					const resultFn = choices.get(result);
J
Joao Moreno 已提交
2063

J
Joao Moreno 已提交
2064 2065 2066
					if (resultFn) {
						resultFn();
					}
J
Joao Moreno 已提交
2067 2068 2069
				}
			});
		};
2070 2071

		// patch this object, so people can call methods directly
2072
		(this as any)[key] = result;
2073 2074

		return result;
J
Joao Moreno 已提交
2075 2076
	}

2077
	private getSCMResource(uri?: Uri): Resource | undefined {
J
Joao Moreno 已提交
2078
		uri = uri ? uri : (window.activeTextEditor && window.activeTextEditor.document.uri);
J
Joao Moreno 已提交
2079

J
Joao Moreno 已提交
2080
		this.outputChannel.appendLine(`git.getSCMResource.uri ${uri && uri.toString()}`);
J
Joao Moreno 已提交
2081 2082 2083
		for (const r of this.model.repositories.map(r => r.root)) {
			this.outputChannel.appendLine(`repo root ${r}`);
		}
J
Joao Moreno 已提交
2084

J
Joao Moreno 已提交
2085
		if (!uri) {
2086
			return undefined;
J
Joao Moreno 已提交
2087 2088 2089
		}

		if (uri.scheme === 'git') {
J
Joao Moreno 已提交
2090 2091
			const { path } = fromGitUri(uri);
			uri = Uri.file(path);
J
Joao Moreno 已提交
2092 2093 2094 2095
		}

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

J
Joao Moreno 已提交
2098
			if (!repository) {
2099 2100
				return undefined;
			}
J
Joao Moreno 已提交
2101

J
Joao Moreno 已提交
2102 2103
			return repository.workingTreeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]
				|| repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
J
Joao Moreno 已提交
2104
		}
2105
		return undefined;
J
Joao Moreno 已提交
2106 2107
	}

J
Joao Moreno 已提交
2108
	private runByRepository<T>(resource: Uri, fn: (repository: Repository, resource: Uri) => Promise<T>): Promise<T[]>;
J
Joao Moreno 已提交
2109 2110 2111 2112 2113 2114
	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) => {
2115
			let repository = this.model.getRepository(resource);
J
Joao Moreno 已提交
2116 2117 2118 2119 2120 2121

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

2122
			// Could it be a submodule?
2123
			if (pathEquals(resource.fsPath, repository.root)) {
2124 2125 2126
				repository = this.model.getRepositoryForSubmodule(resource) || repository;
			}

J
Joao Moreno 已提交
2127
			const tuple = result.filter(p => p.repository === repository)[0];
J
Joao Moreno 已提交
2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143

			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 已提交
2144 2145 2146
	dispose(): void {
		this.disposables.forEach(d => d.dispose());
	}
J
João Moreno 已提交
2147
}