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

'use strict';

J
Joao Moreno 已提交
8
import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType } from 'vscode';
J
Joao Moreno 已提交
9
import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git';
J
Joao Moreno 已提交
10
import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent } from './util';
J
Joao Moreno 已提交
11
import { memoize, throttle, debounce } from './decorators';
J
Joao Moreno 已提交
12
import { toGitUri } from './uri';
J
Joao Moreno 已提交
13
import { AutoFetcher } from './autofetch';
J
Joao Moreno 已提交
14
import * as path from 'path';
J
Joao Moreno 已提交
15
import * as nls from 'vscode-nls';
16
import * as fs from 'fs';
M
Matt Bierner 已提交
17
import { StatusBarCommands } from './statusbar';
J
Joao Moreno 已提交
18
import { Branch, Ref, Remote, RefType, GitErrorCodes } from './api/git';
J
Joao Moreno 已提交
19

J
Joao Moreno 已提交
20 21
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));

J
Joao Moreno 已提交
22
const localize = nls.loadMessageBundle();
J
Joao Moreno 已提交
23 24 25 26 27 28
const iconsRootPath = path.join(path.dirname(__dirname), 'resources', 'icons');

function getIconUri(iconName: string, theme: string): Uri {
	return Uri.file(path.join(iconsRootPath, theme, `${iconName}.svg`));
}

29
export const enum RepositoryState {
J
Joao Moreno 已提交
30
	Idle,
J
Joao Moreno 已提交
31
	Disposed
J
Joao Moreno 已提交
32 33
}

34
export const enum Status {
J
Joao Moreno 已提交
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
	INDEX_MODIFIED,
	INDEX_ADDED,
	INDEX_DELETED,
	INDEX_RENAMED,
	INDEX_COPIED,

	MODIFIED,
	DELETED,
	UNTRACKED,
	IGNORED,

	ADDED_BY_US,
	ADDED_BY_THEM,
	DELETED_BY_US,
	DELETED_BY_THEM,
	BOTH_ADDED,
	BOTH_DELETED,
	BOTH_MODIFIED
}

55
export const enum ResourceGroupType {
J
Joao Moreno 已提交
56 57 58 59 60
	Merge,
	Index,
	WorkingTree
}

J
Joao Moreno 已提交
61
export class Resource implements SourceControlResourceState {
J
Joao Moreno 已提交
62

63
	@memoize
J
Joao Moreno 已提交
64
	get resourceUri(): Uri {
J
Joao Moreno 已提交
65
		if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED)) {
J
Joao Moreno 已提交
66 67 68 69
			return this.renameResourceUri;
		}

		return this._resourceUri;
70 71 72
	}

	@memoize
J
Joao Moreno 已提交
73 74 75 76 77 78
	get command(): Command {
		return {
			command: 'git.openResource',
			title: localize('open', "Open"),
			arguments: [this]
		};
J
Joao Moreno 已提交
79 80
	}

J
Joao Moreno 已提交
81
	get resourceGroupType(): ResourceGroupType { return this._resourceGroupType; }
J
Joao Moreno 已提交
82
	get type(): Status { return this._type; }
J
Joao Moreno 已提交
83 84
	get original(): Uri { return this._resourceUri; }
	get renameResourceUri(): Uri | undefined { return this._renameResourceUri; }
J
Joao Moreno 已提交
85

86
	private static Icons: any = {
J
Joao Moreno 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
		light: {
			Modified: getIconUri('status-modified', 'light'),
			Added: getIconUri('status-added', 'light'),
			Deleted: getIconUri('status-deleted', 'light'),
			Renamed: getIconUri('status-renamed', 'light'),
			Copied: getIconUri('status-copied', 'light'),
			Untracked: getIconUri('status-untracked', 'light'),
			Ignored: getIconUri('status-ignored', 'light'),
			Conflict: getIconUri('status-conflict', 'light'),
		},
		dark: {
			Modified: getIconUri('status-modified', 'dark'),
			Added: getIconUri('status-added', 'dark'),
			Deleted: getIconUri('status-deleted', 'dark'),
			Renamed: getIconUri('status-renamed', 'dark'),
			Copied: getIconUri('status-copied', 'dark'),
			Untracked: getIconUri('status-untracked', 'dark'),
			Ignored: getIconUri('status-ignored', 'dark'),
			Conflict: getIconUri('status-conflict', 'dark')
		}
	};

J
Joao Moreno 已提交
109
	private getIconPath(theme: string): Uri {
J
Joao Moreno 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
		switch (this.type) {
			case Status.INDEX_MODIFIED: return Resource.Icons[theme].Modified;
			case Status.MODIFIED: return Resource.Icons[theme].Modified;
			case Status.INDEX_ADDED: return Resource.Icons[theme].Added;
			case Status.INDEX_DELETED: return Resource.Icons[theme].Deleted;
			case Status.DELETED: return Resource.Icons[theme].Deleted;
			case Status.INDEX_RENAMED: return Resource.Icons[theme].Renamed;
			case Status.INDEX_COPIED: return Resource.Icons[theme].Copied;
			case Status.UNTRACKED: return Resource.Icons[theme].Untracked;
			case Status.IGNORED: return Resource.Icons[theme].Ignored;
			case Status.BOTH_DELETED: return Resource.Icons[theme].Conflict;
			case Status.ADDED_BY_US: return Resource.Icons[theme].Conflict;
			case Status.DELETED_BY_THEM: return Resource.Icons[theme].Conflict;
			case Status.ADDED_BY_THEM: return Resource.Icons[theme].Conflict;
			case Status.DELETED_BY_US: return Resource.Icons[theme].Conflict;
			case Status.BOTH_ADDED: return Resource.Icons[theme].Conflict;
			case Status.BOTH_MODIFIED: return Resource.Icons[theme].Conflict;
		}
	}

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
	private get tooltip(): string {
		switch (this.type) {
			case Status.INDEX_MODIFIED: return localize('index modified', "Index Modified");
			case Status.MODIFIED: return localize('modified', "Modified");
			case Status.INDEX_ADDED: return localize('index added', "Index Added");
			case Status.INDEX_DELETED: return localize('index deleted', "Index Deleted");
			case Status.DELETED: return localize('deleted', "Deleted");
			case Status.INDEX_RENAMED: return localize('index renamed', "Index Renamed");
			case Status.INDEX_COPIED: return localize('index copied', "Index Copied");
			case Status.UNTRACKED: return localize('untracked', "Untracked");
			case Status.IGNORED: return localize('ignored', "Ignored");
			case Status.BOTH_DELETED: return localize('both deleted', "Both Deleted");
			case Status.ADDED_BY_US: return localize('added by us', "Added By Us");
			case Status.DELETED_BY_THEM: return localize('deleted by them', "Deleted By Them");
			case Status.ADDED_BY_THEM: return localize('added by them', "Added By Them");
			case Status.DELETED_BY_US: return localize('deleted by us', "Deleted By Us");
			case Status.BOTH_ADDED: return localize('both added', "Both Added");
			case Status.BOTH_MODIFIED: return localize('both modified', "Both Modified");
			default: return '';
		}
	}

J
Joao Moreno 已提交
152 153 154 155 156 157
	private get strikeThrough(): boolean {
		switch (this.type) {
			case Status.DELETED:
			case Status.BOTH_DELETED:
			case Status.DELETED_BY_THEM:
			case Status.DELETED_BY_US:
J
Joao Moreno 已提交
158
			case Status.INDEX_DELETED:
J
Joao Moreno 已提交
159 160 161 162 163 164
				return true;
			default:
				return false;
		}
	}

165 166
	@memoize
	private get faded(): boolean {
167 168 169 170
		// TODO@joao
		return false;
		// const workspaceRootPath = this.workspaceRoot.fsPath;
		// return this.resourceUri.fsPath.substr(0, workspaceRootPath.length) !== workspaceRootPath;
171 172
	}

J
Joao Moreno 已提交
173
	get decorations(): SourceControlResourceDecorations {
174 175
		const light = this._useIcons ? { iconPath: this.getIconPath('light') } : undefined;
		const dark = this._useIcons ? { iconPath: this.getIconPath('dark') } : undefined;
176
		const tooltip = this.tooltip;
177 178
		const strikeThrough = this.strikeThrough;
		const faded = this.faded;
179 180
		const letter = this.letter;
		const color = this.color;
J
Joao Moreno 已提交
181

182
		return { strikeThrough, faded, tooltip, light, dark, letter, color, source: 'git.resource' /*todo@joh*/ };
183 184
	}

N
Nick Snyder 已提交
185
	get letter(): string {
186
		switch (this.type) {
187 188 189 190 191 192 193 194 195 196
			case Status.INDEX_MODIFIED:
			case Status.MODIFIED:
				return 'M';
			case Status.INDEX_ADDED:
				return 'A';
			case Status.INDEX_DELETED:
			case Status.DELETED:
				return 'D';
			case Status.INDEX_RENAMED:
				return 'R';
197
			case Status.UNTRACKED:
198 199 200
				return 'U';
			case Status.IGNORED:
				return 'I';
J
Joao Moreno 已提交
201 202 203 204
			case Status.DELETED_BY_THEM:
				return 'D';
			case Status.DELETED_BY_US:
				return 'D';
205 206 207 208 209 210 211 212 213 214
			case Status.INDEX_COPIED:
			case Status.BOTH_DELETED:
			case Status.ADDED_BY_US:
			case Status.ADDED_BY_THEM:
			case Status.BOTH_ADDED:
			case Status.BOTH_MODIFIED:
				return 'C';
		}
	}

N
Nick Snyder 已提交
215
	get color(): ThemeColor {
216
		switch (this.type) {
217 218
			case Status.INDEX_MODIFIED:
			case Status.MODIFIED:
J
Johannes Rieken 已提交
219
				return new ThemeColor('gitDecoration.modifiedResourceForeground');
220 221
			case Status.INDEX_DELETED:
			case Status.DELETED:
J
Johannes Rieken 已提交
222
				return new ThemeColor('gitDecoration.deletedResourceForeground');
223 224
			case Status.INDEX_ADDED:
				return new ThemeColor('gitDecoration.addedResourceForeground');
225 226
			case Status.INDEX_RENAMED: // todo@joh - special color?
			case Status.UNTRACKED:
J
Johannes Rieken 已提交
227
				return new ThemeColor('gitDecoration.untrackedResourceForeground');
228
			case Status.IGNORED:
J
Johannes Rieken 已提交
229
				return new ThemeColor('gitDecoration.ignoredResourceForeground');
230 231 232 233 234 235 236 237
			case Status.INDEX_COPIED:
			case Status.BOTH_DELETED:
			case Status.ADDED_BY_US:
			case Status.DELETED_BY_THEM:
			case Status.ADDED_BY_THEM:
			case Status.DELETED_BY_US:
			case Status.BOTH_ADDED:
			case Status.BOTH_MODIFIED:
J
Johannes Rieken 已提交
238
				return new ThemeColor('gitDecoration.conflictingResourceForeground');
239
		}
J
Joao Moreno 已提交
240 241
	}

J
Johannes Rieken 已提交
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
	get priority(): number {
		switch (this.type) {
			case Status.INDEX_MODIFIED:
			case Status.MODIFIED:
				return 2;
			case Status.IGNORED:
				return 3;
			case Status.INDEX_COPIED:
			case Status.BOTH_DELETED:
			case Status.ADDED_BY_US:
			case Status.DELETED_BY_THEM:
			case Status.ADDED_BY_THEM:
			case Status.DELETED_BY_US:
			case Status.BOTH_ADDED:
			case Status.BOTH_MODIFIED:
				return 4;
			default:
				return 1;
		}
	}

N
Nick Snyder 已提交
263
	get resourceDecoration(): DecorationData {
264
		const title = this.tooltip;
265
		const letter = this.letter;
266
		const color = this.color;
J
Johannes Rieken 已提交
267
		const priority = this.priority;
268
		return { bubble: true, source: 'git.resource', title, letter, color, priority };
269 270
	}

271
	constructor(
J
Joao Moreno 已提交
272
		private _resourceGroupType: ResourceGroupType,
273 274
		private _resourceUri: Uri,
		private _type: Status,
275
		private _useIcons: boolean,
276 277
		private _renameResourceUri?: Uri
	) { }
J
Joao Moreno 已提交
278 279
}

280
export const enum Operation {
J
Joao Moreno 已提交
281
	Status = 'Status',
J
Joao Moreno 已提交
282
	Config = 'Config',
283
	Diff = 'Diff',
J
Joao Moreno 已提交
284
	MergeBase = 'MergeBase',
J
Joao Moreno 已提交
285
	Add = 'Add',
J
Joao Moreno 已提交
286
	Remove = 'Remove',
J
Joao Moreno 已提交
287 288 289 290
	RevertFiles = 'RevertFiles',
	Commit = 'Commit',
	Clean = 'Clean',
	Branch = 'Branch',
J
Joao Moreno 已提交
291 292 293
	GetBranch = 'GetBranch',
	SetBranchUpstream = 'SetBranchUpstream',
	HashObject = 'HashObject',
J
Joao Moreno 已提交
294
	Checkout = 'Checkout',
295
	CheckoutTracking = 'CheckoutTracking',
J
Joao Moreno 已提交
296
	Reset = 'Reset',
J
Joao Moreno 已提交
297
	Remote = 'Remote',
J
Joao Moreno 已提交
298 299 300 301 302 303 304 305
	Fetch = 'Fetch',
	Pull = 'Pull',
	Push = 'Push',
	Sync = 'Sync',
	Show = 'Show',
	Stage = 'Stage',
	GetCommitTemplate = 'GetCommitTemplate',
	DeleteBranch = 'DeleteBranch',
306
	RenameBranch = 'RenameBranch',
307
	DeleteRef = 'DeleteRef',
J
Joao Moreno 已提交
308 309 310 311
	Merge = 'Merge',
	Ignore = 'Ignore',
	Tag = 'Tag',
	Stash = 'Stash',
J
Joao Moreno 已提交
312
	CheckIgnore = 'CheckIgnore',
J
Joao Moreno 已提交
313
	GetObjectDetails = 'GetObjectDetails',
314 315
	SubmoduleUpdate = 'SubmoduleUpdate',
	RebaseContinue = 'RebaseContinue',
316 317
}

J
Joao Moreno 已提交
318 319 320 321
function isReadOnly(operation: Operation): boolean {
	switch (operation) {
		case Operation.Show:
		case Operation.GetCommitTemplate:
322
		case Operation.CheckIgnore:
J
Joao Moreno 已提交
323
		case Operation.GetObjectDetails:
324
		case Operation.MergeBase:
J
Joao Moreno 已提交
325 326 327 328 329 330
			return true;
		default:
			return false;
	}
}

331 332 333
function shouldShowProgress(operation: Operation): boolean {
	switch (operation) {
		case Operation.Fetch:
334
		case Operation.CheckIgnore:
J
Joao Moreno 已提交
335
		case Operation.GetObjectDetails:
J
Joao Moreno 已提交
336
		case Operation.Show:
337 338 339 340 341 342
			return false;
		default:
			return true;
	}
}

343
export interface Operations {
344
	isIdle(): boolean;
J
Joao Moreno 已提交
345
	shouldShowProgress(): boolean;
346 347 348 349 350
	isRunning(operation: Operation): boolean;
}

class OperationsImpl implements Operations {

J
Joao Moreno 已提交
351
	private operations = new Map<Operation, number>();
352

J
Joao Moreno 已提交
353 354
	start(operation: Operation): void {
		this.operations.set(operation, (this.operations.get(operation) || 0) + 1);
355 356
	}

J
Joao Moreno 已提交
357 358 359 360 361 362 363 364
	end(operation: Operation): void {
		const count = (this.operations.get(operation) || 0) - 1;

		if (count <= 0) {
			this.operations.delete(operation);
		} else {
			this.operations.set(operation, count);
		}
365 366 367
	}

	isRunning(operation: Operation): boolean {
J
Joao Moreno 已提交
368
		return this.operations.has(operation);
369
	}
370 371

	isIdle(): boolean {
J
Joao Moreno 已提交
372 373 374 375 376 377 378 379 380
		const operations = this.operations.keys();

		for (const operation of operations) {
			if (!isReadOnly(operation)) {
				return false;
			}
		}

		return true;
381
	}
J
Joao Moreno 已提交
382 383 384 385 386 387 388 389 390 391 392 393

	shouldShowProgress(): boolean {
		const operations = this.operations.keys();

		for (const operation of operations) {
			if (shouldShowProgress(operation)) {
				return true;
			}
		}

		return false;
	}
394 395
}

J
Joao Moreno 已提交
396 397 398 399
export interface GitResourceGroup extends SourceControlResourceGroup {
	resourceStates: Resource[];
}

J
Joao Moreno 已提交
400 401 402 403 404
export interface OperationResult {
	operation: Operation;
	error: any;
}

J
Joao Moreno 已提交
405 406
class ProgressManager {

J
Joao Moreno 已提交
407
	private enabled = false;
J
Joao Moreno 已提交
408 409
	private disposable: IDisposable = EmptyDisposable;

J
Joao Moreno 已提交
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
	constructor(private repository: Repository) {
		const onDidChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git', Uri.file(this.repository.root)));
		onDidChange(_ => this.updateEnablement());
		this.updateEnablement();
	}

	private updateEnablement(): void {
		const config = workspace.getConfiguration('git', Uri.file(this.repository.root));

		if (config.get<boolean>('showProgress')) {
			this.enable();
		} else {
			this.disable();
		}
	}

	private enable(): void {
		if (this.enabled) {
			return;
		}

		const start = onceEvent(filterEvent(this.repository.onDidChangeOperations, () => this.repository.operations.shouldShowProgress()));
		const end = onceEvent(filterEvent(debounceEvent(this.repository.onDidChangeOperations, 300), () => !this.repository.operations.shouldShowProgress()));
J
Joao Moreno 已提交
433 434 435 436 437 438 439 440 441

		const setup = () => {
			this.disposable = start(() => {
				const promise = eventToPromise(end).then(() => setup());
				window.withProgress({ location: ProgressLocation.SourceControl }, () => promise);
			});
		};

		setup();
J
Joao Moreno 已提交
442
		this.enabled = true;
J
Joao Moreno 已提交
443 444
	}

J
Joao Moreno 已提交
445 446 447 448 449
	private disable(): void {
		if (!this.enabled) {
			return;
		}

J
Joao Moreno 已提交
450
		this.disposable.dispose();
J
Joao Moreno 已提交
451 452 453 454 455 456
		this.disposable = EmptyDisposable;
		this.enabled = false;
	}

	dispose(): void {
		this.disable();
J
Joao Moreno 已提交
457 458 459
	}
}

J
Joao Moreno 已提交
460
export class Repository implements Disposable {
J
Joao Moreno 已提交
461

J
Joao Moreno 已提交
462 463 464
	private _onDidChangeRepository = new EventEmitter<Uri>();
	readonly onDidChangeRepository: Event<Uri> = this._onDidChangeRepository.event;

J
Joao 已提交
465 466
	private _onDidChangeState = new EventEmitter<RepositoryState>();
	readonly onDidChangeState: Event<RepositoryState> = this._onDidChangeState.event;
J
Joao Moreno 已提交
467

J
Joao Moreno 已提交
468
	private _onDidChangeStatus = new EventEmitter<void>();
J
Joao Moreno 已提交
469
	readonly onDidRunGitStatus: Event<void> = this._onDidChangeStatus.event;
J
Joao Moreno 已提交
470

J
Joao Moreno 已提交
471 472 473
	private _onDidChangeOriginalResource = new EventEmitter<Uri>();
	readonly onDidChangeOriginalResource: Event<Uri> = this._onDidChangeOriginalResource.event;

474 475 476
	private _onRunOperation = new EventEmitter<Operation>();
	readonly onRunOperation: Event<Operation> = this._onRunOperation.event;

J
Joao Moreno 已提交
477 478
	private _onDidRunOperation = new EventEmitter<OperationResult>();
	readonly onDidRunOperation: Event<OperationResult> = this._onDidRunOperation.event;
479 480 481 482 483 484

	@memoize
	get onDidChangeOperations(): Event<void> {
		return anyEvent(this.onRunOperation as Event<any>, this.onDidRunOperation as Event<any>);
	}

J
Joao Moreno 已提交
485 486 487
	private _sourceControl: SourceControl;
	get sourceControl(): SourceControl { return this._sourceControl; }

J
Joao Moreno 已提交
488 489
	get inputBox(): SourceControlInputBox { return this._sourceControl.inputBox; }

J
Joao Moreno 已提交
490 491
	private _mergeGroup: SourceControlResourceGroup;
	get mergeGroup(): GitResourceGroup { return this._mergeGroup as GitResourceGroup; }
J
Joao Moreno 已提交
492

J
Joao Moreno 已提交
493 494
	private _indexGroup: SourceControlResourceGroup;
	get indexGroup(): GitResourceGroup { return this._indexGroup as GitResourceGroup; }
J
Joao Moreno 已提交
495

J
Joao Moreno 已提交
496 497
	private _workingTreeGroup: SourceControlResourceGroup;
	get workingTreeGroup(): GitResourceGroup { return this._workingTreeGroup as GitResourceGroup; }
J
Joao Moreno 已提交
498

J
Joao Moreno 已提交
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
	private _HEAD: Branch | undefined;
	get HEAD(): Branch | undefined {
		return this._HEAD;
	}

	private _refs: Ref[] = [];
	get refs(): Ref[] {
		return this._refs;
	}

	private _remotes: Remote[] = [];
	get remotes(): Remote[] {
		return this._remotes;
	}

514 515 516 517 518
	private _submodules: Submodule[] = [];
	get submodules(): Submodule[] {
		return this._submodules;
	}

519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
	private _rebaseCommit: Commit | undefined = undefined;

	set rebaseCommit(rebaseCommit: Commit | undefined) {
		if (this._rebaseCommit && !rebaseCommit) {
			this.inputBox.value = '';
		} else if (rebaseCommit && (!this._rebaseCommit || this._rebaseCommit.hash !== rebaseCommit.hash)) {
			this.inputBox.value = rebaseCommit.message;
		}

		this._rebaseCommit = rebaseCommit;
	}

	get rebaseCommit(): Commit | undefined {
		return this._rebaseCommit;
	}

535 536 537
	private _operations = new OperationsImpl();
	get operations(): Operations { return this._operations; }

J
Joao 已提交
538 539 540
	private _state = RepositoryState.Idle;
	get state(): RepositoryState { return this._state; }
	set state(state: RepositoryState) {
J
Joao Moreno 已提交
541 542
		this._state = state;
		this._onDidChangeState.fire(state);
J
Joao Moreno 已提交
543 544 545 546

		this._HEAD = undefined;
		this._refs = [];
		this._remotes = [];
J
Joao Moreno 已提交
547 548 549 550
		this.mergeGroup.resourceStates = [];
		this.indexGroup.resourceStates = [];
		this.workingTreeGroup.resourceStates = [];
		this._sourceControl.count = 0;
J
Joao Moreno 已提交
551 552
	}

553 554 555 556
	get root(): string {
		return this.repository.root;
	}

557 558
	private isRepositoryHuge = false;
	private didWarnAboutLimit = false;
J
Joao Moreno 已提交
559
	private disposables: Disposable[] = [];
J
Joao Moreno 已提交
560

561
	constructor(
J
Joao Moreno 已提交
562 563
		private readonly repository: BaseRepository,
		globalState: Memento
564
	) {
J
Joao Moreno 已提交
565 566 567
		const fsWatcher = workspace.createFileSystemWatcher('**');
		this.disposables.push(fsWatcher);

J
Joao Moreno 已提交
568 569 570 571 572 573 574 575 576 577 578 579 580 581
		const workspaceFilter = (uri: Uri) => isDescendant(repository.root, uri.fsPath);
		const onWorkspaceDelete = filterEvent(fsWatcher.onDidDelete, workspaceFilter);
		const onWorkspaceChange = filterEvent(anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate), workspaceFilter);
		const onRepositoryDotGitDelete = filterEvent(onWorkspaceDelete, uri => /\/\.git$/.test(uri.path));
		const onRepositoryChange = anyEvent(onWorkspaceDelete, onWorkspaceChange);

		// relevant repository changes are:
		//  - DELETE .git folder
		//  - ANY CHANGE within .git folder except .git itself and .git/index.lock
		const onRelevantRepositoryChange = anyEvent(
			onRepositoryDotGitDelete,
			filterEvent(onRepositoryChange, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path))
		);

J
Joao Moreno 已提交
582
		onRelevantRepositoryChange(this.onFSChange, this, this.disposables);
583

J
Joao Moreno 已提交
584
		const onRelevantGitChange = filterEvent(onRelevantRepositoryChange, uri => /\/\.git\//.test(uri.path));
J
Joao Moreno 已提交
585 586
		onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);

J
Joao Moreno 已提交
587 588
		const root = Uri.file(repository.root);
		this._sourceControl = scm.createSourceControl('git', 'Git', root);
589
		this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message (press {0} to commit)");
J
Joao Moreno 已提交
590
		this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] };
J
Joao Moreno 已提交
591
		this._sourceControl.quickDiffProvider = this;
J
Joao Moreno 已提交
592
		this._sourceControl.inputBox.validateInput = this.validateInput.bind(this);
J
Joao Moreno 已提交
593 594 595 596 597 598
		this.disposables.push(this._sourceControl);

		this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes"));
		this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes"));
		this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes"));

J
Joao Moreno 已提交
599 600 601 602 603 604 605 606
		const updateIndexGroupVisibility = () => {
			const config = workspace.getConfiguration('git', root);
			this.indexGroup.hideWhenEmpty = !config.get<boolean>('alwaysShowStagedChangesResourceGroup');
		};

		const onConfigListener = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.alwaysShowStagedChangesResourceGroup', root));
		onConfigListener(updateIndexGroupVisibility, this, this.disposables);
		updateIndexGroupVisibility();
W
wistcc 已提交
607

J
Joao Moreno 已提交
608 609 610 611 612 613
		this.mergeGroup.hideWhenEmpty = true;

		this.disposables.push(this.mergeGroup);
		this.disposables.push(this.indexGroup);
		this.disposables.push(this.workingTreeGroup);

J
Joao Moreno 已提交
614
		this.disposables.push(new AutoFetcher(this, globalState));
J
Joao Moreno 已提交
615

616 617 618 619 620 621 622 623 624 625
		// https://github.com/Microsoft/vscode/issues/39039
		const onSuccessfulPush = filterEvent(this.onDidRunOperation, e => e.operation === Operation.Push && !e.error);
		onSuccessfulPush(() => {
			const gitConfig = workspace.getConfiguration('git');

			if (gitConfig.get<boolean>('showPushSuccessNotification')) {
				window.showInformationMessage(localize('push success', "Successfully pushed."));
			}
		}, null, this.disposables);

J
Joao Moreno 已提交
626 627 628 629 630
		const statusBar = new StatusBarCommands(this);
		this.disposables.push(statusBar);
		statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables);
		this._sourceControl.statusBarCommands = statusBar.commands;

J
Joao Moreno 已提交
631 632 633
		const progressManager = new ProgressManager(this);
		this.disposables.push(progressManager);

J
Joao Moreno 已提交
634
		this.updateCommitTemplate();
J
Joao Moreno 已提交
635
		this.status();
J
Joao Moreno 已提交
636 637
	}

638
	validateInput(text: string, position: number): SourceControlInputBoxValidation | undefined {
639 640 641 642 643 644 645 646 647
		if (this.rebaseCommit) {
			if (this.rebaseCommit.message !== text) {
				return {
					message: localize('commit in rebase', "It's not possible to change the commit message in the middle of a rebase. Please complete the rebase operation and use interactive rebase instead."),
					type: SourceControlInputBoxValidationType.Warning
				};
			}
		}

648 649 650 651 652 653 654
		const config = workspace.getConfiguration('git');
		const setting = config.get<'always' | 'warn' | 'off'>('inputValidation');

		if (setting === 'off') {
			return;
		}

J
Joao Moreno 已提交
655
		if (/^\s+$/.test(text)) {
656
			return {
J
Joao Moreno 已提交
657
				message: localize('commitMessageWhitespacesOnlyWarning', "Current commit message only contains whitespace characters"),
658 659 660 661
				type: SourceControlInputBoxValidationType.Warning
			};
		}

662 663 664 665 666 667 668 669 670 671 672
		let start = 0, end;
		let match: RegExpExecArray | null;
		const regex = /\r?\n/g;

		while ((match = regex.exec(text)) && position > match.index) {
			start = match.index + match[0].length;
		}

		end = match ? match.index : text.length;

		const line = text.substring(start, end);
J
Joao Moreno 已提交
673
		const threshold = Math.max(config.get<number>('inputValidationLength') || 72, 0) || 72;
674

J
Joao Moreno 已提交
675
		if (line.length <= threshold) {
676 677 678 679 680
			if (setting !== 'always') {
				return;
			}

			return {
J
Joao Moreno 已提交
681
				message: localize('commitMessageCountdown', "{0} characters left in current line", threshold - line.length),
682 683 684 685
				type: SourceControlInputBoxValidationType.Information
			};
		} else {
			return {
J
Joao Moreno 已提交
686
				message: localize('commitMessageWarning', "{0} characters over {1} in current line", line.length - threshold, threshold),
687 688 689 690 691
				type: SourceControlInputBoxValidationType.Warning
			};
		}
	}

J
Joao Moreno 已提交
692 693 694 695 696
	provideOriginalResource(uri: Uri): Uri | undefined {
		if (uri.scheme !== 'file') {
			return;
		}

697
		return toGitUri(uri, '', { replaceFileExtension: true });
J
Joao Moreno 已提交
698 699 700 701 702 703 704 705 706 707
	}

	private async updateCommitTemplate(): Promise<void> {
		try {
			this._sourceControl.commitTemplate = await this.repository.getCommitTemplate();
		} catch (e) {
			// noop
		}
	}

J
Joao Moreno 已提交
708 709 710 711 712 713 714 715 716 717 718 719
	getConfigs(): Promise<{ key: string; value: string; }[]> {
		return this.run(Operation.Config, () => this.repository.getConfigs('local'));
	}

	getConfig(key: string): Promise<string> {
		return this.run(Operation.Config, () => this.repository.config('local', key));
	}

	setConfig(key: string, value: string): Promise<string> {
		return this.run(Operation.Config, () => this.repository.config('local', key, value));
	}

J
Joao Moreno 已提交
720
	@throttle
J
Joao Moreno 已提交
721 722
	async status(): Promise<void> {
		await this.run(Operation.Status);
J
Joao Moreno 已提交
723
	}
J
Joao Moreno 已提交
724

J
Joao Moreno 已提交
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
	diffWithHEAD(path: string): Promise<string> {
		return this.run(Operation.Diff, () => this.repository.diffWithHEAD(path));
	}

	diffWith(ref: string, path: string): Promise<string> {
		return this.run(Operation.Diff, () => this.repository.diffWith(ref, path));
	}

	diffIndexWithHEAD(path: string): Promise<string> {
		return this.run(Operation.Diff, () => this.repository.diffIndexWithHEAD(path));
	}

	diffIndexWith(ref: string, path: string): Promise<string> {
		return this.run(Operation.Diff, () => this.repository.diffIndexWith(ref, path));
	}

	diffBlobs(object1: string, object2: string): Promise<string> {
		return this.run(Operation.Diff, () => this.repository.diffBlobs(object1, object2));
	}

	diffBetween(ref1: string, ref2: string, path: string): Promise<string> {
		return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path));
	}

	getMergeBase(ref1: string, ref2: string): Promise<string> {
		return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2));
	}

	async hashObject(data: string): Promise<string> {
		return this.run(Operation.HashObject, () => this.repository.hashObject(data));
755 756
	}

J
Joao Moreno 已提交
757
	async add(resources: Uri[]): Promise<void> {
758
		await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath)));
J
Joao Moreno 已提交
759 760
	}

J
Joao Moreno 已提交
761 762 763 764
	async rm(resources: Uri[]): Promise<void> {
		await this.run(Operation.Remove, () => this.repository.rm(resources.map(r => r.fsPath)));
	}

J
Joao Moreno 已提交
765 766
	async stage(resource: Uri, contents: string): Promise<void> {
		const relativePath = path.relative(this.repository.root, resource.fsPath).replace(/\\/g, '/');
J
Joao Moreno 已提交
767
		await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents));
J
Joao Moreno 已提交
768
		this._onDidChangeOriginalResource.fire(resource);
J
Joao Moreno 已提交
769 770
	}

J
Joao Moreno 已提交
771 772
	async revert(resources: Uri[]): Promise<void> {
		await this.run(Operation.RevertFiles, () => this.repository.revert('HEAD', resources.map(r => r.fsPath)));
J
Joao Moreno 已提交
773
	}
J
Joao Moreno 已提交
774

J
Joao Moreno 已提交
775
	async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
776 777 778 779 780
		if (this.rebaseCommit) {
			await this.run(Operation.RebaseContinue, async () => {
				if (opts.all) {
					await this.repository.add([]);
				}
J
Joao Moreno 已提交
781

782 783 784 785 786 787 788 789 790 791 792
				await this.repository.rebaseContinue();
			});
		} else {
			await this.run(Operation.Commit, async () => {
				if (opts.all) {
					await this.repository.add([]);
				}

				await this.repository.commit(message, opts);
			});
		}
J
Joao Moreno 已提交
793
	}
J
Joao Moreno 已提交
794

J
Joao Moreno 已提交
795
	async clean(resources: Uri[]): Promise<void> {
796 797 798
		await this.run(Operation.Clean, async () => {
			const toClean: string[] = [];
			const toCheckout: string[] = [];
799
			const submodulesToUpdate: string[] = [];
800 801

			resources.forEach(r => {
802 803 804 805 806 807 808 809 810
				const fsPath = r.fsPath;

				for (const submodule of this.submodules) {
					if (path.join(this.root, submodule.path) === fsPath) {
						submodulesToUpdate.push(fsPath);
						return;
					}
				}

811
				const raw = r.toString();
J
Joao Moreno 已提交
812
				const scmResource = find(this.workingTreeGroup.resourceStates, sr => sr.resourceUri.toString() === raw);
813 814 815 816 817 818

				if (!scmResource) {
					return;
				}

				switch (scmResource.type) {
819 820
					case Status.UNTRACKED:
					case Status.IGNORED:
821
						toClean.push(fsPath);
822 823 824
						break;

					default:
825
						toCheckout.push(fsPath);
826 827 828
						break;
				}
			});
J
Joao Moreno 已提交
829

830
			const promises: Promise<void>[] = [];
J
Joao Moreno 已提交
831

832 833 834
			if (toClean.length > 0) {
				promises.push(this.repository.clean(toClean));
			}
J
Joao Moreno 已提交
835

836 837 838
			if (toCheckout.length > 0) {
				promises.push(this.repository.checkout('', toCheckout));
			}
J
Joao Moreno 已提交
839

840 841 842 843
			if (submodulesToUpdate.length > 0) {
				promises.push(this.repository.updateSubmodules(submodulesToUpdate));
			}

844 845
			await Promise.all(promises);
		});
J
Joao Moreno 已提交
846
	}
J
Joao Moreno 已提交
847

J
Joao Moreno 已提交
848
	async branch(name: string, checkout: boolean, ref?: string): Promise<void> {
J
Joao Moreno 已提交
849
		await this.run(Operation.Branch, () => this.repository.branch(name, true));
J
Joao Moreno 已提交
850 851
	}

852 853
	async deleteBranch(name: string, force?: boolean): Promise<void> {
		await this.run(Operation.DeleteBranch, () => this.repository.deleteBranch(name, force));
M
Maik Riechert 已提交
854 855
	}

856 857 858 859
	async renameBranch(name: string): Promise<void> {
		await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name));
	}

J
Joao Moreno 已提交
860 861 862 863 864 865 866 867
	async getBranch(name: string): Promise<Branch> {
		return await this.run(Operation.GetBranch, () => this.repository.getBranch(name));
	}

	async setBranchUpstream(name: string, upstream: string): Promise<void> {
		await this.run(Operation.SetBranchUpstream, () => this.repository.setBranchUpstream(name, upstream));
	}

J
Joao Moreno 已提交
868 869
	async merge(ref: string): Promise<void> {
		await this.run(Operation.Merge, () => this.repository.merge(ref));
870 871
	}

J
Joao Moreno 已提交
872 873
	async tag(name: string, message?: string): Promise<void> {
		await this.run(Operation.Tag, () => this.repository.tag(name, message));
874 875
	}

J
Joao Moreno 已提交
876
	async checkout(treeish: string): Promise<void> {
J
Joao Moreno 已提交
877
		await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
J
Joao Moreno 已提交
878
	}
J
Joao Moreno 已提交
879

880 881 882 883
	async checkoutTracking(treeish: string): Promise<void> {
		await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { track: true }));
	}

J
Joao Moreno 已提交
884 885 886 887 888 889 890 891
	async getCommit(ref: string): Promise<Commit> {
		return await this.repository.getCommit(ref);
	}

	async reset(treeish: string, hard?: boolean): Promise<void> {
		await this.run(Operation.Reset, () => this.repository.reset(treeish, hard));
	}

892 893 894 895
	async deleteRef(ref: string): Promise<void> {
		await this.run(Operation.DeleteRef, () => this.repository.deleteRef(ref));
	}

J
Joao Moreno 已提交
896 897 898 899 900 901 902 903
	async addRemote(name: string, url: string): Promise<void> {
		await this.run(Operation.Remote, () => this.repository.addRemote(name, url));
	}

	async removeRemote(name: string): Promise<void> {
		await this.run(Operation.Remote, () => this.repository.removeRemote(name));
	}

J
Joao Moreno 已提交
904
	@throttle
J
Joao Moreno 已提交
905 906 907 908
	async fetchDefault(): Promise<void> {
		await this.run(Operation.Fetch, () => this.repository.fetch());
	}

J
Joao Moreno 已提交
909 910 911 912 913
	@throttle
	async fetchAll(): Promise<void> {
		await this.run(Operation.Fetch, () => this.repository.fetch({ all: true }));
	}

J
Joao Moreno 已提交
914
	async fetch(remote?: string, ref?: string): Promise<void> {
J
Joao Moreno 已提交
915
		await this.run(Operation.Fetch, () => this.repository.fetch({ remote, ref }));
J
Joao Moreno 已提交
916 917
	}

J
Joao Moreno 已提交
918
	@throttle
J
Joao Moreno 已提交
919
	async pullWithRebase(head: Branch | undefined): Promise<void> {
J
Joao Moreno 已提交
920 921
		let remote: string | undefined;
		let branch: string | undefined;
J
Joao Moreno 已提交
922

J
Joao Moreno 已提交
923 924 925
		if (head && head.name && head.upstream) {
			remote = head.upstream.remote;
			branch = `${head.upstream.name}`;
J
Joao Moreno 已提交
926 927
		}

J
Joao Moreno 已提交
928 929 930 931 932 933 934 935
		const config = workspace.getConfiguration('git', Uri.file(this.root));
		const fetchOnPull = config.get<boolean>('fetchOnPull');

		if (fetchOnPull) {
			await this.run(Operation.Pull, () => this.repository.pull(true));
		} else {
			await this.run(Operation.Pull, () => this.repository.pull(true, remote, branch));
		}
J
Joao Moreno 已提交
936 937 938
	}

	@throttle
J
Joao Moreno 已提交
939
	async pull(head?: Branch): Promise<void> {
J
Joao Moreno 已提交
940 941 942
		let remote: string | undefined;
		let branch: string | undefined;

J
Joao Moreno 已提交
943 944 945
		if (head && head.name && head.upstream) {
			remote = head.upstream.remote;
			branch = `${head.upstream.name}`;
J
Joao Moreno 已提交
946 947
		}

J
Joao Moreno 已提交
948 949 950 951 952 953 954 955
		const config = workspace.getConfiguration('git', Uri.file(this.root));
		const fetchOnPull = config.get<boolean>('fetchOnPull');

		if (fetchOnPull) {
			await this.run(Operation.Pull, () => this.repository.pull(false));
		} else {
			await this.run(Operation.Pull, () => this.repository.pull(false, remote, branch));
		}
J
Joao Moreno 已提交
956 957
	}

958
	async pullFrom(rebase?: boolean, remote?: string, branch?: string): Promise<void> {
J
Joao Moreno 已提交
959 960 961 962 963 964 965 966
		const config = workspace.getConfiguration('git', Uri.file(this.root));
		const fetchOnPull = config.get<boolean>('fetchOnPull');

		if (fetchOnPull) {
			await this.run(Operation.Pull, () => this.repository.pull(rebase));
		} else {
			await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, branch));
		}
J
Joao Moreno 已提交
967 968
	}

J
Joao Moreno 已提交
969
	@throttle
970
	async push(head: Branch, forcePushMode?: ForcePushMode): Promise<void> {
J
Joao Moreno 已提交
971 972 973
		let remote: string | undefined;
		let branch: string | undefined;

J
Joao Moreno 已提交
974 975 976
		if (head && head.name && head.upstream) {
			remote = head.upstream.remote;
			branch = `${head.name}:${head.upstream.name}`;
J
Joao Moreno 已提交
977 978
		}

979
		await this.run(Operation.Push, () => this.repository.push(remote, branch, undefined, undefined, forcePushMode));
J
Joao Moreno 已提交
980 981
	}

982 983
	async pushTo(remote?: string, name?: string, setUpstream: boolean = false, forcePushMode?: ForcePushMode): Promise<void> {
		await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream, undefined, forcePushMode));
984 985
	}

986 987
	async pushTags(remote?: string, forcePushMode?: ForcePushMode): Promise<void> {
		await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true, forcePushMode));
988 989
	}

J
Joao Moreno 已提交
990 991 992 993 994 995 996 997 998 999 1000
	@throttle
	sync(head: Branch): Promise<void> {
		return this._sync(head, false);
	}

	@throttle
	async syncRebase(head: Branch): Promise<void> {
		return this._sync(head, true);
	}

	private async _sync(head: Branch, rebase: boolean): Promise<void> {
1001
		let remoteName: string | undefined;
J
Joao Moreno 已提交
1002 1003 1004 1005
		let pullBranch: string | undefined;
		let pushBranch: string | undefined;

		if (head.name && head.upstream) {
1006
			remoteName = head.upstream.remote;
J
Joao Moreno 已提交
1007 1008 1009 1010
			pullBranch = `${head.upstream.name}`;
			pushBranch = `${head.name}:${head.upstream.name}`;
		}

1011
		await this.run(Operation.Sync, async () => {
J
Joao Moreno 已提交
1012 1013 1014 1015 1016 1017 1018 1019
			const config = workspace.getConfiguration('git', Uri.file(this.root));
			const fetchOnPull = config.get<boolean>('fetchOnPull');

			if (fetchOnPull) {
				await this.repository.pull(rebase);
			} else {
				await this.repository.pull(rebase, remoteName, pullBranch);
			}
1020

1021
			const remote = this.remotes.find(r => r.name === remoteName);
J
Joao Moreno 已提交
1022 1023 1024 1025 1026 1027

			if (remote && remote.isReadOnly) {
				return;
			}

			const shouldPush = this.HEAD && (typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true);
1028 1029

			if (shouldPush) {
1030
				await this.repository.push(remoteName, pushBranch);
1031 1032
			}
		});
J
Joao Moreno 已提交
1033 1034
	}

1035
	async show(ref: string, filePath: string): Promise<string> {
1036 1037
		return await this.run(Operation.Show, async () => {
			const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/');
J
Joao Moreno 已提交
1038
			const configFiles = workspace.getConfiguration('files', Uri.file(filePath));
J
Joao Moreno 已提交
1039
			const defaultEncoding = configFiles.get<string>('encoding');
1040 1041
			const autoGuessEncoding = configFiles.get<boolean>('autoGuessEncoding');

1042 1043 1044 1045 1046 1047 1048 1049 1050 1051
			try {
				return await this.repository.bufferString(`${ref}:${relativePath}`, defaultEncoding, autoGuessEncoding);
			} catch (err) {
				if (err.gitErrorCode === GitErrorCodes.WrongCase) {
					const gitRelativePath = await this.repository.getGitRelativePath(ref, relativePath);
					return await this.repository.bufferString(`${ref}:${gitRelativePath}`, defaultEncoding, autoGuessEncoding);
				}

				throw err;
			}
J
Joao Moreno 已提交
1052 1053 1054 1055
		});
	}

	async buffer(ref: string, filePath: string): Promise<Buffer> {
J
Joao Moreno 已提交
1056
		return this.run(Operation.Show, () => {
J
Joao Moreno 已提交
1057
			const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/');
J
Joao Moreno 已提交
1058
			return this.repository.buffer(`${ref}:${relativePath}`);
J
Joao Moreno 已提交
1059 1060 1061
		});
	}

J
Joao Moreno 已提交
1062 1063
	getObjectDetails(ref: string, filePath: string): Promise<{ mode: string, object: string, size: number }> {
		return this.run(Operation.GetObjectDetails, () => this.repository.getObjectDetails(ref, filePath));
J
Joao Moreno 已提交
1064 1065 1066 1067 1068 1069
	}

	detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> {
		return this.run(Operation.Show, () => this.repository.detectObjectType(object));
	}

J
Joao Moreno 已提交
1070 1071 1072 1073
	async getStashes(): Promise<Stash[]> {
		return await this.repository.getStashes();
	}

1074 1075
	async createStash(message?: string, includeUntracked?: boolean): Promise<void> {
		return await this.run(Operation.Stash, () => this.repository.createStash(message, includeUntracked));
J
Joao Moreno 已提交
1076 1077 1078 1079
	}

	async popStash(index?: number): Promise<void> {
		return await this.run(Operation.Stash, () => this.repository.popStash(index));
1080 1081
	}

1082 1083 1084 1085
	async applyStash(index?: number): Promise<void> {
		return await this.run(Operation.Stash, () => this.repository.applyStash(index));
	}

J
Joao Moreno 已提交
1086 1087 1088 1089
	async getCommitTemplate(): Promise<string> {
		return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
	}

J
Joao Moreno 已提交
1090
	async ignore(files: Uri[]): Promise<void> {
N
NKumar2 已提交
1091
		return await this.run(Operation.Ignore, async () => {
J
Joao Moreno 已提交
1092 1093
			const ignoreFile = `${this.repository.root}${path.sep}.gitignore`;
			const textToAppend = files
J
Joao Moreno 已提交
1094
				.map(uri => path.relative(this.repository.root, uri.fsPath).replace(/\\/g, '/'))
J
Joao Moreno 已提交
1095
				.join('\n');
N
NKumar2 已提交
1096

J
Joao Moreno 已提交
1097 1098 1099
			const document = await new Promise(c => fs.exists(ignoreFile, c))
				? await workspace.openTextDocument(ignoreFile)
				: await workspace.openTextDocument(Uri.file(ignoreFile).with({ scheme: 'untitled' }));
1100

J
Joao Moreno 已提交
1101
			await window.showTextDocument(document);
J
Joao Moreno 已提交
1102

J
Joao Moreno 已提交
1103
			const edit = new WorkspaceEdit();
J
Joao Moreno 已提交
1104 1105
			const lastLine = document.lineAt(document.lineCount - 1);
			const text = lastLine.isEmptyOrWhitespace ? `${textToAppend}\n` : `\n${textToAppend}\n`;
1106

J
Joao Moreno 已提交
1107
			edit.insert(document.uri, lastLine.range.end, text);
J
Joao Moreno 已提交
1108
			workspace.applyEdit(edit);
N
NKumar2 已提交
1109 1110 1111
		});
	}

J
Johannes Rieken 已提交
1112
	checkIgnore(filePaths: string[]): Promise<Set<string>> {
1113
		return this.run(Operation.CheckIgnore, () => {
J
Johannes Rieken 已提交
1114 1115
			return new Promise<Set<string>>((resolve, reject) => {

J
Joao Moreno 已提交
1116 1117
				filePaths = filePaths
					.filter(filePath => isDescendant(this.root, filePath));
1118

1119 1120
				if (filePaths.length === 0) {
					// nothing left
C
cleidigh 已提交
1121
					return resolve(new Set<string>());
1122 1123
				}

1124 1125 1126
				// https://git-scm.com/docs/git-check-ignore#git-check-ignore--z
				const child = this.repository.stream(['check-ignore', '-z', '--stdin'], { stdio: [null, null, null] });
				child.stdin.end(filePaths.join('\0'), 'utf8');
J
Johannes Rieken 已提交
1127

1128
				const onExit = (exitCode: number) => {
J
Johannes Rieken 已提交
1129 1130 1131 1132
					if (exitCode === 1) {
						// nothing ignored
						resolve(new Set<string>());
					} else if (exitCode === 0) {
1133 1134
						// paths are separated by the null-character
						resolve(new Set<string>(data.split('\0')));
J
Johannes Rieken 已提交
1135
					} else {
J
Joao Moreno 已提交
1136 1137 1138 1139 1140
						if (/ is in submodule /.test(stderr)) {
							reject(new GitError({ stdout: data, stderr, exitCode, gitErrorCode: GitErrorCodes.IsInSubmodule }));
						} else {
							reject(new GitError({ stdout: data, stderr, exitCode }));
						}
J
Johannes Rieken 已提交
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151
					}
				};

				let data = '';
				const onStdoutData = (raw: string) => {
					data += raw;
				};

				child.stdout.setEncoding('utf8');
				child.stdout.on('data', onStdoutData);

1152 1153 1154
				let stderr: string = '';
				child.stderr.setEncoding('utf8');
				child.stderr.on('data', raw => stderr += raw);
J
Johannes Rieken 已提交
1155 1156 1157 1158 1159 1160 1161

				child.on('error', reject);
				child.on('exit', onExit);
			});
		});
	}

J
Joao Moreno 已提交
1162
	private async run<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
J
Joao 已提交
1163
		if (this.state !== RepositoryState.Idle) {
1164 1165 1166
			throw new Error('Repository not initialized');
		}

J
Joao Moreno 已提交
1167
		let error: any = null;
J
Joao Moreno 已提交
1168

J
Joao Moreno 已提交
1169 1170
		this._operations.start(operation);
		this._onRunOperation.fire(operation);
J
Joao Moreno 已提交
1171

J
Joao Moreno 已提交
1172 1173
		try {
			const result = await this.retryRun(runOperation);
J
Joao Moreno 已提交
1174

J
Joao Moreno 已提交
1175 1176 1177
			if (!isReadOnly(operation)) {
				await this.updateModelState();
			}
J
Joao Moreno 已提交
1178

J
Joao Moreno 已提交
1179 1180 1181
			return result;
		} catch (err) {
			error = err;
J
Joao Moreno 已提交
1182

J
Joao Moreno 已提交
1183 1184
			if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) {
				this.state = RepositoryState.Disposed;
J
Joao Moreno 已提交
1185
			}
1186

J
Joao Moreno 已提交
1187 1188 1189 1190 1191
			throw err;
		} finally {
			this._operations.end(operation);
			this._onDidRunOperation.fire({ operation, error });
		}
J
Joao Moreno 已提交
1192
	}
1193

1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211
	private async retryRun<T>(runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
		let attempt = 0;

		while (true) {
			try {
				attempt++;
				return await runOperation();
			} catch (err) {
				if (err.gitErrorCode === GitErrorCodes.RepositoryIsLocked && attempt <= 10) {
					// quatratic backoff
					await timeout(Math.pow(attempt, 2) * 50);
				} else {
					throw err;
				}
			}
		}
	}

J
Joao Moreno 已提交
1212
	@throttle
1213
	private async updateModelState(): Promise<void> {
1214 1215 1216
		const { status, didHitLimit } = await this.repository.getStatus();
		const config = workspace.getConfiguration('git');
		const shouldIgnore = config.get<boolean>('ignoreLimitWarning') === true;
J
Johannes Rieken 已提交
1217
		const useIcons = !config.get<boolean>('decorations.enabled', true);
1218 1219 1220 1221

		this.isRepositoryHuge = didHitLimit;

		if (didHitLimit && !shouldIgnore && !this.didWarnAboutLimit) {
B
Benjamin Pasero 已提交
1222
			const neverAgain = { title: localize('neveragain', "Don't Show Again") };
1223

1224
			window.showWarningMessage(localize('huge', "The git repository at '{0}' has too many active changes, only a subset of Git features will be enabled.", this.repository.root), neverAgain).then(result => {
1225 1226 1227 1228 1229 1230 1231 1232
				if (result === neverAgain) {
					config.update('ignoreLimitWarning', true, false);
				}
			});

			this.didWarnAboutLimit = true;
		}

J
Joao Moreno 已提交
1233
		let HEAD: Branch | undefined;
J
Joao Moreno 已提交
1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248

		try {
			HEAD = await this.repository.getHEAD();

			if (HEAD.name) {
				try {
					HEAD = await this.repository.getBranch(HEAD.name);
				} catch (err) {
					// noop
				}
			}
		} catch (err) {
			// noop
		}

1249
		const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]);
J
Joao Moreno 已提交
1250 1251 1252 1253

		this._HEAD = HEAD;
		this._refs = refs;
		this._remotes = remotes;
1254
		this._submodules = submodules;
1255
		this.rebaseCommit = rebaseCommit;
J
Joao Moreno 已提交
1256 1257 1258 1259 1260 1261

		const index: Resource[] = [];
		const workingTree: Resource[] = [];
		const merge: Resource[] = [];

		status.forEach(raw => {
J
Joao Moreno 已提交
1262 1263
			const uri = Uri.file(path.join(this.repository.root, raw.path));
			const renameUri = raw.rename ? Uri.file(path.join(this.repository.root, raw.rename)) : undefined;
J
Joao Moreno 已提交
1264 1265

			switch (raw.x + raw.y) {
1266 1267 1268 1269 1270 1271 1272 1273 1274
				case '??': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons));
				case '!!': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons));
				case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons));
				case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons));
				case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons));
				case 'UA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons));
				case 'DU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons));
				case 'AA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons));
				case 'UU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons));
J
Joao Moreno 已提交
1275 1276 1277
			}

			switch (raw.x) {
J
Joao Moreno 已提交
1278
				case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break;
1279 1280 1281 1282
				case 'A': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break;
				case 'D': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break;
				case 'R': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break;
				case 'C': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break;
J
Joao Moreno 已提交
1283 1284 1285
			}

			switch (raw.y) {
1286 1287
				case 'M': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break;
				case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break;
J
Joao Moreno 已提交
1288 1289 1290
			}
		});

J
Joao Moreno 已提交
1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306
		// set resource groups
		this.mergeGroup.resourceStates = merge;
		this.indexGroup.resourceStates = index;
		this.workingTreeGroup.resourceStates = workingTree;

		// set count badge
		const countBadge = workspace.getConfiguration('git').get<string>('countBadge');
		let count = merge.length + index.length + workingTree.length;

		switch (countBadge) {
			case 'off': count = 0; break;
			case 'tracked': count = count - workingTree.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break;
		}

		this._sourceControl.count = count;

J
Joao Moreno 已提交
1307 1308 1309
		// Disable `Discard All Changes` for "fresh" repositories
		// https://github.com/Microsoft/vscode/issues/43066
		commands.executeCommand('setContext', 'gitFreshRepository', !this._HEAD || !this._HEAD.commit);
J
Joao Moreno 已提交
1310

J
Joao Moreno 已提交
1311
		this._onDidChangeStatus.fire();
J
Joao Moreno 已提交
1312 1313
	}

1314 1315
	private async getRebaseCommit(): Promise<Commit | undefined> {
		const rebaseHeadPath = path.join(this.repository.root, '.git', 'REBASE_HEAD');
J
Jason Bright 已提交
1316 1317
		const rebaseApplyPath = path.join(this.repository.root, '.git', 'rebase-apply');
		const rebaseMergePath = path.join(this.repository.root, '.git', 'rebase-merge');
1318 1319

		try {
J
Jason Bright 已提交
1320 1321 1322 1323 1324 1325 1326 1327
			const [rebaseApplyExists, rebaseMergePathExists, rebaseHead] = await Promise.all([
				new Promise<boolean>(c => fs.exists(rebaseApplyPath, c)),
				new Promise<boolean>(c => fs.exists(rebaseMergePath, c)),
				new Promise<string>((c, e) => fs.readFile(rebaseHeadPath, 'utf8', (err, result) => err ? e(err) : c(result)))
			]);
			if (!rebaseApplyExists && !rebaseMergePathExists) {
				return undefined;
			}
1328 1329 1330 1331 1332 1333
			return await this.getCommit(rebaseHead.trim());
		} catch (err) {
			return undefined;
		}
	}

J
Joao Moreno 已提交
1334
	private onFSChange(uri: Uri): void {
J
Joao Moreno 已提交
1335 1336 1337 1338 1339 1340 1341
		const config = workspace.getConfiguration('git');
		const autorefresh = config.get<boolean>('autorefresh');

		if (!autorefresh) {
			return;
		}

1342 1343 1344 1345
		if (this.isRepositoryHuge) {
			return;
		}

J
Joao Moreno 已提交
1346 1347 1348 1349 1350 1351 1352
		if (!this.operations.isIdle()) {
			return;
		}

		this.eventuallyUpdateWhenIdleAndWait();
	}

1353
	@debounce(1000)
J
Joao Moreno 已提交
1354
	private eventuallyUpdateWhenIdleAndWait(): void {
1355 1356 1357
		this.updateWhenIdleAndWait();
	}

J
Joao Moreno 已提交
1358
	@throttle
1359
	private async updateWhenIdleAndWait(): Promise<void> {
J
Joao 已提交
1360
		await this.whenIdleAndFocused();
J
Joao Moreno 已提交
1361
		await this.status();
J
Joao Moreno 已提交
1362
		await timeout(5000);
1363 1364
	}

J
Joao Moreno 已提交
1365
	async whenIdleAndFocused(): Promise<void> {
J
Joao 已提交
1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378
		while (true) {
			if (!this.operations.isIdle()) {
				await eventToPromise(this.onDidRunOperation);
				continue;
			}

			if (!window.state.focused) {
				const onDidFocusWindow = filterEvent(window.onDidChangeWindowState, e => e.focused);
				await eventToPromise(onDidFocusWindow);
				continue;
			}

			return;
1379 1380 1381
		}
	}

J
Joao 已提交
1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408
	get headLabel(): string {
		const HEAD = this.HEAD;

		if (!HEAD) {
			return '';
		}

		const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0];
		const tagName = tag && tag.name;
		const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8);

		return head
			+ (this.workingTreeGroup.resourceStates.length > 0 ? '*' : '')
			+ (this.indexGroup.resourceStates.length > 0 ? '+' : '')
			+ (this.mergeGroup.resourceStates.length > 0 ? '!' : '');
	}

	get syncLabel(): string {
		if (!this.HEAD
			|| !this.HEAD.name
			|| !this.HEAD.commit
			|| !this.HEAD.upstream
			|| !(this.HEAD.ahead || this.HEAD.behind)
		) {
			return '';
		}

1409 1410 1411
		const remoteName = this.HEAD && this.HEAD.remote || this.HEAD.upstream.remote;
		const remote = this.remotes.find(r => r.name === remoteName);

J
Joao Moreno 已提交
1412
		if (remote && remote.isReadOnly) {
1413 1414 1415
			return `${this.HEAD.behind}↓`;
		}

J
Joao 已提交
1416 1417 1418
		return `${this.HEAD.behind}${this.HEAD.ahead}↑`;
	}

J
Joao Moreno 已提交
1419
	dispose(): void {
J
Joao Moreno 已提交
1420
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
1421
	}
1422
}