commands.ts 69.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 107 108 109 110 111 112 113 114
class HEADItem implements QuickPickItem {

	constructor(private repository: Repository) { }

	get label(): string { return 'HEAD'; }
	get description(): string { return (this.repository.HEAD && this.repository.HEAD.commit || '').substr(0, 8); }
	get alwaysShow(): boolean { return true; }
}

J
Joao Moreno 已提交
115
interface CommandOptions {
J
Joao Moreno 已提交
116
	repository?: boolean;
J
Joao Moreno 已提交
117 118 119
	diff?: boolean;
}

120 121 122 123
interface Command {
	commandId: string;
	key: string;
	method: Function;
J
Joao Moreno 已提交
124
	options: CommandOptions;
125 126 127
}

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

J
Joao Moreno 已提交
129
function command(commandId: string, options: CommandOptions = {}): Function {
130
	return (_target: any, key: string, descriptor: any) => {
J
Joao Moreno 已提交
131 132 133 134
		if (!(typeof descriptor.value === 'function')) {
			throw new Error('not supported');
		}

J
Joao Moreno 已提交
135
		Commands.push({ commandId, key, method: descriptor.value, options });
J
Joao Moreno 已提交
136 137
	};
}
J
Joao Moreno 已提交
138

J
Joao Moreno 已提交
139 140 141 142 143 144 145 146 147
const ImageMimetypes = [
	'image/png',
	'image/gif',
	'image/jpeg',
	'image/webp',
	'image/tiff',
	'image/bmp'
];

J
Joao Moreno 已提交
148
async function categorizeResourceByResolution(resources: Resource[]): Promise<{ merge: Resource[], resolved: Resource[], unresolved: Resource[], deletionConflicts: Resource[] }> {
149 150 151
	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 已提交
152
	const isAnyDeleted = (s: Resource) => s.type === Status.DELETED_BY_THEM || s.type === Status.DELETED_BY_US;
153 154 155
	const possibleUnresolved = merge.filter(isBothAddedOrModified);
	const promises = possibleUnresolved.map(s => grep(s.resourceUri.fsPath, /^<{7}|^={7}|^>{7}/));
	const unresolvedBothModified = await Promise.all<boolean>(promises);
156
	const resolved = possibleUnresolved.filter((_s, i) => !unresolvedBothModified[i]);
J
Joao Moreno 已提交
157
	const deletionConflicts = merge.filter(s => isAnyDeleted(s));
158
	const unresolved = [
J
Joao Moreno 已提交
159
		...merge.filter(s => !isBothAddedOrModified(s) && !isAnyDeleted(s)),
160
		...possibleUnresolved.filter((_s, i) => unresolvedBothModified[i])
161 162
	];

J
Joao Moreno 已提交
163
	return { merge, resolved, unresolved, deletionConflicts };
164 165
}

J
Joao Moreno 已提交
166
function createCheckoutItems(repository: Repository): CheckoutItem[] {
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
	const config = workspace.getConfiguration('git');
	const checkoutType = config.get<string>('checkoutType') || 'all';
	const includeTags = checkoutType === 'all' || checkoutType === 'tags';
	const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

	const heads = repository.refs.filter(ref => ref.type === RefType.Head)
		.map(ref => new CheckoutItem(ref));
	const tags = (includeTags ? repository.refs.filter(ref => ref.type === RefType.Tag) : [])
		.map(ref => new CheckoutTagItem(ref));
	const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
		.map(ref => new CheckoutRemoteHeadItem(ref));

	return [...heads, ...tags, ...remoteHeads];
}

J
Joao Moreno 已提交
182
async function getBranchNameFromUser(): Promise<string> {
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
	const config = workspace.getConfiguration('git');
	const branchWhitespaceChar = config.get<string>('branchWhitespaceChar')!;
	const branchValidationRegex = config.get<string>('branchValidationRegex')!;
	const sanitize = (name: string) => name ?
		name.trim().replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar)
		: name;

	const result = await window.showInputBox({
		placeHolder: localize('branch name', "Branch name"),
		prompt: localize('provide branch name', "Please provide a branch name"),
		ignoreFocusOut: true,
		validateInput: (name: string) => {
			const validateName = new RegExp(branchValidationRegex);
			if (validateName.test(sanitize(name))) {
				return null;
			}

			return localize('branch name format invalid', "Branch name needs to match regex: {0}", branchValidationRegex);
		}
	});

	return sanitize(result || '');
}

207 208 209 210 211 212 213 214 215
enum PushType {
	Push,
	PushTo,
	PushTags,
}

interface PushOptions {
	pushType: PushType;
	forcePush?: boolean;
J
Joao Moreno 已提交
216
	silent?: boolean;
217 218
}

J
Joao Moreno 已提交
219
export class CommandCenter {
J
Joao Moreno 已提交
220 221

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

J
Joao Moreno 已提交
223
	constructor(
J
Joao Moreno 已提交
224
		private git: Git,
J
Joao Moreno 已提交
225
		private model: Model,
J
Joao Moreno 已提交
226 227
		private outputChannel: OutputChannel,
		private telemetryReporter: TelemetryReporter
J
Joao Moreno 已提交
228
	) {
J
Joao Moreno 已提交
229 230
		this.disposables = Commands.map(({ commandId, key, method, options }) => {
			const command = this.createCommand(commandId, key, method, options);
231

J
Joao Moreno 已提交
232
			if (options.diff) {
J
Joao Moreno 已提交
233 234 235 236 237
				return commands.registerDiffInformationCommand(commandId, command);
			} else {
				return commands.registerCommand(commandId, command);
			}
		});
J
Joao Moreno 已提交
238 239
	}

J
Joao Moreno 已提交
240 241
	@command('git.refresh', { repository: true })
	async refresh(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
242
		await repository.status();
J
Joao Moreno 已提交
243
	}
J
Joao Moreno 已提交
244

J
Joao Moreno 已提交
245 246
	@command('git.openResource')
	async openResource(resource: Resource): Promise<void> {
J
Joao Moreno 已提交
247 248 249 250 251 252 253 254 255 256 257 258 259 260
		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 已提交
261 262
	}

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

266 267 268 269
		try {
			stat = await new Promise<Stats>((c, e) => lstat(resource.resourceUri.fsPath, (err, stat) => err ? e(err) : c(stat)));
		} catch (err) {
			// noop
270
		}
271

272 273 274 275
		let left: Uri | undefined;
		let right: Uri | undefined;

		if (stat && stat.isDirectory()) {
276 277 278 279
			const repository = this.model.getRepositoryForSubmodule(resource.resourceUri);

			if (repository) {
				right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root });
280 281
			}
		} else {
J
Joao Moreno 已提交
282 283 284 285
			if (resource.type !== Status.DELETED_BY_THEM) {
				left = await this.getLeftResource(resource);
			}

286 287
			right = await this.getRightResource(resource);
		}
288

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

J
Joao Moreno 已提交
291 292 293 294 295
		if (!right) {
			// TODO
			console.error('oh no');
			return;
		}
J
Joao Moreno 已提交
296

J
Joao Moreno 已提交
297
		const opts: TextDocumentShowOptions = {
J
Joao Moreno 已提交
298 299
			preserveFocus,
			preview,
300
			viewColumn: ViewColumn.Active
J
Joao Moreno 已提交
301 302
		};

J
Joao Moreno 已提交
303 304
		const activeTextEditor = window.activeTextEditor;

305 306 307
		// 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 已提交
308 309 310
			opts.selection = activeTextEditor.selection;
		}

311
		if (!left) {
J
Joao Moreno 已提交
312
			await commands.executeCommand<void>('vscode.open', right, opts, title);
J
Joao Moreno 已提交
313 314 315 316
		} else {
			await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
		}
	}
J
Joao Moreno 已提交
317

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

J
Joao Moreno 已提交
321 322 323
		if (!repository) {
			return toGitUri(uri, ref);
		}
J
Joao Moreno 已提交
324

J
Joao Moreno 已提交
325
		try {
J
Joao Moreno 已提交
326 327 328
			let gitRef = ref;

			if (gitRef === '~') {
J
Joao Moreno 已提交
329
				const uriString = uri.toString();
J
Joao Moreno 已提交
330
				const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
J
Joao Moreno 已提交
331
				gitRef = indexStatus ? '' : 'HEAD';
J
Joao Moreno 已提交
332 333
			}

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

J
Joao Moreno 已提交
337 338 339
			if (mimetype === 'text/plain') {
				return toGitUri(uri, ref);
			}
340

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

J
Joao Moreno 已提交
345
			if (ImageMimetypes.indexOf(mimetype) > -1) {
J
Joao Moreno 已提交
346 347
				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 已提交
348 349
			}

J
Joao Moreno 已提交
350
			return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`);
J
Joao Moreno 已提交
351 352 353 354

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

J
Joao Moreno 已提交
357
	private async getLeftResource(resource: Resource): Promise<Uri | undefined> {
J
Joao Moreno 已提交
358 359 360
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
361
				return this.getURI(resource.original, 'HEAD');
J
Joao Moreno 已提交
362 363

			case Status.MODIFIED:
J
Joao Moreno 已提交
364
				return this.getURI(resource.resourceUri, '~');
365 366

			case Status.DELETED_BY_THEM:
J
Joao Moreno 已提交
367
				return this.getURI(resource.resourceUri, '');
J
Joao Moreno 已提交
368
		}
369
		return undefined;
J
Joao Moreno 已提交
370
	}
J
Joao Moreno 已提交
371

J
Joao Moreno 已提交
372
	private async getRightResource(resource: Resource): Promise<Uri | undefined> {
J
Joao Moreno 已提交
373 374 375 376 377
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_ADDED:
			case Status.INDEX_COPIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
378
				return this.getURI(resource.resourceUri, '');
J
Joao Moreno 已提交
379 380 381

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

J
Joao Moreno 已提交
384 385 386 387 388 389
			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 已提交
390 391 392
			case Status.MODIFIED:
			case Status.UNTRACKED:
			case Status.IGNORED:
393
			case Status.INTENT_TO_ADD:
J
Joao Moreno 已提交
394 395 396 397 398 399
				const repository = this.model.getRepository(resource.resourceUri);

				if (!repository) {
					return;
				}

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

J
Joao Moreno 已提交
403 404
				if (indexStatus && indexStatus.renameResourceUri) {
					return indexStatus.renameResourceUri;
J
Joao Moreno 已提交
405 406
				}

J
Joao Moreno 已提交
407
				return resource.resourceUri;
J
Joao Moreno 已提交
408

409
			case Status.BOTH_ADDED:
J
Joao Moreno 已提交
410
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
411
				return resource.resourceUri;
J
Joao Moreno 已提交
412
		}
413
		return undefined;
J
Joao Moreno 已提交
414 415 416
	}

	private getTitle(resource: Resource): string {
J
Joao Moreno 已提交
417
		const basename = path.basename(resource.resourceUri.fsPath);
J
Joao Moreno 已提交
418 419 420 421 422 423 424

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

			case Status.MODIFIED:
M
Marc Kassay 已提交
425 426
			case Status.BOTH_ADDED:
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
427
				return `${basename} (Working Tree)`;
J
Joao Moreno 已提交
428 429 430 431 432 433

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

			case Status.DELETED_BY_THEM:
				return `${basename} (Ours)`;
J
Joao Moreno 已提交
434 435 436 437 438
		}

		return '';
	}

J
Joao Moreno 已提交
439
	@command('git.clone')
440 441 442 443 444 445 446
	async clone(url?: string): Promise<void> {
		if (!url) {
			url = await window.showInputBox({
				prompt: localize('repourl', "Repository URL"),
				ignoreFocusOut: true
			});
		}
J
Joao Moreno 已提交
447 448

		if (!url) {
K
kieferrm 已提交
449
			/* __GDPR__
K
kieferrm 已提交
450 451 452 453
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
454 455
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' });
			return;
J
Joao Moreno 已提交
456 457
		}

458
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
459 460 461 462 463 464 465 466 467
		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 已提交
468 469
		});

J
Joao Moreno 已提交
470
		if (!uris || uris.length === 0) {
K
kieferrm 已提交
471
			/* __GDPR__
K
kieferrm 已提交
472 473 474 475
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
476 477
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' });
			return;
J
Joao Moreno 已提交
478 479
		}

J
Joao Moreno 已提交
480 481 482
		const uri = uris[0];
		const parentPath = uri.fsPath;

483
		try {
484 485 486 487 488
			const opts = {
				location: ProgressLocation.Notification,
				title: localize('cloning', "Cloning git repository '{0}'...", url),
				cancellable: true
			};
M
Maryam Archie 已提交
489

490 491
			const repositoryPath = await window.withProgress(
				opts,
J
Joao Moreno 已提交
492
				(_, token) => this.git.clone(url!, parentPath, token)
493
			);
494

495 496
			const choices = [];
			let message = localize('proposeopen', "Would you like to open the cloned repository?");
497
			const open = localize('openrepo', "Open Repository");
498 499 500 501 502 503 504 505 506
			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);
507 508

			const openFolder = result === open;
K
kieferrm 已提交
509
			/* __GDPR__
K
kieferrm 已提交
510 511 512 513 514
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
					"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
				}
			*/
515
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 });
516 517 518

			const uri = Uri.file(repositoryPath);

519
			if (openFolder) {
520 521 522
				commands.executeCommand('vscode.openFolder', uri);
			} else if (result === addToWorkspace) {
				workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
523
			}
524 525
		} catch (err) {
			if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
K
kieferrm 已提交
526
				/* __GDPR__
K
kieferrm 已提交
527 528 529 530
					"clone" : {
						"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
531
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
J
Joao Moreno 已提交
532 533
			} else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) {
				return;
534
			} else {
K
kieferrm 已提交
535
				/* __GDPR__
K
kieferrm 已提交
536 537 538 539
					"clone" : {
						"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
540
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
541
			}
J
Joao Moreno 已提交
542

543
			throw err;
J
Joao Moreno 已提交
544 545 546
		}
	}

J
Joao Moreno 已提交
547
	@command('git.init')
J
Joao Moreno 已提交
548
	async init(): Promise<void> {
J
Joao Moreno 已提交
549
		let repositoryPath: string | undefined = undefined;
J
Joao Moreno 已提交
550
		let askToOpen = true;
J
Joao Moreno 已提交
551

J
Joao Moreno 已提交
552
		if (workspace.workspaceFolders) {
J
Joao Moreno 已提交
553
			const placeHolder = localize('init', "Pick workspace folder to initialize git repo in");
J
Joao Moreno 已提交
554 555 556 557 558
			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 已提交
559 560 561 562
			const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });

			if (!item) {
				return;
J
Joao Moreno 已提交
563 564
			} else if (item.folder) {
				repositoryPath = item.folder.uri.fsPath;
J
Joao Moreno 已提交
565
				askToOpen = false;
J
Joao Moreno 已提交
566 567
			}
		}
J
Joao Moreno 已提交
568

J
Joao Moreno 已提交
569
		if (!repositoryPath) {
J
Joao Moreno 已提交
570 571 572 573 574 575 576 577 578 579 580 581
			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 已提交
582

J
Joao Moreno 已提交
583
			if (!result || result.length === 0) {
J
Joao Moreno 已提交
584 585
				return;
			}
J
Joao Moreno 已提交
586 587 588 589 590 591 592 593 594 595 596 597

			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 已提交
598
			repositoryPath = uri.fsPath;
J
Joao Moreno 已提交
599 600 601 602

			if (workspace.workspaceFolders && workspace.workspaceFolders.some(w => w.uri.toString() === uri.toString())) {
				askToOpen = false;
			}
J
Joao Moreno 已提交
603 604
		}

J
Joao Moreno 已提交
605
		await this.git.init(repositoryPath);
I
Ivan Sučić 已提交
606 607

		const choices = [];
J
Joao Moreno 已提交
608 609
		let message = localize('proposeopen init', "Would you like to open the initialized repository?");
		const open = localize('openrepo', "Open Repository");
I
Ivan Sučić 已提交
610
		choices.push(open);
J
Joao Moreno 已提交
611

J
Joao Moreno 已提交
612 613 614 615
		if (!askToOpen) {
			return;
		}

J
Joao Moreno 已提交
616 617 618 619 620
		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ć 已提交
621 622

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

J
Joao Moreno 已提交
625 626 627 628 629 630
		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ć 已提交
631
		}
J
Joao Moreno 已提交
632 633
	}

J
Joao Moreno 已提交
634 635
	@command('git.openRepository', { repository: false })
	async openRepository(path?: string): Promise<void> {
636
		if (!path) {
J
Joao Moreno 已提交
637 638 639 640 641 642
			const result = await window.showOpenDialog({
				canSelectFiles: false,
				canSelectFolders: true,
				canSelectMany: false,
				defaultUri: Uri.file(os.homedir()),
				openLabel: localize('open repo', "Open Repository")
643 644
			});

J
Joao Moreno 已提交
645
			if (!result || result.length === 0) {
646 647 648
				return;
			}

J
Joao Moreno 已提交
649
			path = result[0].fsPath;
650
		}
J
Joao Moreno 已提交
651 652

		await this.model.openRepository(path);
653 654
	}

J
Joao Moreno 已提交
655 656 657 658 659
	@command('git.close', { repository: true })
	async close(repository: Repository): Promise<void> {
		this.model.close(repository);
	}

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

664
		let uris: Uri[] | undefined;
665 666 667

		if (arg instanceof Uri) {
			if (arg.scheme === 'git') {
668
				uris = [Uri.file(fromGitUri(arg).path)];
669
			} else if (arg.scheme === 'file') {
670
				uris = [arg];
671 672 673 674 675 676
			}
		} else {
			let resource = arg;

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

			if (resource) {
J
Joao Moreno 已提交
681 682 683 684
				const resources = ([resource, ...resourceStates] as Resource[])
					.filter(r => r.type !== Status.DELETED && r.type !== Status.INDEX_DELETED);

				uris = resources.map(r => r.resourceUri);
685
			}
J
Joao Moreno 已提交
686 687
		}

688
		if (!uris) {
J
Joao Moreno 已提交
689
			return;
J
Joao Moreno 已提交
690 691
		}

692 693 694
		const activeTextEditor = window.activeTextEditor;
		for (const uri of uris) {
			const opts: TextDocumentShowOptions = {
J
Joao Moreno 已提交
695
				preserveFocus,
J
Joao Moreno 已提交
696
				preview: false,
697
				viewColumn: ViewColumn.Active
698 699
			};

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

702 703 704
			// 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 已提交
705
				// preserve not only selection but also visible range
B
Benjamin Pasero 已提交
706
				opts.selection = activeTextEditor.selection;
E
Eric Gang 已提交
707 708 709 710 711
				const previousVisibleRanges = activeTextEditor.visibleRanges;
				const editor = await window.showTextDocument(document, opts);
				editor.revealRange(previousVisibleRanges[0]);
			} else {
				await window.showTextDocument(document, opts);
B
Benjamin Pasero 已提交
712
			}
J
Joao Moreno 已提交
713
		}
J
Joao Moreno 已提交
714 715
	}

J
Joao Moreno 已提交
716 717 718 719 720
	@command('git.openFile2')
	async openFile2(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
		this.openFile(arg, ...resourceStates);
	}

J
Joao Moreno 已提交
721 722
	@command('git.openHEADFile')
	async openHEADFile(arg?: Resource | Uri): Promise<void> {
D
Duroktar 已提交
723
		let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
724
		const preview = !(arg instanceof Resource);
D
Duroktar 已提交
725 726 727 728

		if (arg instanceof Resource) {
			resource = arg;
		} else if (arg instanceof Uri) {
729
			resource = this.getSCMResource(arg);
D
Duroktar 已提交
730
		} else {
731
			resource = this.getSCMResource();
D
Duroktar 已提交
732 733 734 735 736 737
		}

		if (!resource) {
			return;
		}

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

J
Joao Moreno 已提交
740 741 742
		if (!HEAD) {
			window.showWarningMessage(localize('HEAD not available', "HEAD version of '{0}' is not available.", path.basename(resource.resourceUri.fsPath)));
			return;
D
Duroktar 已提交
743
		}
J
Joao Moreno 已提交
744

J
Joao Moreno 已提交
745 746 747 748 749
		const opts: TextDocumentShowOptions = {
			preview
		};

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

J
Joao Moreno 已提交
752 753
	@command('git.openChange')
	async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
754
		const preserveFocus = arg instanceof Resource;
J
Joao Moreno 已提交
755 756
		const preview = !(arg instanceof Resource);

J
Joao 已提交
757
		const preserveSelection = arg instanceof Uri || !arg;
758
		let resources: Resource[] | undefined = undefined;
759

760
		if (arg instanceof Uri) {
761
			const resource = this.getSCMResource(arg);
762 763 764
			if (resource !== undefined) {
				resources = [resource];
			}
765
		} else {
766
			let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
767

768 769 770
			if (arg instanceof Resource) {
				resource = arg;
			} else {
771
				resource = this.getSCMResource();
772 773 774 775 776
			}

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

779
		if (!resources) {
J
Joao Moreno 已提交
780
			return;
J
Joao Moreno 已提交
781
		}
J
Joao Moreno 已提交
782

783
		for (const resource of resources) {
J
Joao 已提交
784
			await this._openResource(resource, preview, preserveFocus, preserveSelection);
785
		}
J
Joao Moreno 已提交
786 787
	}

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

792 793
		resourceStates = resourceStates.filter(s => !!s);

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

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

799 800 801 802 803 804 805
			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

806
		const selection = resourceStates.filter(s => s instanceof Resource) as Resource[];
J
Joao Moreno 已提交
807
		const { resolved, unresolved, deletionConflicts } = await categorizeResourceByResolution(selection);
808 809 810 811 812

		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));
813

814 815 816 817 818 819 820 821
			const yes = localize('yes', "Yes");
			const pick = await window.showWarningMessage(message, { modal: true }, yes);

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

J
Joao Moreno 已提交
822 823 824 825 826 827 828 829 830 831 832 833 834 835
		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 已提交
836
		const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree);
837
		const scmResources = [...workingTree, ...resolved, ...unresolved];
838

J
Joao Moreno 已提交
839
		this.outputChannel.appendLine(`git.stage.scmResources ${scmResources.length}`);
840
		if (!scmResources.length) {
J
Joao Moreno 已提交
841 842
			return;
		}
J
Joao Moreno 已提交
843

844
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
845
		await this.runByRepository(resources, async (repository, resources) => repository.add(resources));
J
Joao Moreno 已提交
846 847
	}

J
Joao Moreno 已提交
848 849
	@command('git.stageAll', { repository: true })
	async stageAll(repository: Repository): Promise<void> {
850
		const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[];
J
Joao Moreno 已提交
851 852 853 854 855 856 857 858 859 860 861 862 863
		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;
		}
864 865 866

		if (unresolved.length > 0) {
			const message = unresolved.length > 1
J
Joao Moreno 已提交
867 868
				? 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));
869 870 871 872 873 874 875 876 877

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

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

J
Joao Moreno 已提交
878
		await repository.add([]);
J
Joao Moreno 已提交
879 880
	}

J
Joao Moreno 已提交
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
	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 已提交
916
	@command('git.stageChange')
J
Joao Moreno 已提交
917 918 919 920 921 922 923 924
	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 已提交
925 926
	}

J
Joao Moreno 已提交
927
	@command('git.stageSelectedRanges', { diff: true })
J
Joao Moreno 已提交
928
	async stageSelectedChanges(changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
929 930 931 932 933 934 935
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
J
Joao Moreno 已提交
936 937 938 939
		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 已提交
940

J
Joao Moreno 已提交
941
		if (!selectedChanges.length) {
J
Joao Moreno 已提交
942 943 944
			return;
		}

J
Joao Moreno 已提交
945 946
		await this._stageChanges(textEditor, selectedChanges);
	}
J
Joao Moreno 已提交
947

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

J
Joao Moreno 已提交
952
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
953 954 955
			return;
		}

J
Joao Moreno 已提交
956
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
957
		const originalDocument = await workspace.openTextDocument(originalUri);
J
Joao Moreno 已提交
958
		const result = applyLineChanges(originalDocument, modifiedDocument, changes);
J
Joao Moreno 已提交
959

J
Joao Moreno 已提交
960
		await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
J
Joao Moreno 已提交
961
	}
J
Joao Moreno 已提交
962

J
Joao Moreno 已提交
963
	@command('git.revertChange')
J
Joao Moreno 已提交
964 965 966 967
	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 已提交
968 969 970
			return;
		}

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

J
Joao Moreno 已提交
974
	@command('git.revertSelectedRanges', { diff: true })
J
Joao Moreno 已提交
975
	async revertSelectedRanges(changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
976 977 978 979 980 981 982
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
J
Joao Moreno 已提交
983 984
		const selections = textEditor.selections;
		const selectedChanges = changes.filter(change => {
J
Joao Moreno 已提交
985
			const modifiedRange = getModifiedRange(modifiedDocument, change);
J
Joao Moreno 已提交
986 987 988 989
			return selections.every(selection => !selection.intersection(modifiedRange));
		});

		if (selectedChanges.length === changes.length) {
J
Joao Moreno 已提交
990 991 992
			return;
		}

J
Joao Moreno 已提交
993 994
		await this._revertChanges(textEditor, selectedChanges);
	}
J
Joao Moreno 已提交
995

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

J
Joao Moreno 已提交
1000
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
1001 1002 1003
			return;
		}

J
Joao Moreno 已提交
1004 1005
		const originalUri = toGitUri(modifiedUri, '~');
		const originalDocument = await workspace.openTextDocument(originalUri);
1006 1007
		const selectionsBeforeRevert = textEditor.selections;
		const visibleRangesBeforeRevert = textEditor.visibleRanges;
J
Joao Moreno 已提交
1008
		const result = applyLineChanges(originalDocument, modifiedDocument, changes);
1009

J
Joao Moreno 已提交
1010 1011 1012
		const edit = new WorkspaceEdit();
		edit.replace(modifiedUri, new Range(new Position(0, 0), modifiedDocument.lineAt(modifiedDocument.lineCount - 1).range.end), result);
		workspace.applyEdit(edit);
1013

J
Joao Moreno 已提交
1014 1015
		await modifiedDocument.save();

1016 1017
		textEditor.selections = selectionsBeforeRevert;
		textEditor.revealRange(visibleRangesBeforeRevert[0]);
J
Joao Moreno 已提交
1018 1019
	}

J
Joao Moreno 已提交
1020 1021
	@command('git.unstage')
	async unstage(...resourceStates: SourceControlResourceState[]): Promise<void> {
1022 1023
		resourceStates = resourceStates.filter(s => !!s);

J
Joao Moreno 已提交
1024
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
1025
			const resource = this.getSCMResource();
1026 1027 1028 1029 1030 1031 1032 1033

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

1037
		if (!scmResources.length) {
J
Joao Moreno 已提交
1038 1039 1040
			return;
		}

1041
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
1042
		await this.runByRepository(resources, async (repository, resources) => repository.revert(resources));
J
Joao Moreno 已提交
1043 1044
	}

J
Joao Moreno 已提交
1045 1046
	@command('git.unstageAll', { repository: true })
	async unstageAll(repository: Repository): Promise<void> {
1047
		await repository.revert([]);
J
Joao Moreno 已提交
1048
	}
J
Joao Moreno 已提交
1049

J
Joao Moreno 已提交
1050 1051
	@command('git.unstageSelectedRanges', { diff: true })
	async unstageSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
1052 1053 1054 1055 1056 1057 1058 1059 1060
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

1061 1062 1063 1064 1065 1066 1067
		if (modifiedUri.scheme !== 'git') {
			return;
		}

		const { ref } = fromGitUri(modifiedUri);

		if (ref !== '') {
J
Joao Moreno 已提交
1068 1069 1070
			return;
		}

J
Joao Moreno 已提交
1071
		const originalUri = toGitUri(modifiedUri, 'HEAD');
J
Joao Moreno 已提交
1072
		const originalDocument = await workspace.openTextDocument(originalUri);
1073 1074 1075 1076
		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 已提交
1077 1078 1079 1080 1081

		if (!selectedDiffs.length) {
			return;
		}

1082 1083
		const invertedDiffs = selectedDiffs.map(invertLineChange);
		const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs);
J
Joao Moreno 已提交
1084

J
Joao Moreno 已提交
1085
		await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
J
Joao Moreno 已提交
1086 1087
	}

J
Joao Moreno 已提交
1088 1089
	@command('git.clean')
	async clean(...resourceStates: SourceControlResourceState[]): Promise<void> {
1090 1091
		resourceStates = resourceStates.filter(s => !!s);

J
Joao Moreno 已提交
1092
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
1093
			const resource = this.getSCMResource();
1094 1095 1096 1097 1098 1099 1100 1101

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

J
Joao Moreno 已提交
1105
		if (!scmResources.length) {
J
Joao Moreno 已提交
1106 1107
			return;
		}
J
Joao Moreno 已提交
1108

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

J
Joao Moreno 已提交
1113
		if (scmResources.length === 1) {
J
Joao Moreno 已提交
1114
			if (untrackedCount > 0) {
J
Joao Moreno 已提交
1115
				message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(scmResources[0].resourceUri.fsPath));
J
Joao Moreno 已提交
1116 1117
				yes = localize('delete file', "Delete file");
			} else {
1118 1119 1120 1121 1122 1123
				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 已提交
1124 1125
			}
		} else {
1126 1127 1128 1129 1130 1131
			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 已提交
1132 1133 1134 1135 1136

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

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

J
Joao Moreno 已提交
1140 1141 1142 1143
		if (pick !== yes) {
			return;
		}

J
Joao Moreno 已提交
1144
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
1145
		await this.runByRepository(resources, async (repository, resources) => repository.clean(resources));
J
Joao Moreno 已提交
1146
	}
J
Joao Moreno 已提交
1147

J
Joao Moreno 已提交
1148 1149
	@command('git.cleanAll', { repository: true })
	async cleanAll(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1150
		let resources = repository.workingTreeGroup.resourceStates;
J
Joao Moreno 已提交
1151 1152 1153 1154 1155

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

J
Joao Moreno 已提交
1156 1157
		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 已提交
1158

J
Joao Moreno 已提交
1159 1160 1161 1162 1163 1164
		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 已提交
1165
				: localize('discardAll', "Discard All {0} Files", resources.length);
J
Joao Moreno 已提交
1166
			const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
1167

J
Joao Moreno 已提交
1168
			if (pick !== yes) {
J
Joao Moreno 已提交
1169 1170 1171
				return;
			}

J
Joao 已提交
1172
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
1173 1174 1175 1176 1177 1178 1179 1180
			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 已提交
1181 1182
			}

J
Joao 已提交
1183
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
1184 1185 1186 1187
		} 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 已提交
1188

J
Joao Moreno 已提交
1189 1190 1191
			if (pick !== yes) {
				return;
			}
J
Joao Moreno 已提交
1192

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

J
Joao Moreno 已提交
1195 1196 1197 1198
		} 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 已提交
1199

J
Joao Moreno 已提交
1200 1201 1202 1203 1204
			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 已提交
1205

J
Joao Moreno 已提交
1206
			const yesAll = localize('discardAll', "Discard All {0} Files", resources.length);
J
Joao Moreno 已提交
1207 1208 1209 1210 1211 1212 1213 1214
			const pick = await window.showWarningMessage(message, { modal: true }, yesTracked, yesAll);

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

J
Joao 已提交
1215
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
1216
		}
J
Joao Moreno 已提交
1217 1218
	}

J
Joao Moreno 已提交
1219
	private async smartCommit(
J
Joao Moreno 已提交
1220
		repository: Repository,
1221
		getCommitMessage: () => Promise<string | undefined>,
J
Joao Moreno 已提交
1222 1223
		opts?: CommitOptions
	): Promise<boolean> {
1224
		const config = workspace.getConfiguration('git', Uri.file(repository.root));
J
Joao Moreno 已提交
1225 1226 1227 1228 1229 1230 1231 1232
		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 已提交
1233 1234
					? 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 已提交
1235 1236 1237 1238 1239 1240 1241 1242 1243 1244
				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
				}
1245 1246 1247
			}
		}

1248
		const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
1249
		const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
J
Joao Moreno 已提交
1250 1251
		const noStagedChanges = repository.indexGroup.resourceStates.length === 0;
		const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
1252 1253

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

J
Joao Moreno 已提交
1256 1257
			// 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?");
1258 1259 1260 1261 1262
			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 已提交
1263 1264 1265
				config.update('enableSmartCommit', true, true);
			} else if (pick !== yes) {
				return false; // do not commit on cancel
1266 1267 1268
			}
		}

J
Joao Moreno 已提交
1269
		if (!opts) {
1270
			opts = { all: noStagedChanges };
J
Joao Moreno 已提交
1271 1272
		} else if (!opts.all && noStagedChanges) {
			opts = { ...opts, all: true };
J
Joao Moreno 已提交
1273 1274
		}

1275 1276 1277
		// enable signing of commits if configurated
		opts.signCommit = enableCommitSigning;

1278 1279 1280 1281
		if (config.get<boolean>('alwaysSignOff')) {
			opts.signoff = true;
		}

J
Joao Moreno 已提交
1282
		if (
1283
			(
J
Joao Moreno 已提交
1284 1285 1286 1287 1288
				// no changes
				(noStagedChanges && noUnstagedChanges)
				// or no staged changes and not `all`
				|| (!opts.all && noStagedChanges)
			)
1289
			&& !opts.empty
J
Joao Moreno 已提交
1290
		) {
J
Joao Moreno 已提交
1291 1292 1293 1294
			window.showInformationMessage(localize('no changes', "There are no changes to commit."));
			return false;
		}

J
Joao Moreno 已提交
1295
		const message = await getCommitMessage();
J
Joao Moreno 已提交
1296 1297 1298 1299 1300

		if (!message) {
			return false;
		}

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

J
Joao Moreno 已提交
1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313
		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 已提交
1314 1315 1316
		return true;
	}

J
Joao Moreno 已提交
1317
	private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> {
J
Joao Moreno 已提交
1318
		const message = repository.inputBox.value;
J
Joao Moreno 已提交
1319
		const getCommitMessage = async () => {
J
Joao Moreno 已提交
1320 1321 1322 1323
			if (message) {
				return message;
			}

1324 1325 1326 1327 1328 1329
			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 已提交
1330
			return await window.showInputBox({
1331
				value,
J
Joao Moreno 已提交
1332
				placeHolder: localize('commit message', "Commit message"),
J
Joao Moreno 已提交
1333 1334
				prompt: localize('provide commit message', "Please provide a commit message"),
				ignoreFocusOut: true
J
Joao Moreno 已提交
1335
			});
J
Joao Moreno 已提交
1336 1337
		};

J
Joao Moreno 已提交
1338
		const didCommit = await this.smartCommit(repository, getCommitMessage, opts);
J
Joao Moreno 已提交
1339 1340

		if (message && didCommit) {
J
Joao Moreno 已提交
1341
			repository.inputBox.value = await repository.getCommitTemplate();
J
Joao Moreno 已提交
1342
		}
J
Joao Moreno 已提交
1343 1344
	}

J
Joao Moreno 已提交
1345
	@command('git.commit', { repository: true })
J
Joao Moreno 已提交
1346 1347
	async commit(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository);
J
Joao Moreno 已提交
1348 1349
	}

J
Joao Moreno 已提交
1350
	@command('git.commitWithInput', { repository: true })
J
Joao Moreno 已提交
1351
	async commitWithInput(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1352
		if (!repository.inputBox.value) {
J
Joao Moreno 已提交
1353 1354 1355
			return;
		}

J
Joao Moreno 已提交
1356
		const didCommit = await this.smartCommit(repository, async () => repository.inputBox.value);
J
Joao Moreno 已提交
1357 1358

		if (didCommit) {
J
Joao Moreno 已提交
1359
			repository.inputBox.value = await repository.getCommitTemplate();
J
Joao Moreno 已提交
1360
		}
J
Joao Moreno 已提交
1361 1362
	}

J
Joao Moreno 已提交
1363
	@command('git.commitStaged', { repository: true })
J
Joao Moreno 已提交
1364 1365
	async commitStaged(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: false });
J
Joao Moreno 已提交
1366 1367
	}

J
Joao Moreno 已提交
1368
	@command('git.commitStagedSigned', { repository: true })
J
Joao Moreno 已提交
1369 1370
	async commitStagedSigned(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: false, signoff: true });
J
Joao Moreno 已提交
1371 1372
	}

J
Joao Moreno 已提交
1373
	@command('git.commitStagedAmend', { repository: true })
J
Joao Moreno 已提交
1374
	async commitStagedAmend(repository: Repository): Promise<void> {
1375
		await this.commitWithAnyInput(repository, { all: false, amend: true });
K
Krzysztof Cieślak 已提交
1376 1377
	}

J
Joao Moreno 已提交
1378
	@command('git.commitAll', { repository: true })
J
Joao Moreno 已提交
1379 1380
	async commitAll(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true });
J
Joao Moreno 已提交
1381 1382
	}

J
Joao Moreno 已提交
1383
	@command('git.commitAllSigned', { repository: true })
J
Joao Moreno 已提交
1384 1385
	async commitAllSigned(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true, signoff: true });
J
Joao Moreno 已提交
1386 1387
	}

J
Joao Moreno 已提交
1388
	@command('git.commitAllAmend', { repository: true })
J
Joao Moreno 已提交
1389 1390
	async commitAllAmend(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true, amend: true });
K
Krzysztof Cieślak 已提交
1391 1392
	}

J
Joao Moreno 已提交
1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414
	@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 已提交
1415 1416 1417 1418 1419
	@command('git.restoreCommitTemplate', { repository: true })
	async restoreCommitTemplate(repository: Repository): Promise<void> {
		repository.inputBox.value = await repository.getCommitTemplate();
	}

J
Joao Moreno 已提交
1420
	@command('git.undoCommit', { repository: true })
J
Joao Moreno 已提交
1421 1422
	async undoCommit(repository: Repository): Promise<void> {
		const HEAD = repository.HEAD;
J
Joao Moreno 已提交
1423 1424

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

J
Joao Moreno 已提交
1429
		const commit = await repository.getCommit('HEAD');
J
Joao Moreno 已提交
1430 1431

		if (commit.parents.length > 0) {
1432 1433 1434 1435 1436
			await repository.reset('HEAD~');
		} else {
			await repository.deleteRef('HEAD');
			await this.unstageAll(repository);
		}
J
Joao Moreno 已提交
1437

J
Joao Moreno 已提交
1438
		repository.inputBox.value = commit.message;
J
Joao Moreno 已提交
1439 1440
	}

J
Joao Moreno 已提交
1441
	@command('git.checkout', { repository: true })
1442
	async checkout(repository: Repository, treeish: string): Promise<boolean> {
J
Joao Moreno 已提交
1443
		if (typeof treeish === 'string') {
1444 1445
			await repository.checkout(treeish);
			return true;
J
Joao Moreno 已提交
1446
		}
J
Joao Moreno 已提交
1447
		const createBranch = new CreateBranchItem(this);
J
Joao Moreno 已提交
1448

J
Joao Moreno 已提交
1449
		const picks = [createBranch, ...createCheckoutItems(repository)];
1450
		const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout');
J
Joao Moreno 已提交
1451
		const choice = await window.showQuickPick(picks, { placeHolder });
J
Joao Moreno 已提交
1452 1453

		if (!choice) {
1454
			return false;
J
Joao Moreno 已提交
1455 1456
		}

J
Joao Moreno 已提交
1457
		await choice.run(repository);
1458
		return true;
J
Joao Moreno 已提交
1459 1460
	}

J
Joao Moreno 已提交
1461
	@command('git.branch', { repository: true })
J
Joao Moreno 已提交
1462 1463 1464 1465
	async branch(repository: Repository): Promise<void> {
		const picks = [new HEADItem(repository), ...createCheckoutItems(repository)];
		const placeHolder = localize('select a ref to create a new branch from', 'Select a ref to create a new branch from');
		const target = await window.showQuickPick(picks, { placeHolder });
J
Joao Moreno 已提交
1466

J
Joao Moreno 已提交
1467
		if (!target) {
1468 1469
			return;
		}
J
Joao Moreno 已提交
1470

J
Joao Moreno 已提交
1471
		const name = await getBranchNameFromUser();
J
Joao Moreno 已提交
1472

J
Joao Moreno 已提交
1473
		if (!name) {
J
Joao Moreno 已提交
1474 1475
			return;
		}
J
Joao Moreno 已提交
1476

J
Joao Moreno 已提交
1477
		await repository.branch(name, true, target.label);
J
Joao Moreno 已提交
1478 1479
	}

J
Joao Moreno 已提交
1480
	@command('git.deleteBranch', { repository: true })
J
Joao Moreno 已提交
1481
	async deleteBranch(repository: Repository, name: string, force?: boolean): Promise<void> {
1482 1483
		let run: (force?: boolean) => Promise<void>;
		if (typeof name === 'string') {
J
Joao Moreno 已提交
1484
			run = force => repository.deleteBranch(name, force);
1485
		} else {
J
Joao Moreno 已提交
1486 1487
			const currentHead = repository.HEAD && repository.HEAD.name;
			const heads = repository.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead)
1488
				.map(ref => new BranchDeleteItem(ref));
M
Maik Riechert 已提交
1489

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

M
Maik Riechert 已提交
1493
			if (!choice || !choice.branchName) {
1494 1495
				return;
			}
M
Maik Riechert 已提交
1496
			name = choice.branchName;
J
Joao Moreno 已提交
1497
			run = force => choice.run(repository, force);
M
Maik Riechert 已提交
1498 1499
		}

1500 1501 1502 1503 1504 1505 1506 1507 1508
		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");
1509
			const pick = await window.showWarningMessage(message, { modal: true }, yes);
1510 1511 1512 1513 1514

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

1517 1518
	@command('git.renameBranch', { repository: true })
	async renameBranch(repository: Repository): Promise<void> {
J
Justin Horner 已提交
1519 1520 1521 1522 1523 1524 1525 1526
		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 {
1527
			await repository.renameBranch(name);
J
Justin Horner 已提交
1528 1529 1530
		} catch (err) {
			switch (err.gitErrorCode) {
				case GitErrorCodes.InvalidBranchName:
1531 1532
					window.showErrorMessage(localize('invalid branch name', 'Invalid branch name'));
					return;
J
Justin Horner 已提交
1533
				case GitErrorCodes.BranchAlreadyExists:
J
Joao Moreno 已提交
1534
					window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", name));
1535 1536 1537
					return;
				default:
					throw err;
J
Justin Horner 已提交
1538 1539 1540 1541
			}
		}
	}

J
Joao Moreno 已提交
1542
	@command('git.merge', { repository: true })
J
Joao Moreno 已提交
1543
	async merge(repository: Repository): Promise<void> {
1544 1545 1546 1547
		const config = workspace.getConfiguration('git');
		const checkoutType = config.get<string>('checkoutType') || 'all';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
1548
		const heads = repository.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
1549 1550
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
1551

J
Joao Moreno 已提交
1552
		const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
1553 1554
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
1555 1556

		const picks = [...heads, ...remoteHeads];
1557 1558
		const placeHolder = localize('select a branch to merge from', 'Select a branch to merge from');
		const choice = await window.showQuickPick<MergeItem>(picks, { placeHolder });
1559 1560 1561 1562 1563

		if (!choice) {
			return;
		}

1564
		await choice.run(repository);
1565 1566
	}

J
Joao Moreno 已提交
1567
	@command('git.createTag', { repository: true })
J
Joao Moreno 已提交
1568
	async createTag(repository: Repository): Promise<void> {
1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580
		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 已提交
1581
			prompt: localize('provide tag message', "Please provide a message to annotate the tag"),
1582 1583 1584 1585 1586
			ignoreFocusOut: true
		});

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

J
Joao Moreno 已提交
1590 1591 1592 1593 1594 1595 1596
	@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 已提交
1597
		await repository.fetchDefault();
J
Joao Moreno 已提交
1598 1599
	}

R
Ryan Scott 已提交
1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610
	@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 已提交
1611 1612 1613 1614 1615 1616 1617 1618 1619 1620
	@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 已提交
1621
	@command('git.pullFrom', { repository: true })
J
Joao Moreno 已提交
1622 1623
	async pullFrom(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
1624 1625 1626 1627 1628 1629

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

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

D
Dozed12 已提交
1634
		if (!remotePick) {
1635 1636 1637
			return;
		}

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

D
Dozed12 已提交
1644
		if (!branchPick) {
1645 1646 1647
			return;
		}

F
Francisco Moreira 已提交
1648 1649
		const remoteCharCnt = remotePick.label.length;

1650
		await repository.pullFrom(false, remotePick.label, branchPick.label.slice(remoteCharCnt + 1));
1651 1652
	}

J
Joao Moreno 已提交
1653
	@command('git.pull', { repository: true })
J
Joao Moreno 已提交
1654 1655
	async pull(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1656 1657 1658 1659 1660 1661

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

J
Joao Moreno 已提交
1662
		await repository.pull(repository.HEAD);
J
Joao Moreno 已提交
1663 1664
	}

J
Joao Moreno 已提交
1665
	@command('git.pullRebase', { repository: true })
J
Joao Moreno 已提交
1666 1667
	async pullRebase(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1668 1669 1670 1671 1672 1673

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

J
Joao Moreno 已提交
1674
		await repository.pullWithRebase(repository.HEAD);
J
Joao Moreno 已提交
1675 1676
	}

J
Joao Moreno 已提交
1677
	private async _push(repository: Repository, pushOptions: PushOptions) {
J
Joao Moreno 已提交
1678
		const remotes = repository.remotes;
1679

J
Joao Moreno 已提交
1680
		if (remotes.length === 0) {
J
Joao Moreno 已提交
1681 1682 1683
			if (!pushOptions.silent) {
				window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
			}
J
Joao Moreno 已提交
1684 1685
			return;
		}
1686

J
Joao Moreno 已提交
1687 1688 1689 1690 1691 1692
		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."));
1693 1694
				return;
			}
J
Joao Moreno 已提交
1695

J
Joao Moreno 已提交
1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709
			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 已提交
1710 1711
		}

1712 1713 1714 1715 1716 1717 1718
		if (pushOptions.pushType === PushType.PushTags) {
			await repository.pushTags(undefined, forcePushMode);

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

1719
		if (!repository.HEAD || !repository.HEAD.name) {
J
Joao Moreno 已提交
1720 1721 1722
			if (!pushOptions.silent) {
				window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
			}
1723 1724 1725
			return;
		}

1726 1727 1728 1729 1730 1731 1732
		if (pushOptions.pushType === PushType.Push) {
			try {
				await repository.push(repository.HEAD, forcePushMode);
			} catch (err) {
				if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
					throw err;
				}
1733

J
Joao Moreno 已提交
1734 1735 1736 1737
				if (pushOptions.silent) {
					return;
				}

1738 1739 1740 1741 1742 1743 1744 1745 1746 1747
				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 {
1748
			const branchName = repository.HEAD.name;
1749 1750 1751
			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 });
1752

1753 1754
			if (!pick) {
				return;
1755
			}
1756 1757

			await repository.pushTo(pick.label, branchName, undefined, forcePushMode);
1758
		}
J
Joao Moreno 已提交
1759 1760
	}

1761 1762
	@command('git.push', { repository: true })
	async push(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1763
		await this._push(repository, { pushType: PushType.Push });
1764
	}
1765

1766 1767
	@command('git.pushForce', { repository: true })
	async pushForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1768
		await this._push(repository, { pushType: PushType.Push, forcePush: true });
1769
	}
1770

1771 1772
	@command('git.pushWithTags', { repository: true })
	async pushWithTags(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1773
		await this._push(repository, { pushType: PushType.PushTags });
1774
	}
1775

1776 1777
	@command('git.pushWithTagsForce', { repository: true })
	async pushWithTagsForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1778
		await this._push(repository, { pushType: PushType.PushTags, forcePush: true });
1779 1780
	}

J
Joao Moreno 已提交
1781
	@command('git.pushTo', { repository: true })
J
Joao Moreno 已提交
1782
	async pushTo(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1783
		await this._push(repository, { pushType: PushType.PushTo });
1784
	}
J
Joao Moreno 已提交
1785

1786 1787
	@command('git.pushToForce', { repository: true })
	async pushToForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1788
		await this._push(repository, { pushType: PushType.PushTo, forcePush: true });
J
Joao Moreno 已提交
1789 1790
	}

J
Joao Moreno 已提交
1791
	private async _sync(repository: Repository, rebase: boolean): Promise<void> {
J
Joao Moreno 已提交
1792
		const HEAD = repository.HEAD;
J
Joao Moreno 已提交
1793 1794 1795 1796 1797

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

J
Joao Moreno 已提交
1798 1799 1800 1801
		const remoteName = HEAD.remote || HEAD.upstream.remote;
		const remote = repository.remotes.find(r => r.name === remoteName);
		const isReadonly = remote && remote.isReadOnly;

J
Joao Moreno 已提交
1802
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
1803
		const shouldPrompt = !isReadonly && config.get<boolean>('confirmSync') === true;
J
Joao Moreno 已提交
1804 1805

		if (shouldPrompt) {
J
Joao Moreno 已提交
1806
			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 已提交
1807
			const yes = localize('ok', "OK");
B
Benjamin Pasero 已提交
1808
			const neverAgain = localize('never again', "OK, Don't Show Again");
J
Joao Moreno 已提交
1809 1810 1811 1812 1813 1814 1815 1816 1817
			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 已提交
1818
		if (rebase) {
J
Joao Moreno 已提交
1819
			await repository.syncRebase(HEAD);
J
Joao Moreno 已提交
1820
		} else {
J
Joao Moreno 已提交
1821
			await repository.sync(HEAD);
J
Joao Moreno 已提交
1822 1823 1824 1825 1826 1827
		}
	}

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

J
Joao 已提交
1830 1831 1832 1833 1834 1835 1836 1837 1838
	@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 已提交
1839
			await repository.sync(HEAD);
J
Joao 已提交
1840 1841 1842
		}));
	}

1843
	@command('git.syncRebase', { repository: true })
J
Joao Moreno 已提交
1844 1845
	syncRebase(repository: Repository): Promise<void> {
		return this._sync(repository, true);
J
Joao Moreno 已提交
1846 1847
	}

J
Joao Moreno 已提交
1848
	@command('git.publish', { repository: true })
J
Joao Moreno 已提交
1849 1850
	async publish(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1851 1852 1853 1854 1855 1856

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

J
Joao Moreno 已提交
1857
		const branchName = repository.HEAD && repository.HEAD.name || '';
M
Markus Wolf 已提交
1858 1859 1860 1861 1862 1863
		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 已提交
1864 1865 1866 1867 1868

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
1869
		await repository.pushTo(choice, branchName, true);
J
Joao Moreno 已提交
1870 1871
	}

1872 1873
	@command('git.ignore')
	async ignore(...resourceStates: SourceControlResourceState[]): Promise<void> {
1874 1875
		resourceStates = resourceStates.filter(s => !!s);

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

1879
			if (!resource) {
J
Joao Moreno 已提交
1880 1881 1882
				return;
			}

1883
			resourceStates = [resource];
J
Joao Moreno 已提交
1884 1885
		}

1886
		const resources = resourceStates
J
Joao Moreno 已提交
1887 1888 1889
			.filter(s => s instanceof Resource)
			.map(r => r.resourceUri);

1890
		if (!resources.length) {
N
NKumar2 已提交
1891 1892 1893
			return;
		}

1894
		await this.runByRepository(resources, async (repository, resources) => repository.ignore(resources));
N
NKumar2 已提交
1895 1896
	}

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

1901
		if (noUnstagedChanges && noStagedChanges) {
K
Krzysztof Cieślak 已提交
1902 1903 1904
			window.showInformationMessage(localize('no changes stash', "There are no changes to stash."));
			return;
		}
J
Joao Moreno 已提交
1905

1906
		const message = await this.getStashMessage();
J
Joao Moreno 已提交
1907 1908 1909 1910 1911

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

J
Joao Moreno 已提交
1912
		await repository.createStash(message, includeUntracked);
K
Krzysztof Cieślak 已提交
1913 1914
	}

1915 1916 1917 1918 1919 1920 1921
	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 已提交
1922 1923 1924 1925 1926 1927 1928 1929 1930 1931
	@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 已提交
1932
	@command('git.stashPop', { repository: true })
J
Joao Moreno 已提交
1933
	async stashPop(repository: Repository): Promise<void> {
1934
		const placeHolder = localize('pick stash to pop', "Pick a stash to pop");
J
Joao Moreno 已提交
1935
		const stash = await this.pickStash(repository, placeHolder);
1936

J
Joao Moreno 已提交
1937
		if (!stash) {
1938 1939 1940
			return;
		}

J
Joao Moreno 已提交
1941
		await repository.popStash(stash.index);
1942 1943 1944 1945
	}

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

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

1953 1954 1955 1956 1957 1958
		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 已提交
1959
		const stash = await this.pickStash(repository, placeHolder);
K
Krzysztof Cieślak 已提交
1960

J
Joao Moreno 已提交
1961
		if (!stash) {
K
Krzysztof Cieślak 已提交
1962 1963
			return;
		}
J
Joao Moreno 已提交
1964

J
Joao Moreno 已提交
1965
		await repository.applyStash(stash.index);
K
Krzysztof Cieślak 已提交
1966 1967
	}

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

J
Joao Moreno 已提交
1972 1973
		if (stashes.length === 0) {
			window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
1974 1975 1976 1977 1978 1979
			return;
		}

		await repository.applyStash();
	}

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

J
Joao Moreno 已提交
1983 1984
		if (stashes.length === 0) {
			window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
K
Krzysztof Cieślak 已提交
1985 1986 1987
			return;
		}

J
Joao Moreno 已提交
1988 1989 1990
		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 已提交
1991
	}
K
Krzysztof Cieślak 已提交
1992

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

J
Joao Moreno 已提交
1997
			if (!options.repository) {
J
Joao Moreno 已提交
1998 1999
				result = Promise.resolve(method.apply(this, args));
			} else {
J
Joao Moreno 已提交
2000 2001
				// try to guess the repository based on the first argument
				const repository = this.model.getRepository(args[0]);
J
Joao Moreno 已提交
2002 2003 2004 2005 2006 2007 2008 2009 2010
				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();
				}
2011

J
Joao Moreno 已提交
2012
				result = repositoryPromise.then(repository => {
J
Joao Moreno 已提交
2013
					if (!repository) {
J
Joao 已提交
2014
						return Promise.resolve();
J
Joao Moreno 已提交
2015 2016
					}

J
Joao Moreno 已提交
2017
					return Promise.resolve(method.apply(this, [repository, ...args]));
J
Joao Moreno 已提交
2018
				});
J
Joao Moreno 已提交
2019 2020
			}

K
kieferrm 已提交
2021
			/* __GDPR__
K
kieferrm 已提交
2022 2023 2024 2025
				"git.command" : {
					"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
J
Joao Moreno 已提交
2026 2027
			this.telemetryReporter.sendTelemetryEvent('git.command', { command: id });

J
Joao Moreno 已提交
2028
			return result.catch(async err => {
J
Joao Moreno 已提交
2029
				const options: MessageOptions = {
2030
					modal: true
J
Joao Moreno 已提交
2031 2032
				};

J
Joao Moreno 已提交
2033
				let message: string;
2034
				let type: 'error' | 'warning' = 'error';
J
Joao Moreno 已提交
2035

J
Joao Moreno 已提交
2036 2037 2038 2039 2040
				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 已提交
2041
				switch (err.gitErrorCode) {
2042
					case GitErrorCodes.DirtyWorkTree:
J
Joao Moreno 已提交
2043 2044
						message = localize('clean repo', "Please clean your repository working tree before checkout.");
						break;
2045
					case GitErrorCodes.PushRejected:
J
Joao Moreno 已提交
2046
						message = localize('cant push', "Can't push refs to remote. Try running 'Pull' first to integrate your changes.");
2047
						break;
2048 2049 2050 2051 2052
					case GitErrorCodes.Conflict:
						message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing.");
						type = 'warning';
						options.modal = false;
						break;
J
Joao Moreno 已提交
2053 2054 2055 2056 2057
					case GitErrorCodes.StashConflict:
						message = localize('stash merge conflicts', "There were merge conflicts while applying the stash.");
						type = 'warning';
						options.modal = false;
						break;
2058 2059
					case GitErrorCodes.NoUserNameConfigured:
					case GitErrorCodes.NoUserEmailConfigured:
J
Joao Moreno 已提交
2060 2061
						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')));
2062
						break;
J
Joao Moreno 已提交
2063
					default:
2064 2065 2066
						const hint = (err.stderr || err.message || String(err))
							.replace(/^error: /mi, '')
							.replace(/^> husky.*$/mi, '')
J
Joao Moreno 已提交
2067
							.split(/[\r\n]/)
J
João Moreno 已提交
2068
							.filter((line: string) => !!line)
2069 2070 2071 2072 2073
						[0];

						message = hint
							? localize('git error details', "Git: {0}", hint)
							: localize('git error', "Git error");
J
Joao Moreno 已提交
2074 2075 2076 2077 2078 2079 2080 2081 2082

						break;
				}

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

J
Joao Moreno 已提交
2083 2084 2085 2086 2087 2088 2089
				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 已提交
2090

J
Joao Moreno 已提交
2091 2092 2093
					if (resultFn) {
						resultFn();
					}
J
Joao Moreno 已提交
2094 2095 2096
				}
			});
		};
2097 2098

		// patch this object, so people can call methods directly
2099
		(this as any)[key] = result;
2100 2101

		return result;
J
Joao Moreno 已提交
2102 2103
	}

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

J
Joao Moreno 已提交
2107
		this.outputChannel.appendLine(`git.getSCMResource.uri ${uri && uri.toString()}`);
J
Joao Moreno 已提交
2108 2109 2110
		for (const r of this.model.repositories.map(r => r.root)) {
			this.outputChannel.appendLine(`repo root ${r}`);
		}
J
Joao Moreno 已提交
2111

J
Joao Moreno 已提交
2112
		if (!uri) {
2113
			return undefined;
J
Joao Moreno 已提交
2114 2115 2116
		}

		if (uri.scheme === 'git') {
J
Joao Moreno 已提交
2117 2118
			const { path } = fromGitUri(uri);
			uri = Uri.file(path);
J
Joao Moreno 已提交
2119 2120 2121 2122
		}

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

J
Joao Moreno 已提交
2125
			if (!repository) {
2126 2127
				return undefined;
			}
J
Joao Moreno 已提交
2128

J
Joao Moreno 已提交
2129 2130
			return repository.workingTreeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]
				|| repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
J
Joao Moreno 已提交
2131
		}
2132
		return undefined;
J
Joao Moreno 已提交
2133 2134
	}

J
Joao Moreno 已提交
2135
	private runByRepository<T>(resource: Uri, fn: (repository: Repository, resource: Uri) => Promise<T>): Promise<T[]>;
J
Joao Moreno 已提交
2136 2137 2138 2139 2140 2141
	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) => {
2142
			let repository = this.model.getRepository(resource);
J
Joao Moreno 已提交
2143 2144 2145 2146 2147 2148

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

2149
			// Could it be a submodule?
2150
			if (pathEquals(resource.fsPath, repository.root)) {
2151 2152 2153
				repository = this.model.getRepositoryForSubmodule(resource) || repository;
			}

J
Joao Moreno 已提交
2154
			const tuple = result.filter(p => p.repository === repository)[0];
J
Joao Moreno 已提交
2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170

			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 已提交
2171 2172 2173
	dispose(): void {
		this.disposables.forEach(d => d.dispose());
	}
J
João Moreno 已提交
2174
}