commands.ts 70.0 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];
}

182 183 184 185 186 187 188 189 190
enum PushType {
	Push,
	PushTo,
	PushTags,
}

interface PushOptions {
	pushType: PushType;
	forcePush?: boolean;
J
Joao Moreno 已提交
191
	silent?: boolean;
192 193
}

J
Joao Moreno 已提交
194
export class CommandCenter {
J
Joao Moreno 已提交
195 196

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

J
Joao Moreno 已提交
198
	constructor(
J
Joao Moreno 已提交
199
		private git: Git,
J
Joao Moreno 已提交
200
		private model: Model,
J
Joao Moreno 已提交
201 202
		private outputChannel: OutputChannel,
		private telemetryReporter: TelemetryReporter
J
Joao Moreno 已提交
203
	) {
J
Joao Moreno 已提交
204 205
		this.disposables = Commands.map(({ commandId, key, method, options }) => {
			const command = this.createCommand(commandId, key, method, options);
206

J
Joao Moreno 已提交
207
			if (options.diff) {
J
Joao Moreno 已提交
208 209 210 211 212
				return commands.registerDiffInformationCommand(commandId, command);
			} else {
				return commands.registerCommand(commandId, command);
			}
		});
J
Joao Moreno 已提交
213 214
	}

J
Joao Moreno 已提交
215 216
	@command('git.refresh', { repository: true })
	async refresh(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
217
		await repository.status();
J
Joao Moreno 已提交
218
	}
J
Joao Moreno 已提交
219

J
Joao Moreno 已提交
220 221
	@command('git.openResource')
	async openResource(resource: Resource): Promise<void> {
J
Joao Moreno 已提交
222 223 224 225 226 227 228 229 230 231 232 233 234 235
		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 已提交
236 237
	}

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

241 242 243 244
		try {
			stat = await new Promise<Stats>((c, e) => lstat(resource.resourceUri.fsPath, (err, stat) => err ? e(err) : c(stat)));
		} catch (err) {
			// noop
245
		}
246

247 248 249 250
		let left: Uri | undefined;
		let right: Uri | undefined;

		if (stat && stat.isDirectory()) {
251 252 253 254
			const repository = this.model.getRepositoryForSubmodule(resource.resourceUri);

			if (repository) {
				right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root });
255 256
			}
		} else {
J
Joao Moreno 已提交
257 258 259 260
			if (resource.type !== Status.DELETED_BY_THEM) {
				left = await this.getLeftResource(resource);
			}

261 262
			right = await this.getRightResource(resource);
		}
263

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

J
Joao Moreno 已提交
266 267 268 269 270
		if (!right) {
			// TODO
			console.error('oh no');
			return;
		}
J
Joao Moreno 已提交
271

J
Joao Moreno 已提交
272
		const opts: TextDocumentShowOptions = {
J
Joao Moreno 已提交
273 274
			preserveFocus,
			preview,
275
			viewColumn: ViewColumn.Active
J
Joao Moreno 已提交
276 277
		};

J
Joao Moreno 已提交
278 279
		const activeTextEditor = window.activeTextEditor;

280 281 282
		// 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 已提交
283 284 285
			opts.selection = activeTextEditor.selection;
		}

286
		if (!left) {
J
Joao Moreno 已提交
287
			await commands.executeCommand<void>('vscode.open', right, opts, title);
J
Joao Moreno 已提交
288 289 290 291
		} else {
			await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
		}
	}
J
Joao Moreno 已提交
292

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

J
Joao Moreno 已提交
296 297 298
		if (!repository) {
			return toGitUri(uri, ref);
		}
J
Joao Moreno 已提交
299

J
Joao Moreno 已提交
300
		try {
J
Joao Moreno 已提交
301 302 303
			let gitRef = ref;

			if (gitRef === '~') {
J
Joao Moreno 已提交
304
				const uriString = uri.toString();
J
Joao Moreno 已提交
305
				const [indexStatus] = repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString);
J
Joao Moreno 已提交
306
				gitRef = indexStatus ? '' : 'HEAD';
J
Joao Moreno 已提交
307 308
			}

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

J
Joao Moreno 已提交
312 313 314
			if (mimetype === 'text/plain') {
				return toGitUri(uri, ref);
			}
315

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

J
Joao Moreno 已提交
320
			if (ImageMimetypes.indexOf(mimetype) > -1) {
J
Joao Moreno 已提交
321 322
				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 已提交
323 324
			}

J
Joao Moreno 已提交
325
			return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${gitRef},`);
J
Joao Moreno 已提交
326 327 328 329

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

J
Joao Moreno 已提交
332
	private async getLeftResource(resource: Resource): Promise<Uri | undefined> {
J
Joao Moreno 已提交
333 334 335
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
336
				return this.getURI(resource.original, 'HEAD');
J
Joao Moreno 已提交
337 338

			case Status.MODIFIED:
J
Joao Moreno 已提交
339
				return this.getURI(resource.resourceUri, '~');
340 341

			case Status.DELETED_BY_THEM:
J
Joao Moreno 已提交
342
				return this.getURI(resource.resourceUri, '');
J
Joao Moreno 已提交
343
		}
344
		return undefined;
J
Joao Moreno 已提交
345
	}
J
Joao Moreno 已提交
346

J
Joao Moreno 已提交
347
	private async getRightResource(resource: Resource): Promise<Uri | undefined> {
J
Joao Moreno 已提交
348 349 350 351 352
		switch (resource.type) {
			case Status.INDEX_MODIFIED:
			case Status.INDEX_ADDED:
			case Status.INDEX_COPIED:
			case Status.INDEX_RENAMED:
J
Joao Moreno 已提交
353
				return this.getURI(resource.resourceUri, '');
J
Joao Moreno 已提交
354 355 356

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

J
Joao Moreno 已提交
359 360 361 362 363 364
			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 已提交
365 366 367
			case Status.MODIFIED:
			case Status.UNTRACKED:
			case Status.IGNORED:
368
			case Status.INTENT_TO_ADD:
J
Joao Moreno 已提交
369 370 371 372 373 374
				const repository = this.model.getRepository(resource.resourceUri);

				if (!repository) {
					return;
				}

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

J
Joao Moreno 已提交
378 379
				if (indexStatus && indexStatus.renameResourceUri) {
					return indexStatus.renameResourceUri;
J
Joao Moreno 已提交
380 381
				}

J
Joao Moreno 已提交
382
				return resource.resourceUri;
J
Joao Moreno 已提交
383

384
			case Status.BOTH_ADDED:
J
Joao Moreno 已提交
385
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
386
				return resource.resourceUri;
J
Joao Moreno 已提交
387
		}
388
		return undefined;
J
Joao Moreno 已提交
389 390 391
	}

	private getTitle(resource: Resource): string {
J
Joao Moreno 已提交
392
		const basename = path.basename(resource.resourceUri.fsPath);
J
Joao Moreno 已提交
393 394 395 396 397 398 399

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

			case Status.MODIFIED:
M
Marc Kassay 已提交
400 401
			case Status.BOTH_ADDED:
			case Status.BOTH_MODIFIED:
J
Joao Moreno 已提交
402
				return `${basename} (Working Tree)`;
J
Joao Moreno 已提交
403 404 405 406 407 408

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

			case Status.DELETED_BY_THEM:
				return `${basename} (Ours)`;
J
Joao Moreno 已提交
409 410 411 412 413
		}

		return '';
	}

J
Joao Moreno 已提交
414
	@command('git.clone')
415 416 417 418 419 420 421
	async clone(url?: string): Promise<void> {
		if (!url) {
			url = await window.showInputBox({
				prompt: localize('repourl', "Repository URL"),
				ignoreFocusOut: true
			});
		}
J
Joao Moreno 已提交
422 423

		if (!url) {
K
kieferrm 已提交
424
			/* __GDPR__
K
kieferrm 已提交
425 426 427 428
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
429 430
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' });
			return;
J
Joao Moreno 已提交
431 432
		}

433
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
434 435 436 437 438 439 440 441 442
		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 已提交
443 444
		});

J
Joao Moreno 已提交
445
		if (!uris || uris.length === 0) {
K
kieferrm 已提交
446
			/* __GDPR__
K
kieferrm 已提交
447 448 449 450
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
451 452
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' });
			return;
J
Joao Moreno 已提交
453 454
		}

J
Joao Moreno 已提交
455 456 457
		const uri = uris[0];
		const parentPath = uri.fsPath;

458
		try {
459 460 461 462 463
			const opts = {
				location: ProgressLocation.Notification,
				title: localize('cloning', "Cloning git repository '{0}'...", url),
				cancellable: true
			};
M
Maryam Archie 已提交
464

465 466
			const repositoryPath = await window.withProgress(
				opts,
J
Joao Moreno 已提交
467
				(_, token) => this.git.clone(url!, parentPath, token)
468
			);
469

470 471
			const choices = [];
			let message = localize('proposeopen', "Would you like to open the cloned repository?");
472
			const open = localize('openrepo', "Open Repository");
473 474 475 476 477 478 479 480 481
			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);
482 483

			const openFolder = result === open;
K
kieferrm 已提交
484
			/* __GDPR__
K
kieferrm 已提交
485 486 487 488 489
				"clone" : {
					"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
					"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
				}
			*/
490
			this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: openFolder ? 1 : 0 });
491 492 493

			const uri = Uri.file(repositoryPath);

494
			if (openFolder) {
495 496 497
				commands.executeCommand('vscode.openFolder', uri);
			} else if (result === addToWorkspace) {
				workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri });
498
			}
499 500
		} catch (err) {
			if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
K
kieferrm 已提交
501
				/* __GDPR__
K
kieferrm 已提交
502 503 504 505
					"clone" : {
						"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
506
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
J
Joao Moreno 已提交
507 508
			} else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) {
				return;
509
			} else {
K
kieferrm 已提交
510
				/* __GDPR__
K
kieferrm 已提交
511 512 513 514
					"clone" : {
						"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
					}
				*/
515
				this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
516
			}
J
Joao Moreno 已提交
517

518
			throw err;
J
Joao Moreno 已提交
519 520 521
		}
	}

J
Joao Moreno 已提交
522
	@command('git.init')
J
Joao Moreno 已提交
523
	async init(): Promise<void> {
J
Joao Moreno 已提交
524
		let repositoryPath: string | undefined = undefined;
J
Joao Moreno 已提交
525
		let askToOpen = true;
J
Joao Moreno 已提交
526

J
Joao Moreno 已提交
527
		if (workspace.workspaceFolders) {
J
Joao Moreno 已提交
528
			const placeHolder = localize('init', "Pick workspace folder to initialize git repo in");
J
Joao Moreno 已提交
529 530 531 532 533
			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 已提交
534 535 536 537
			const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });

			if (!item) {
				return;
J
Joao Moreno 已提交
538 539
			} else if (item.folder) {
				repositoryPath = item.folder.uri.fsPath;
J
Joao Moreno 已提交
540
				askToOpen = false;
J
Joao Moreno 已提交
541 542
			}
		}
J
Joao Moreno 已提交
543

J
Joao Moreno 已提交
544
		if (!repositoryPath) {
J
Joao Moreno 已提交
545 546 547 548 549 550 551 552 553 554 555 556
			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 已提交
557

J
Joao Moreno 已提交
558
			if (!result || result.length === 0) {
J
Joao Moreno 已提交
559 560
				return;
			}
J
Joao Moreno 已提交
561 562 563 564 565 566 567 568 569 570 571 572

			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 已提交
573
			repositoryPath = uri.fsPath;
J
Joao Moreno 已提交
574 575 576 577

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

J
Joao Moreno 已提交
580
		await this.git.init(repositoryPath);
I
Ivan Sučić 已提交
581 582

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

J
Joao Moreno 已提交
587 588 589 590
		if (!askToOpen) {
			return;
		}

J
Joao Moreno 已提交
591 592 593 594 595
		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ć 已提交
596 597

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

J
Joao Moreno 已提交
600 601 602 603 604 605
		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ć 已提交
606
		}
J
Joao Moreno 已提交
607 608
	}

J
Joao Moreno 已提交
609 610
	@command('git.openRepository', { repository: false })
	async openRepository(path?: string): Promise<void> {
611
		if (!path) {
J
Joao Moreno 已提交
612 613 614 615 616 617
			const result = await window.showOpenDialog({
				canSelectFiles: false,
				canSelectFolders: true,
				canSelectMany: false,
				defaultUri: Uri.file(os.homedir()),
				openLabel: localize('open repo', "Open Repository")
618 619
			});

J
Joao Moreno 已提交
620
			if (!result || result.length === 0) {
621 622 623
				return;
			}

J
Joao Moreno 已提交
624
			path = result[0].fsPath;
625
		}
J
Joao Moreno 已提交
626 627

		await this.model.openRepository(path);
628 629
	}

J
Joao Moreno 已提交
630 631 632 633 634
	@command('git.close', { repository: true })
	async close(repository: Repository): Promise<void> {
		this.model.close(repository);
	}

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

639
		let uris: Uri[] | undefined;
640 641 642

		if (arg instanceof Uri) {
			if (arg.scheme === 'git') {
643
				uris = [Uri.file(fromGitUri(arg).path)];
644
			} else if (arg.scheme === 'file') {
645
				uris = [arg];
646 647 648 649 650 651
			}
		} else {
			let resource = arg;

			if (!(resource instanceof Resource)) {
				// can happen when called from a keybinding
652
				resource = this.getSCMResource();
653 654 655
			}

			if (resource) {
J
Joao Moreno 已提交
656 657 658 659
				const resources = ([resource, ...resourceStates] as Resource[])
					.filter(r => r.type !== Status.DELETED && r.type !== Status.INDEX_DELETED);

				uris = resources.map(r => r.resourceUri);
660
			}
J
Joao Moreno 已提交
661 662
		}

663
		if (!uris) {
J
Joao Moreno 已提交
664
			return;
J
Joao Moreno 已提交
665 666
		}

667 668 669
		const activeTextEditor = window.activeTextEditor;
		for (const uri of uris) {
			const opts: TextDocumentShowOptions = {
J
Joao Moreno 已提交
670
				preserveFocus,
J
Joao Moreno 已提交
671
				preview: false,
672
				viewColumn: ViewColumn.Active
673 674
			};

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

677 678 679
			// 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 已提交
680
				// preserve not only selection but also visible range
B
Benjamin Pasero 已提交
681
				opts.selection = activeTextEditor.selection;
E
Eric Gang 已提交
682 683 684 685 686
				const previousVisibleRanges = activeTextEditor.visibleRanges;
				const editor = await window.showTextDocument(document, opts);
				editor.revealRange(previousVisibleRanges[0]);
			} else {
				await window.showTextDocument(document, opts);
B
Benjamin Pasero 已提交
687
			}
J
Joao Moreno 已提交
688
		}
J
Joao Moreno 已提交
689 690
	}

J
Joao Moreno 已提交
691 692 693 694 695
	@command('git.openFile2')
	async openFile2(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
		this.openFile(arg, ...resourceStates);
	}

J
Joao Moreno 已提交
696 697
	@command('git.openHEADFile')
	async openHEADFile(arg?: Resource | Uri): Promise<void> {
D
Duroktar 已提交
698
		let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
699
		const preview = !(arg instanceof Resource);
D
Duroktar 已提交
700 701 702 703

		if (arg instanceof Resource) {
			resource = arg;
		} else if (arg instanceof Uri) {
704
			resource = this.getSCMResource(arg);
D
Duroktar 已提交
705
		} else {
706
			resource = this.getSCMResource();
D
Duroktar 已提交
707 708 709 710 711 712
		}

		if (!resource) {
			return;
		}

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

J
Joao Moreno 已提交
715 716 717
		if (!HEAD) {
			window.showWarningMessage(localize('HEAD not available', "HEAD version of '{0}' is not available.", path.basename(resource.resourceUri.fsPath)));
			return;
D
Duroktar 已提交
718
		}
J
Joao Moreno 已提交
719

J
Joao Moreno 已提交
720 721 722 723 724
		const opts: TextDocumentShowOptions = {
			preview
		};

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

J
Joao Moreno 已提交
727 728
	@command('git.openChange')
	async openChange(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise<void> {
729
		const preserveFocus = arg instanceof Resource;
J
Joao Moreno 已提交
730 731
		const preview = !(arg instanceof Resource);

J
Joao 已提交
732
		const preserveSelection = arg instanceof Uri || !arg;
733
		let resources: Resource[] | undefined = undefined;
734

735
		if (arg instanceof Uri) {
736
			const resource = this.getSCMResource(arg);
737 738 739
			if (resource !== undefined) {
				resources = [resource];
			}
740
		} else {
741
			let resource: Resource | undefined = undefined;
J
Joao Moreno 已提交
742

743 744 745
			if (arg instanceof Resource) {
				resource = arg;
			} else {
746
				resource = this.getSCMResource();
747 748 749 750 751
			}

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

754
		if (!resources) {
J
Joao Moreno 已提交
755
			return;
J
Joao Moreno 已提交
756
		}
J
Joao Moreno 已提交
757

758
		for (const resource of resources) {
J
Joao 已提交
759
			await this._openResource(resource, preview, preserveFocus, preserveSelection);
760
		}
J
Joao Moreno 已提交
761 762
	}

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

767 768
		resourceStates = resourceStates.filter(s => !!s);

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

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

774 775 776 777 778 779 780
			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

781
		const selection = resourceStates.filter(s => s instanceof Resource) as Resource[];
J
Joao Moreno 已提交
782
		const { resolved, unresolved, deletionConflicts } = await categorizeResourceByResolution(selection);
783 784 785 786 787

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

789 790 791 792 793 794 795 796
			const yes = localize('yes', "Yes");
			const pick = await window.showWarningMessage(message, { modal: true }, yes);

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

J
Joao Moreno 已提交
797 798 799 800 801 802 803 804 805 806 807 808 809 810
		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 已提交
811
		const workingTree = selection.filter(s => s.resourceGroupType === ResourceGroupType.WorkingTree);
812
		const scmResources = [...workingTree, ...resolved, ...unresolved];
813

J
Joao Moreno 已提交
814
		this.outputChannel.appendLine(`git.stage.scmResources ${scmResources.length}`);
815
		if (!scmResources.length) {
J
Joao Moreno 已提交
816 817
			return;
		}
J
Joao Moreno 已提交
818

819
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
820
		await this.runByRepository(resources, async (repository, resources) => repository.add(resources));
J
Joao Moreno 已提交
821 822
	}

J
Joao Moreno 已提交
823 824
	@command('git.stageAll', { repository: true })
	async stageAll(repository: Repository): Promise<void> {
825
		const resources = repository.mergeGroup.resourceStates.filter(s => s instanceof Resource) as Resource[];
J
Joao Moreno 已提交
826 827 828 829 830 831 832 833 834 835 836 837 838
		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;
		}
839 840 841

		if (unresolved.length > 0) {
			const message = unresolved.length > 1
J
Joao Moreno 已提交
842 843
				? 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));
844 845 846 847 848 849 850 851 852

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

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

J
Joao Moreno 已提交
853
		await repository.add([]);
J
Joao Moreno 已提交
854 855
	}

J
Joao Moreno 已提交
856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890
	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 已提交
891
	@command('git.stageChange')
J
Joao Moreno 已提交
892 893 894 895 896 897 898 899
	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 已提交
900 901
	}

J
Joao Moreno 已提交
902
	@command('git.stageSelectedRanges', { diff: true })
J
Joao Moreno 已提交
903
	async stageSelectedChanges(changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
904 905 906 907 908 909 910
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
J
Joao Moreno 已提交
911 912 913 914
		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 已提交
915

J
Joao Moreno 已提交
916
		if (!selectedChanges.length) {
J
Joao Moreno 已提交
917 918 919
			return;
		}

J
Joao Moreno 已提交
920 921
		await this._stageChanges(textEditor, selectedChanges);
	}
J
Joao Moreno 已提交
922

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

J
Joao Moreno 已提交
927
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
928 929 930
			return;
		}

J
Joao Moreno 已提交
931
		const originalUri = toGitUri(modifiedUri, '~');
J
Joao Moreno 已提交
932
		const originalDocument = await workspace.openTextDocument(originalUri);
J
Joao Moreno 已提交
933
		const result = applyLineChanges(originalDocument, modifiedDocument, changes);
J
Joao Moreno 已提交
934

J
Joao Moreno 已提交
935
		await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
J
Joao Moreno 已提交
936
	}
J
Joao Moreno 已提交
937

J
Joao Moreno 已提交
938
	@command('git.revertChange')
J
Joao Moreno 已提交
939 940 941 942
	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 已提交
943 944 945
			return;
		}

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

J
Joao Moreno 已提交
949
	@command('git.revertSelectedRanges', { diff: true })
J
Joao Moreno 已提交
950
	async revertSelectedRanges(changes: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
951 952 953 954 955 956 957
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

		const modifiedDocument = textEditor.document;
J
Joao Moreno 已提交
958 959
		const selections = textEditor.selections;
		const selectedChanges = changes.filter(change => {
J
Joao Moreno 已提交
960
			const modifiedRange = getModifiedRange(modifiedDocument, change);
J
Joao Moreno 已提交
961 962 963 964
			return selections.every(selection => !selection.intersection(modifiedRange));
		});

		if (selectedChanges.length === changes.length) {
J
Joao Moreno 已提交
965 966 967
			return;
		}

J
Joao Moreno 已提交
968 969
		await this._revertChanges(textEditor, selectedChanges);
	}
J
Joao Moreno 已提交
970

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

J
Joao Moreno 已提交
975
		if (modifiedUri.scheme !== 'file') {
J
Joao Moreno 已提交
976 977 978
			return;
		}

J
Joao Moreno 已提交
979 980
		const originalUri = toGitUri(modifiedUri, '~');
		const originalDocument = await workspace.openTextDocument(originalUri);
981 982
		const selectionsBeforeRevert = textEditor.selections;
		const visibleRangesBeforeRevert = textEditor.visibleRanges;
J
Joao Moreno 已提交
983
		const result = applyLineChanges(originalDocument, modifiedDocument, changes);
984

J
Joao Moreno 已提交
985 986 987
		const edit = new WorkspaceEdit();
		edit.replace(modifiedUri, new Range(new Position(0, 0), modifiedDocument.lineAt(modifiedDocument.lineCount - 1).range.end), result);
		workspace.applyEdit(edit);
988

J
Joao Moreno 已提交
989 990
		await modifiedDocument.save();

991 992
		textEditor.selections = selectionsBeforeRevert;
		textEditor.revealRange(visibleRangesBeforeRevert[0]);
J
Joao Moreno 已提交
993 994
	}

J
Joao Moreno 已提交
995 996
	@command('git.unstage')
	async unstage(...resourceStates: SourceControlResourceState[]): Promise<void> {
997 998
		resourceStates = resourceStates.filter(s => !!s);

J
Joao Moreno 已提交
999
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
1000
			const resource = this.getSCMResource();
1001 1002 1003 1004 1005 1006 1007 1008

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

1012
		if (!scmResources.length) {
J
Joao Moreno 已提交
1013 1014 1015
			return;
		}

1016
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
1017
		await this.runByRepository(resources, async (repository, resources) => repository.revert(resources));
J
Joao Moreno 已提交
1018 1019
	}

J
Joao Moreno 已提交
1020 1021
	@command('git.unstageAll', { repository: true })
	async unstageAll(repository: Repository): Promise<void> {
1022
		await repository.revert([]);
J
Joao Moreno 已提交
1023
	}
J
Joao Moreno 已提交
1024

J
Joao Moreno 已提交
1025 1026
	@command('git.unstageSelectedRanges', { diff: true })
	async unstageSelectedRanges(diffs: LineChange[]): Promise<void> {
J
Joao Moreno 已提交
1027 1028 1029 1030 1031 1032 1033 1034 1035
		const textEditor = window.activeTextEditor;

		if (!textEditor) {
			return;
		}

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

1036 1037 1038 1039 1040 1041 1042
		if (modifiedUri.scheme !== 'git') {
			return;
		}

		const { ref } = fromGitUri(modifiedUri);

		if (ref !== '') {
J
Joao Moreno 已提交
1043 1044 1045
			return;
		}

J
Joao Moreno 已提交
1046
		const originalUri = toGitUri(modifiedUri, 'HEAD');
J
Joao Moreno 已提交
1047
		const originalDocument = await workspace.openTextDocument(originalUri);
1048 1049 1050 1051
		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 已提交
1052 1053 1054 1055 1056

		if (!selectedDiffs.length) {
			return;
		}

1057 1058
		const invertedDiffs = selectedDiffs.map(invertLineChange);
		const result = applyLineChanges(modifiedDocument, originalDocument, invertedDiffs);
J
Joao Moreno 已提交
1059

J
Joao Moreno 已提交
1060
		await this.runByRepository(modifiedUri, async (repository, resource) => await repository.stage(resource, result));
J
Joao Moreno 已提交
1061 1062
	}

J
Joao Moreno 已提交
1063 1064
	@command('git.clean')
	async clean(...resourceStates: SourceControlResourceState[]): Promise<void> {
1065 1066
		resourceStates = resourceStates.filter(s => !!s);

J
Joao Moreno 已提交
1067
		if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
1068
			const resource = this.getSCMResource();
1069 1070 1071 1072 1073 1074 1075 1076

			if (!resource) {
				return;
			}

			resourceStates = [resource];
		}

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

J
Joao Moreno 已提交
1080
		if (!scmResources.length) {
J
Joao Moreno 已提交
1081 1082
			return;
		}
J
Joao Moreno 已提交
1083

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

J
Joao Moreno 已提交
1088
		if (scmResources.length === 1) {
J
Joao Moreno 已提交
1089
			if (untrackedCount > 0) {
J
Joao Moreno 已提交
1090
				message = localize('confirm delete', "Are you sure you want to DELETE {0}?", path.basename(scmResources[0].resourceUri.fsPath));
J
Joao Moreno 已提交
1091 1092
				yes = localize('delete file', "Delete file");
			} else {
1093 1094 1095 1096 1097 1098
				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 已提交
1099 1100
			}
		} else {
1101 1102 1103 1104 1105 1106
			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 已提交
1107 1108 1109 1110 1111

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

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

J
Joao Moreno 已提交
1115 1116 1117 1118
		if (pick !== yes) {
			return;
		}

J
Joao Moreno 已提交
1119
		const resources = scmResources.map(r => r.resourceUri);
J
Joao Moreno 已提交
1120
		await this.runByRepository(resources, async (repository, resources) => repository.clean(resources));
J
Joao Moreno 已提交
1121
	}
J
Joao Moreno 已提交
1122

J
Joao Moreno 已提交
1123 1124
	@command('git.cleanAll', { repository: true })
	async cleanAll(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1125
		let resources = repository.workingTreeGroup.resourceStates;
J
Joao Moreno 已提交
1126 1127 1128 1129 1130

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

J
Joao Moreno 已提交
1131 1132
		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 已提交
1133

J
Joao Moreno 已提交
1134 1135 1136 1137 1138 1139
		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 已提交
1140
				: localize('discardAll', "Discard All {0} Files", resources.length);
J
Joao Moreno 已提交
1141
			const pick = await window.showWarningMessage(message, { modal: true }, yes);
J
Joao Moreno 已提交
1142

J
Joao Moreno 已提交
1143
			if (pick !== yes) {
J
Joao Moreno 已提交
1144 1145 1146
				return;
			}

J
Joao 已提交
1147
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
1148 1149 1150 1151 1152 1153 1154 1155
			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 已提交
1156 1157
			}

J
Joao 已提交
1158
			await repository.clean(resources.map(r => r.resourceUri));
J
Joao Moreno 已提交
1159 1160 1161 1162
		} 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 已提交
1163

J
Joao Moreno 已提交
1164 1165 1166
			if (pick !== yes) {
				return;
			}
J
Joao Moreno 已提交
1167

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

J
Joao Moreno 已提交
1170 1171 1172 1173
		} 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 已提交
1174

J
Joao Moreno 已提交
1175 1176 1177 1178 1179
			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 已提交
1180

J
Joao Moreno 已提交
1181
			const yesAll = localize('discardAll', "Discard All {0} Files", resources.length);
J
Joao Moreno 已提交
1182 1183 1184 1185 1186 1187 1188 1189
			const pick = await window.showWarningMessage(message, { modal: true }, yesTracked, yesAll);

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

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

J
Joao Moreno 已提交
1194
	private async smartCommit(
J
Joao Moreno 已提交
1195
		repository: Repository,
1196
		getCommitMessage: () => Promise<string | undefined>,
J
Joao Moreno 已提交
1197 1198
		opts?: CommitOptions
	): Promise<boolean> {
1199
		const config = workspace.getConfiguration('git', Uri.file(repository.root));
J
Joao Moreno 已提交
1200 1201 1202 1203 1204 1205 1206 1207
		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 已提交
1208 1209
					? 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 已提交
1210 1211 1212 1213 1214 1215 1216 1217 1218 1219
				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
				}
1220 1221 1222
			}
		}

1223
		const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
1224
		const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
J
Joao Moreno 已提交
1225 1226
		const noStagedChanges = repository.indexGroup.resourceStates.length === 0;
		const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
1227 1228

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

J
Joao Moreno 已提交
1231 1232
			// 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?");
1233 1234 1235 1236 1237
			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 已提交
1238 1239 1240
				config.update('enableSmartCommit', true, true);
			} else if (pick !== yes) {
				return false; // do not commit on cancel
1241 1242 1243
			}
		}

J
Joao Moreno 已提交
1244
		if (!opts) {
1245
			opts = { all: noStagedChanges };
J
Joao Moreno 已提交
1246 1247
		} else if (!opts.all && noStagedChanges) {
			opts = { ...opts, all: true };
J
Joao Moreno 已提交
1248 1249
		}

1250 1251 1252
		// enable signing of commits if configurated
		opts.signCommit = enableCommitSigning;

1253 1254 1255 1256
		if (config.get<boolean>('alwaysSignOff')) {
			opts.signoff = true;
		}

J
Joao Moreno 已提交
1257
		if (
1258
			(
J
Joao Moreno 已提交
1259 1260 1261 1262 1263
				// no changes
				(noStagedChanges && noUnstagedChanges)
				// or no staged changes and not `all`
				|| (!opts.all && noStagedChanges)
			)
1264
			&& !opts.empty
J
Joao Moreno 已提交
1265
		) {
J
Joao Moreno 已提交
1266 1267 1268 1269
			window.showInformationMessage(localize('no changes', "There are no changes to commit."));
			return false;
		}

J
Joao Moreno 已提交
1270
		const message = await getCommitMessage();
J
Joao Moreno 已提交
1271 1272 1273 1274 1275

		if (!message) {
			return false;
		}

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

J
Joao Moreno 已提交
1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288
		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 已提交
1289 1290 1291
		return true;
	}

J
Joao Moreno 已提交
1292
	private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> {
J
Joao Moreno 已提交
1293
		const message = repository.inputBox.value;
J
Joao Moreno 已提交
1294
		const getCommitMessage = async () => {
J
Joao Moreno 已提交
1295 1296 1297 1298
			if (message) {
				return message;
			}

1299 1300 1301 1302 1303 1304
			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 已提交
1305
			return await window.showInputBox({
1306
				value,
J
Joao Moreno 已提交
1307
				placeHolder: localize('commit message', "Commit message"),
J
Joao Moreno 已提交
1308 1309
				prompt: localize('provide commit message', "Please provide a commit message"),
				ignoreFocusOut: true
J
Joao Moreno 已提交
1310
			});
J
Joao Moreno 已提交
1311 1312
		};

J
Joao Moreno 已提交
1313
		const didCommit = await this.smartCommit(repository, getCommitMessage, opts);
J
Joao Moreno 已提交
1314 1315

		if (message && didCommit) {
J
Joao Moreno 已提交
1316
			repository.inputBox.value = await repository.getCommitTemplate();
J
Joao Moreno 已提交
1317
		}
J
Joao Moreno 已提交
1318 1319
	}

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

J
Joao Moreno 已提交
1325
	@command('git.commitWithInput', { repository: true })
J
Joao Moreno 已提交
1326
	async commitWithInput(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1327
		if (!repository.inputBox.value) {
J
Joao Moreno 已提交
1328 1329 1330
			return;
		}

J
Joao Moreno 已提交
1331
		const didCommit = await this.smartCommit(repository, async () => repository.inputBox.value);
J
Joao Moreno 已提交
1332 1333

		if (didCommit) {
J
Joao Moreno 已提交
1334
			repository.inputBox.value = await repository.getCommitTemplate();
J
Joao Moreno 已提交
1335
		}
J
Joao Moreno 已提交
1336 1337
	}

J
Joao Moreno 已提交
1338
	@command('git.commitStaged', { repository: true })
J
Joao Moreno 已提交
1339 1340
	async commitStaged(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: false });
J
Joao Moreno 已提交
1341 1342
	}

J
Joao Moreno 已提交
1343
	@command('git.commitStagedSigned', { repository: true })
J
Joao Moreno 已提交
1344 1345
	async commitStagedSigned(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: false, signoff: true });
J
Joao Moreno 已提交
1346 1347
	}

J
Joao Moreno 已提交
1348
	@command('git.commitStagedAmend', { repository: true })
J
Joao Moreno 已提交
1349
	async commitStagedAmend(repository: Repository): Promise<void> {
1350
		await this.commitWithAnyInput(repository, { all: false, amend: true });
K
Krzysztof Cieślak 已提交
1351 1352
	}

J
Joao Moreno 已提交
1353
	@command('git.commitAll', { repository: true })
J
Joao Moreno 已提交
1354 1355
	async commitAll(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true });
J
Joao Moreno 已提交
1356 1357
	}

J
Joao Moreno 已提交
1358
	@command('git.commitAllSigned', { repository: true })
J
Joao Moreno 已提交
1359 1360
	async commitAllSigned(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true, signoff: true });
J
Joao Moreno 已提交
1361 1362
	}

J
Joao Moreno 已提交
1363
	@command('git.commitAllAmend', { repository: true })
J
Joao Moreno 已提交
1364 1365
	async commitAllAmend(repository: Repository): Promise<void> {
		await this.commitWithAnyInput(repository, { all: true, amend: true });
K
Krzysztof Cieślak 已提交
1366 1367
	}

J
Joao Moreno 已提交
1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389
	@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 已提交
1390 1391 1392 1393 1394
	@command('git.restoreCommitTemplate', { repository: true })
	async restoreCommitTemplate(repository: Repository): Promise<void> {
		repository.inputBox.value = await repository.getCommitTemplate();
	}

J
Joao Moreno 已提交
1395
	@command('git.undoCommit', { repository: true })
J
Joao Moreno 已提交
1396 1397
	async undoCommit(repository: Repository): Promise<void> {
		const HEAD = repository.HEAD;
J
Joao Moreno 已提交
1398 1399

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

J
Joao Moreno 已提交
1404
		const commit = await repository.getCommit('HEAD');
J
Joao Moreno 已提交
1405 1406

		if (commit.parents.length > 0) {
1407 1408 1409 1410 1411
			await repository.reset('HEAD~');
		} else {
			await repository.deleteRef('HEAD');
			await this.unstageAll(repository);
		}
J
Joao Moreno 已提交
1412

J
Joao Moreno 已提交
1413
		repository.inputBox.value = commit.message;
J
Joao Moreno 已提交
1414 1415
	}

J
Joao Moreno 已提交
1416
	@command('git.checkout', { repository: true })
1417
	async checkout(repository: Repository, treeish: string): Promise<boolean> {
J
Joao Moreno 已提交
1418
		if (typeof treeish === 'string') {
1419 1420
			await repository.checkout(treeish);
			return true;
J
Joao Moreno 已提交
1421
		}
J
Joao Moreno 已提交
1422
		const createBranch = new CreateBranchItem(this);
J
Joao Moreno 已提交
1423

J
Joao Moreno 已提交
1424
		const picks = [createBranch, ...createCheckoutItems(repository)];
1425
		const placeHolder = localize('select a ref to checkout', 'Select a ref to checkout');
1426 1427 1428 1429 1430 1431 1432 1433

		const quickpick = window.createQuickPick();
		quickpick.items = picks;
		quickpick.placeholder = placeHolder;
		quickpick.show();

		const choice = await new Promise<QuickPickItem | undefined>(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0])));
		quickpick.hide();
J
Joao Moreno 已提交
1434 1435

		if (!choice) {
1436
			return false;
J
Joao Moreno 已提交
1437 1438
		}

1439 1440 1441 1442 1443 1444
		if (choice === createBranch) {
			await this._branch(repository, quickpick.value);
		} else {
			await (choice as CheckoutItem).run(repository);
		}

1445
		return true;
J
Joao Moreno 已提交
1446 1447
	}

J
Joao Moreno 已提交
1448
	@command('git.branch', { repository: true })
J
Joao Moreno 已提交
1449
	async branch(repository: Repository): Promise<void> {
1450 1451
		await this._branch(repository);
	}
J
Joao Moreno 已提交
1452

1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478
	private async _branch(repository: Repository, defaultName?: string): Promise<void> {
		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 rawBranchName = await window.showInputBox({
			value: defaultName,
			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);
			}
		});

		const branchName = sanitize(rawBranchName || '');

		if (!branchName) {
1479 1480
			return;
		}
J
Joao Moreno 已提交
1481

1482 1483 1484
		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 已提交
1485

1486
		if (!target) {
J
Joao Moreno 已提交
1487 1488
			return;
		}
J
Joao Moreno 已提交
1489

1490
		await repository.branch(branchName, true, target.label);
J
Joao Moreno 已提交
1491 1492
	}

J
Joao Moreno 已提交
1493
	@command('git.deleteBranch', { repository: true })
J
Joao Moreno 已提交
1494
	async deleteBranch(repository: Repository, name: string, force?: boolean): Promise<void> {
1495 1496
		let run: (force?: boolean) => Promise<void>;
		if (typeof name === 'string') {
J
Joao Moreno 已提交
1497
			run = force => repository.deleteBranch(name, force);
1498
		} else {
J
Joao Moreno 已提交
1499 1500
			const currentHead = repository.HEAD && repository.HEAD.name;
			const heads = repository.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead)
1501
				.map(ref => new BranchDeleteItem(ref));
M
Maik Riechert 已提交
1502

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

M
Maik Riechert 已提交
1506
			if (!choice || !choice.branchName) {
1507 1508
				return;
			}
M
Maik Riechert 已提交
1509
			name = choice.branchName;
J
Joao Moreno 已提交
1510
			run = force => choice.run(repository, force);
M
Maik Riechert 已提交
1511 1512
		}

1513 1514 1515 1516 1517 1518 1519 1520 1521
		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");
1522
			const pick = await window.showWarningMessage(message, { modal: true }, yes);
1523 1524 1525 1526 1527

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

1530 1531
	@command('git.renameBranch', { repository: true })
	async renameBranch(repository: Repository): Promise<void> {
J
Justin Horner 已提交
1532 1533 1534 1535 1536 1537 1538 1539
		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 {
1540
			await repository.renameBranch(name);
J
Justin Horner 已提交
1541 1542 1543
		} catch (err) {
			switch (err.gitErrorCode) {
				case GitErrorCodes.InvalidBranchName:
1544 1545
					window.showErrorMessage(localize('invalid branch name', 'Invalid branch name'));
					return;
J
Justin Horner 已提交
1546
				case GitErrorCodes.BranchAlreadyExists:
J
Joao Moreno 已提交
1547
					window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", name));
1548 1549 1550
					return;
				default:
					throw err;
J
Justin Horner 已提交
1551 1552 1553 1554
			}
		}
	}

J
Joao Moreno 已提交
1555
	@command('git.merge', { repository: true })
J
Joao Moreno 已提交
1556
	async merge(repository: Repository): Promise<void> {
1557 1558 1559 1560
		const config = workspace.getConfiguration('git');
		const checkoutType = config.get<string>('checkoutType') || 'all';
		const includeRemotes = checkoutType === 'all' || checkoutType === 'remote';

J
Joao Moreno 已提交
1561
		const heads = repository.refs.filter(ref => ref.type === RefType.Head)
J
Joao Moreno 已提交
1562 1563
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
1564

J
Joao Moreno 已提交
1565
		const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : [])
J
Joao Moreno 已提交
1566 1567
			.filter(ref => ref.name || ref.commit)
			.map(ref => new MergeItem(ref as Branch));
1568 1569

		const picks = [...heads, ...remoteHeads];
1570 1571
		const placeHolder = localize('select a branch to merge from', 'Select a branch to merge from');
		const choice = await window.showQuickPick<MergeItem>(picks, { placeHolder });
1572 1573 1574 1575 1576

		if (!choice) {
			return;
		}

1577
		await choice.run(repository);
1578 1579
	}

J
Joao Moreno 已提交
1580
	@command('git.createTag', { repository: true })
J
Joao Moreno 已提交
1581
	async createTag(repository: Repository): Promise<void> {
1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593
		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 已提交
1594
			prompt: localize('provide tag message', "Please provide a message to annotate the tag"),
1595 1596 1597 1598 1599
			ignoreFocusOut: true
		});

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

J
Joao Moreno 已提交
1603 1604 1605 1606 1607 1608 1609
	@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 已提交
1610
		await repository.fetchDefault();
J
Joao Moreno 已提交
1611 1612
	}

R
Ryan Scott 已提交
1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623
	@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 已提交
1624 1625 1626 1627 1628 1629 1630 1631 1632 1633
	@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 已提交
1634
	@command('git.pullFrom', { repository: true })
J
Joao Moreno 已提交
1635 1636
	async pullFrom(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
1637 1638 1639 1640 1641 1642

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

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

D
Dozed12 已提交
1647
		if (!remotePick) {
1648 1649 1650
			return;
		}

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

D
Dozed12 已提交
1657
		if (!branchPick) {
1658 1659 1660
			return;
		}

F
Francisco Moreira 已提交
1661 1662
		const remoteCharCnt = remotePick.label.length;

1663
		await repository.pullFrom(false, remotePick.label, branchPick.label.slice(remoteCharCnt + 1));
1664 1665
	}

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

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

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

J
Joao Moreno 已提交
1678
	@command('git.pullRebase', { repository: true })
J
Joao Moreno 已提交
1679 1680
	async pullRebase(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1681 1682 1683 1684 1685 1686

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

J
Joao Moreno 已提交
1687
		await repository.pullWithRebase(repository.HEAD);
J
Joao Moreno 已提交
1688 1689
	}

J
Joao Moreno 已提交
1690
	private async _push(repository: Repository, pushOptions: PushOptions) {
J
Joao Moreno 已提交
1691
		const remotes = repository.remotes;
1692

J
Joao Moreno 已提交
1693
		if (remotes.length === 0) {
J
Joao Moreno 已提交
1694 1695 1696
			if (!pushOptions.silent) {
				window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."));
			}
J
Joao Moreno 已提交
1697 1698
			return;
		}
1699

J
Joao Moreno 已提交
1700 1701 1702 1703 1704 1705
		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."));
1706 1707
				return;
			}
J
Joao Moreno 已提交
1708

J
Joao Moreno 已提交
1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722
			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 已提交
1723 1724
		}

1725 1726 1727 1728 1729 1730 1731
		if (pushOptions.pushType === PushType.PushTags) {
			await repository.pushTags(undefined, forcePushMode);

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

1732
		if (!repository.HEAD || !repository.HEAD.name) {
J
Joao Moreno 已提交
1733 1734 1735
			if (!pushOptions.silent) {
				window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote."));
			}
1736 1737 1738
			return;
		}

1739 1740 1741 1742 1743 1744 1745
		if (pushOptions.pushType === PushType.Push) {
			try {
				await repository.push(repository.HEAD, forcePushMode);
			} catch (err) {
				if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) {
					throw err;
				}
1746

J
Joao Moreno 已提交
1747 1748 1749 1750
				if (pushOptions.silent) {
					return;
				}

1751 1752 1753 1754 1755 1756 1757 1758 1759 1760
				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 {
1761
			const branchName = repository.HEAD.name;
1762 1763 1764
			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 });
1765

1766 1767
			if (!pick) {
				return;
1768
			}
1769 1770

			await repository.pushTo(pick.label, branchName, undefined, forcePushMode);
1771
		}
J
Joao Moreno 已提交
1772 1773
	}

1774 1775
	@command('git.push', { repository: true })
	async push(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1776
		await this._push(repository, { pushType: PushType.Push });
1777
	}
1778

1779 1780
	@command('git.pushForce', { repository: true })
	async pushForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1781
		await this._push(repository, { pushType: PushType.Push, forcePush: true });
1782
	}
1783

1784 1785
	@command('git.pushWithTags', { repository: true })
	async pushWithTags(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1786
		await this._push(repository, { pushType: PushType.PushTags });
1787
	}
1788

1789 1790
	@command('git.pushWithTagsForce', { repository: true })
	async pushWithTagsForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1791
		await this._push(repository, { pushType: PushType.PushTags, forcePush: true });
1792 1793
	}

J
Joao Moreno 已提交
1794
	@command('git.pushTo', { repository: true })
J
Joao Moreno 已提交
1795
	async pushTo(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1796
		await this._push(repository, { pushType: PushType.PushTo });
1797
	}
J
Joao Moreno 已提交
1798

1799 1800
	@command('git.pushToForce', { repository: true })
	async pushToForce(repository: Repository): Promise<void> {
J
Joao Moreno 已提交
1801
		await this._push(repository, { pushType: PushType.PushTo, forcePush: true });
J
Joao Moreno 已提交
1802 1803
	}

J
Joao Moreno 已提交
1804
	private async _sync(repository: Repository, rebase: boolean): Promise<void> {
J
Joao Moreno 已提交
1805
		const HEAD = repository.HEAD;
J
Joao Moreno 已提交
1806 1807 1808 1809 1810

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

J
Joao Moreno 已提交
1811 1812 1813 1814
		const remoteName = HEAD.remote || HEAD.upstream.remote;
		const remote = repository.remotes.find(r => r.name === remoteName);
		const isReadonly = remote && remote.isReadOnly;

J
Joao Moreno 已提交
1815
		const config = workspace.getConfiguration('git');
J
Joao Moreno 已提交
1816
		const shouldPrompt = !isReadonly && config.get<boolean>('confirmSync') === true;
J
Joao Moreno 已提交
1817 1818

		if (shouldPrompt) {
J
Joao Moreno 已提交
1819
			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 已提交
1820
			const yes = localize('ok', "OK");
B
Benjamin Pasero 已提交
1821
			const neverAgain = localize('never again', "OK, Don't Show Again");
J
Joao Moreno 已提交
1822 1823 1824 1825 1826 1827 1828 1829 1830
			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 已提交
1831
		if (rebase) {
J
Joao Moreno 已提交
1832
			await repository.syncRebase(HEAD);
J
Joao Moreno 已提交
1833
		} else {
J
Joao Moreno 已提交
1834
			await repository.sync(HEAD);
J
Joao Moreno 已提交
1835 1836 1837 1838 1839 1840
		}
	}

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

J
Joao 已提交
1843 1844 1845 1846 1847 1848 1849 1850 1851
	@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 已提交
1852
			await repository.sync(HEAD);
J
Joao 已提交
1853 1854 1855
		}));
	}

1856
	@command('git.syncRebase', { repository: true })
J
Joao Moreno 已提交
1857 1858
	syncRebase(repository: Repository): Promise<void> {
		return this._sync(repository, true);
J
Joao Moreno 已提交
1859 1860
	}

J
Joao Moreno 已提交
1861
	@command('git.publish', { repository: true })
J
Joao Moreno 已提交
1862 1863
	async publish(repository: Repository): Promise<void> {
		const remotes = repository.remotes;
J
Joao Moreno 已提交
1864 1865 1866 1867 1868 1869

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

J
Joao Moreno 已提交
1870
		const branchName = repository.HEAD && repository.HEAD.name || '';
M
Markus Wolf 已提交
1871 1872 1873 1874 1875 1876
		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 已提交
1877 1878 1879 1880 1881

		if (!choice) {
			return;
		}

J
Joao Moreno 已提交
1882
		await repository.pushTo(choice, branchName, true);
J
Joao Moreno 已提交
1883 1884
	}

1885 1886
	@command('git.ignore')
	async ignore(...resourceStates: SourceControlResourceState[]): Promise<void> {
1887 1888
		resourceStates = resourceStates.filter(s => !!s);

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

1892
			if (!resource) {
J
Joao Moreno 已提交
1893 1894 1895
				return;
			}

1896
			resourceStates = [resource];
J
Joao Moreno 已提交
1897 1898
		}

1899
		const resources = resourceStates
J
Joao Moreno 已提交
1900 1901 1902
			.filter(s => s instanceof Resource)
			.map(r => r.resourceUri);

1903
		if (!resources.length) {
N
NKumar2 已提交
1904 1905 1906
			return;
		}

1907
		await this.runByRepository(resources, async (repository, resources) => repository.ignore(resources));
N
NKumar2 已提交
1908 1909
	}

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

1914
		if (noUnstagedChanges && noStagedChanges) {
K
Krzysztof Cieślak 已提交
1915 1916 1917
			window.showInformationMessage(localize('no changes stash', "There are no changes to stash."));
			return;
		}
J
Joao Moreno 已提交
1918

1919
		const message = await this.getStashMessage();
J
Joao Moreno 已提交
1920 1921 1922 1923 1924

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

J
Joao Moreno 已提交
1925
		await repository.createStash(message, includeUntracked);
K
Krzysztof Cieślak 已提交
1926 1927
	}

1928 1929 1930 1931 1932 1933 1934
	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 已提交
1935 1936 1937 1938 1939 1940 1941 1942 1943 1944
	@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 已提交
1945
	@command('git.stashPop', { repository: true })
J
Joao Moreno 已提交
1946
	async stashPop(repository: Repository): Promise<void> {
1947
		const placeHolder = localize('pick stash to pop', "Pick a stash to pop");
J
Joao Moreno 已提交
1948
		const stash = await this.pickStash(repository, placeHolder);
1949

J
Joao Moreno 已提交
1950
		if (!stash) {
1951 1952 1953
			return;
		}

J
Joao Moreno 已提交
1954
		await repository.popStash(stash.index);
1955 1956 1957 1958
	}

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

J
Joao Moreno 已提交
1961 1962
		if (stashes.length === 0) {
			window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
K
Krzysztof Cieślak 已提交
1963 1964 1965
			return;
		}

1966 1967 1968 1969 1970 1971
		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 已提交
1972
		const stash = await this.pickStash(repository, placeHolder);
K
Krzysztof Cieślak 已提交
1973

J
Joao Moreno 已提交
1974
		if (!stash) {
K
Krzysztof Cieślak 已提交
1975 1976
			return;
		}
J
Joao Moreno 已提交
1977

J
Joao Moreno 已提交
1978
		await repository.applyStash(stash.index);
K
Krzysztof Cieślak 已提交
1979 1980
	}

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

J
Joao Moreno 已提交
1985 1986
		if (stashes.length === 0) {
			window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
1987 1988 1989 1990 1991 1992
			return;
		}

		await repository.applyStash();
	}

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

J
Joao Moreno 已提交
1996 1997
		if (stashes.length === 0) {
			window.showInformationMessage(localize('no stashes', "There are no stashes in the repository."));
K
Krzysztof Cieślak 已提交
1998 1999 2000
			return;
		}

J
Joao Moreno 已提交
2001 2002 2003
		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 已提交
2004
	}
K
Krzysztof Cieślak 已提交
2005

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

J
Joao Moreno 已提交
2010
			if (!options.repository) {
J
Joao Moreno 已提交
2011 2012
				result = Promise.resolve(method.apply(this, args));
			} else {
J
Joao Moreno 已提交
2013 2014
				// try to guess the repository based on the first argument
				const repository = this.model.getRepository(args[0]);
J
Joao Moreno 已提交
2015 2016 2017 2018 2019 2020 2021 2022 2023
				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();
				}
2024

J
Joao Moreno 已提交
2025
				result = repositoryPromise.then(repository => {
J
Joao Moreno 已提交
2026
					if (!repository) {
J
Joao 已提交
2027
						return Promise.resolve();
J
Joao Moreno 已提交
2028 2029
					}

J
Joao Moreno 已提交
2030
					return Promise.resolve(method.apply(this, [repository, ...args]));
J
Joao Moreno 已提交
2031
				});
J
Joao Moreno 已提交
2032 2033
			}

K
kieferrm 已提交
2034
			/* __GDPR__
K
kieferrm 已提交
2035 2036 2037 2038
				"git.command" : {
					"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
J
Joao Moreno 已提交
2039 2040
			this.telemetryReporter.sendTelemetryEvent('git.command', { command: id });

J
Joao Moreno 已提交
2041
			return result.catch(async err => {
J
Joao Moreno 已提交
2042
				const options: MessageOptions = {
2043
					modal: true
J
Joao Moreno 已提交
2044 2045
				};

J
Joao Moreno 已提交
2046
				let message: string;
2047
				let type: 'error' | 'warning' = 'error';
J
Joao Moreno 已提交
2048

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

						message = hint
							? localize('git error details', "Git: {0}", hint)
							: localize('git error', "Git error");
J
Joao Moreno 已提交
2087 2088 2089 2090 2091 2092 2093 2094 2095

						break;
				}

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

J
Joao Moreno 已提交
2096 2097 2098 2099 2100 2101 2102
				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 已提交
2103

J
Joao Moreno 已提交
2104 2105 2106
					if (resultFn) {
						resultFn();
					}
J
Joao Moreno 已提交
2107 2108 2109
				}
			});
		};
2110 2111

		// patch this object, so people can call methods directly
2112
		(this as any)[key] = result;
2113 2114

		return result;
J
Joao Moreno 已提交
2115 2116
	}

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

J
Joao Moreno 已提交
2120
		this.outputChannel.appendLine(`git.getSCMResource.uri ${uri && uri.toString()}`);
J
Joao Moreno 已提交
2121 2122 2123
		for (const r of this.model.repositories.map(r => r.root)) {
			this.outputChannel.appendLine(`repo root ${r}`);
		}
J
Joao Moreno 已提交
2124

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

		if (uri.scheme === 'git') {
J
Joao Moreno 已提交
2130 2131
			const { path } = fromGitUri(uri);
			uri = Uri.file(path);
J
Joao Moreno 已提交
2132 2133 2134 2135
		}

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

J
Joao Moreno 已提交
2138
			if (!repository) {
2139 2140
				return undefined;
			}
J
Joao Moreno 已提交
2141

J
Joao Moreno 已提交
2142 2143
			return repository.workingTreeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]
				|| repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0];
J
Joao Moreno 已提交
2144
		}
2145
		return undefined;
J
Joao Moreno 已提交
2146 2147
	}

J
Joao Moreno 已提交
2148
	private runByRepository<T>(resource: Uri, fn: (repository: Repository, resource: Uri) => Promise<T>): Promise<T[]>;
J
Joao Moreno 已提交
2149 2150 2151 2152 2153 2154
	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) => {
2155
			let repository = this.model.getRepository(resource);
J
Joao Moreno 已提交
2156 2157 2158 2159 2160 2161

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

2162
			// Could it be a submodule?
2163
			if (pathEquals(resource.fsPath, repository.root)) {
2164 2165 2166
				repository = this.model.getRepositoryForSubmodule(resource) || repository;
			}

J
Joao Moreno 已提交
2167
			const tuple = result.filter(p => p.repository === repository)[0];
J
Joao Moreno 已提交
2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183

			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 已提交
2184 2185 2186
	dispose(): void {
		this.disposables.forEach(d => d.dispose());
	}
J
João Moreno 已提交
2187
}