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

'use strict';

J
Joao Moreno 已提交
8
import { Uri, Command, EventEmitter, Event, scm, commands, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit } from 'vscode';
J
Joao Moreno 已提交
9 10
import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash } from './git';
import { anyEvent, filterEvent, eventToPromise, dispose, find } 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';
J
Joao Moreno 已提交
17

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

J
Joao Moreno 已提交
20
const localize = nls.loadMessageBundle();
J
Joao Moreno 已提交
21 22 23 24 25 26
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`));
}

J
Joao Moreno 已提交
27 28
export enum State {
	Idle,
J
Joao Moreno 已提交
29
	Disposed
J
Joao Moreno 已提交
30 31
}

J
Joao Moreno 已提交
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
export enum Status {
	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
}

J
Joao Moreno 已提交
53 54 55 56 57 58
export enum ResourceGroupType {
	Merge,
	Index,
	WorkingTree
}

J
Joao Moreno 已提交
59
export class Resource implements SourceControlResourceState {
J
Joao Moreno 已提交
60

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

		return this._resourceUri;
68 69 70
	}

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

J
Joao Moreno 已提交
79
	get resourceGroupType(): ResourceGroupType { return this._resourceGroupType; }
J
Joao Moreno 已提交
80
	get type(): Status { return this._type; }
J
Joao Moreno 已提交
81 82
	get original(): Uri { return this._resourceUri; }
	get renameResourceUri(): Uri | undefined { return this._renameResourceUri; }
J
Joao Moreno 已提交
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128

	private static Icons = {
		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')
		}
	};

	private getIconPath(theme: string): Uri | undefined {
		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;
			default: return void 0;
		}
	}

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
	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 已提交
151 152 153 154 155 156
	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 已提交
157
			case Status.INDEX_DELETED:
J
Joao Moreno 已提交
158 159 160 161 162 163
				return true;
			default:
				return false;
		}
	}

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

J
Joao Moreno 已提交
172
	get decorations(): SourceControlResourceDecorations {
J
Joao Moreno 已提交
173 174
		const light = { iconPath: this.getIconPath('light') };
		const dark = { iconPath: this.getIconPath('dark') };
175
		const tooltip = this.tooltip;
176 177
		const strikeThrough = this.strikeThrough;
		const faded = this.faded;
J
Joao Moreno 已提交
178

179
		return { strikeThrough, faded, tooltip, light, dark };
J
Joao Moreno 已提交
180 181
	}

182
	constructor(
J
Joao Moreno 已提交
183
		private _resourceGroupType: ResourceGroupType,
184 185 186 187
		private _resourceUri: Uri,
		private _type: Status,
		private _renameResourceUri?: Uri
	) { }
J
Joao Moreno 已提交
188 189
}

190
export enum Operation {
J
Joao Moreno 已提交
191
	Status = 1 << 0,
J
Joao Moreno 已提交
192 193
	Add = 1 << 1,
	RevertFiles = 1 << 2,
J
Joao Moreno 已提交
194 195 196 197
	Commit = 1 << 3,
	Clean = 1 << 4,
	Branch = 1 << 5,
	Checkout = 1 << 6,
J
Joao Moreno 已提交
198 199 200 201 202
	Reset = 1 << 7,
	Fetch = 1 << 8,
	Pull = 1 << 9,
	Push = 1 << 10,
	Sync = 1 << 11,
J
Joao Moreno 已提交
203
	Init = 1 << 12,
J
Joao Moreno 已提交
204
	Show = 1 << 13,
J
Joao Moreno 已提交
205
	Stage = 1 << 14,
M
Maik Riechert 已提交
206
	GetCommitTemplate = 1 << 15,
N
NKumar2 已提交
207
	DeleteBranch = 1 << 16,
208
	Merge = 1 << 17,
209
	Ignore = 1 << 18,
210 211
	Tag = 1 << 19,
	Stash = 1 << 20
212 213
}

J
Joao Moreno 已提交
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
// function getOperationName(operation: Operation): string {
// 	switch (operation) {
// 		case Operation.Status: return 'Status';
// 		case Operation.Add: return 'Add';
// 		case Operation.RevertFiles: return 'RevertFiles';
// 		case Operation.Commit: return 'Commit';
// 		case Operation.Clean: return 'Clean';
// 		case Operation.Branch: return 'Branch';
// 		case Operation.Checkout: return 'Checkout';
// 		case Operation.Reset: return 'Reset';
// 		case Operation.Fetch: return 'Fetch';
// 		case Operation.Pull: return 'Pull';
// 		case Operation.Push: return 'Push';
// 		case Operation.Sync: return 'Sync';
// 		case Operation.Init: return 'Init';
// 		case Operation.Show: return 'Show';
// 		case Operation.Stage: return 'Stage';
// 		case Operation.GetCommitTemplate: return 'GetCommitTemplate';
// 		default: return 'unknown';
// 	}
// }

function isReadOnly(operation: Operation): boolean {
	switch (operation) {
		case Operation.Show:
		case Operation.GetCommitTemplate:
			return true;
		default:
			return false;
	}
}

246 247 248 249 250 251 252 253 254
function shouldShowProgress(operation: Operation): boolean {
	switch (operation) {
		case Operation.Fetch:
			return false;
		default:
			return true;
	}
}

255
export interface Operations {
256
	isIdle(): boolean;
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
	isRunning(operation: Operation): boolean;
}

class OperationsImpl implements Operations {

	constructor(private readonly operations: number = 0) {
		// noop
	}

	start(operation: Operation): OperationsImpl {
		return new OperationsImpl(this.operations | operation);
	}

	end(operation: Operation): OperationsImpl {
		return new OperationsImpl(this.operations & ~operation);
	}

	isRunning(operation: Operation): boolean {
		return (this.operations & operation) !== 0;
	}
277 278 279 280

	isIdle(): boolean {
		return this.operations === 0;
	}
281 282
}

J
Joao Moreno 已提交
283 284 285 286
export interface CommitOptions {
	all?: boolean;
	amend?: boolean;
	signoff?: boolean;
287
	signCommit?: boolean;
J
Joao Moreno 已提交
288 289
}

J
Joao Moreno 已提交
290 291 292 293
export interface GitResourceGroup extends SourceControlResourceGroup {
	resourceStates: Resource[];
}

J
Joao Moreno 已提交
294
export class Repository implements Disposable {
J
Joao Moreno 已提交
295

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

J
Joao Moreno 已提交
299 300
	private _onDidChangeState = new EventEmitter<State>();
	readonly onDidChangeState: Event<State> = this._onDidChangeState.event;
J
Joao Moreno 已提交
301

J
Joao Moreno 已提交
302 303
	@memoize
	get onDidChange(): Event<void> {
J
Joao Moreno 已提交
304
		return anyEvent<any>(this.onDidChangeState);
J
Joao Moreno 已提交
305
	}
J
Joao Moreno 已提交
306

307 308 309 310 311 312 313 314 315 316 317
	private _onRunOperation = new EventEmitter<Operation>();
	readonly onRunOperation: Event<Operation> = this._onRunOperation.event;

	private _onDidRunOperation = new EventEmitter<Operation>();
	readonly onDidRunOperation: Event<Operation> = this._onDidRunOperation.event;

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

J
Joao Moreno 已提交
318 319 320
	private _sourceControl: SourceControl;
	get sourceControl(): SourceControl { return this._sourceControl; }

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

J
Joao Moreno 已提交
323 324
	private _mergeGroup: SourceControlResourceGroup;
	get mergeGroup(): GitResourceGroup { return this._mergeGroup as GitResourceGroup; }
J
Joao Moreno 已提交
325

J
Joao Moreno 已提交
326 327
	private _indexGroup: SourceControlResourceGroup;
	get indexGroup(): GitResourceGroup { return this._indexGroup as GitResourceGroup; }
J
Joao Moreno 已提交
328

J
Joao Moreno 已提交
329 330
	private _workingTreeGroup: SourceControlResourceGroup;
	get workingTreeGroup(): GitResourceGroup { return this._workingTreeGroup as GitResourceGroup; }
J
Joao Moreno 已提交
331

J
Joao Moreno 已提交
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
	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;
	}

347 348 349
	private _operations = new OperationsImpl();
	get operations(): Operations { return this._operations; }

J
Joao Moreno 已提交
350
	private _state = State.Idle;
J
Joao Moreno 已提交
351 352 353 354
	get state(): State { return this._state; }
	set state(state: State) {
		this._state = state;
		this._onDidChangeState.fire(state);
J
Joao Moreno 已提交
355 356 357 358

		this._HEAD = undefined;
		this._refs = [];
		this._remotes = [];
J
Joao Moreno 已提交
359 360 361 362 363
		this.mergeGroup.resourceStates = [];
		this.indexGroup.resourceStates = [];
		this.workingTreeGroup.resourceStates = [];
		this._sourceControl.count = 0;
		commands.executeCommand('setContext', 'gitState', '');
J
Joao Moreno 已提交
364 365
	}

366 367 368 369
	get root(): string {
		return this.repository.root;
	}

370 371
	private isRepositoryHuge = false;
	private didWarnAboutLimit = false;
J
Joao Moreno 已提交
372
	private disposables: Disposable[] = [];
J
Joao Moreno 已提交
373

374
	constructor(
375
		private readonly repository: BaseRepository
376
	) {
J
Joao Moreno 已提交
377 378 379
		const fsWatcher = workspace.createFileSystemWatcher('**');
		this.disposables.push(fsWatcher);

380 381 382
		const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
		onWorkspaceChange(this.onFSChange, this, this.disposables);

J
Joao Moreno 已提交
383 384 385 386
		const onGitChange = filterEvent(onWorkspaceChange, uri => /\/\.git\//.test(uri.path));
		const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.path));
		onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);

387 388
		const label = `Git - ${path.basename(repository.root)}`;

389
		this._sourceControl = scm.createSourceControl('git', label);
J
Joao Moreno 已提交
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
		this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") };
		this._sourceControl.quickDiffProvider = this;
		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"));

		this.mergeGroup.hideWhenEmpty = true;
		this.indexGroup.hideWhenEmpty = true;

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

J
Joao Moreno 已提交
405 406
		this.disposables.push(new AutoFetcher(this));

J
Joao Moreno 已提交
407
		this.updateCommitTemplate();
J
Joao Moreno 已提交
408
		this.status();
J
Joao Moreno 已提交
409 410
	}

J
Joao Moreno 已提交
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
	// TODO@Joao reorganize this
	provideOriginalResource(uri: Uri): Uri | undefined {
		if (uri.scheme !== 'file') {
			return;
		}

		return toGitUri(uri, '', true);
	}

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

428 429 430 431 432
	// @throttle
	// async init(): Promise<void> {
	// 	if (this.state !== State.NotAGitRepository) {
	// 		return;
	// 	}
J
Joao Moreno 已提交
433

434 435 436
	// 	await this.git.init(this.workspaceRoot.fsPath);
	// 	await this.status();
	// }
J
Joao Moreno 已提交
437

J
Joao Moreno 已提交
438
	@throttle
J
Joao Moreno 已提交
439 440
	async status(): Promise<void> {
		await this.run(Operation.Status);
J
Joao Moreno 已提交
441
	}
J
Joao Moreno 已提交
442

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

J
Joao Moreno 已提交
447 448
	async stage(resource: Uri, contents: string): Promise<void> {
		const relativePath = path.relative(this.repository.root, resource.fsPath).replace(/\\/g, '/');
J
Joao Moreno 已提交
449 450 451
		await this.run(Operation.Stage, () => this.repository.stage(relativePath, contents));
	}

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

J
Joao Moreno 已提交
456
	async commit(message: string, opts: CommitOptions = Object.create(null)): Promise<void> {
457 458 459 460
		await this.run(Operation.Commit, async () => {
			if (opts.all) {
				await this.repository.add([]);
			}
J
Joao Moreno 已提交
461

462 463
			await this.repository.commit(message, opts);
		});
J
Joao Moreno 已提交
464
	}
J
Joao Moreno 已提交
465

J
Joao Moreno 已提交
466
	async clean(resources: Uri[]): Promise<void> {
467 468 469 470 471
		await this.run(Operation.Clean, async () => {
			const toClean: string[] = [];
			const toCheckout: string[] = [];

			resources.forEach(r => {
472
				const raw = r.toString();
J
Joao Moreno 已提交
473
				const scmResource = find(this.workingTreeGroup.resourceStates, sr => sr.resourceUri.toString() === raw);
474 475 476 477 478 479

				if (!scmResource) {
					return;
				}

				switch (scmResource.type) {
480 481
					case Status.UNTRACKED:
					case Status.IGNORED:
482
						toClean.push(r.fsPath);
483 484 485
						break;

					default:
486
						toCheckout.push(r.fsPath);
487 488 489
						break;
				}
			});
J
Joao Moreno 已提交
490

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

493 494 495
			if (toClean.length > 0) {
				promises.push(this.repository.clean(toClean));
			}
J
Joao Moreno 已提交
496

497 498 499
			if (toCheckout.length > 0) {
				promises.push(this.repository.checkout('', toCheckout));
			}
J
Joao Moreno 已提交
500

501 502
			await Promise.all(promises);
		});
J
Joao Moreno 已提交
503
	}
J
Joao Moreno 已提交
504

J
Joao Moreno 已提交
505
	async branch(name: string): Promise<void> {
J
Joao Moreno 已提交
506
		await this.run(Operation.Branch, () => this.repository.branch(name, true));
J
Joao Moreno 已提交
507 508
	}

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

J
Joao Moreno 已提交
513 514
	async merge(ref: string): Promise<void> {
		await this.run(Operation.Merge, () => this.repository.merge(ref));
515 516
	}

J
Joao Moreno 已提交
517 518
	async tag(name: string, message?: string): Promise<void> {
		await this.run(Operation.Tag, () => this.repository.tag(name, message));
519 520
	}

J
Joao Moreno 已提交
521
	async checkout(treeish: string): Promise<void> {
J
Joao Moreno 已提交
522
		await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
J
Joao Moreno 已提交
523
	}
J
Joao Moreno 已提交
524

J
Joao Moreno 已提交
525 526 527 528 529 530 531 532
	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));
	}

J
Joao Moreno 已提交
533
	@throttle
J
Joao Moreno 已提交
534
	async fetch(): Promise<void> {
J
Joao Moreno 已提交
535
		try {
536
			await this.run(Operation.Fetch, () => this.repository.fetch());
J
Joao Moreno 已提交
537 538
		} catch (err) {
			// noop
539
		}
J
Joao Moreno 已提交
540 541
	}

J
Joao Moreno 已提交
542
	@throttle
543 544
	async pullWithRebase(): Promise<void> {
		await this.run(Operation.Pull, () => this.repository.pull(true));
J
Joao Moreno 已提交
545 546 547
	}

	@throttle
548 549
	async pull(rebase?: boolean, remote?: string, name?: string): Promise<void> {
		await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, name));
J
Joao Moreno 已提交
550 551 552 553 554
	}

	@throttle
	async push(): Promise<void> {
		await this.run(Operation.Push, () => this.repository.push());
J
Joao Moreno 已提交
555 556
	}

557
	async pullFrom(rebase?: boolean, remote?: string, branch?: string): Promise<void> {
M
Matt Shirley 已提交
558
		await this.run(Operation.Pull, () => this.repository.pull(rebase, remote, branch));
J
Joao Moreno 已提交
559 560
	}

J
Joao Moreno 已提交
561 562
	async pushTo(remote?: string, name?: string, setUpstream: boolean = false): Promise<void> {
		await this.run(Operation.Push, () => this.repository.push(remote, name, setUpstream));
563 564
	}

565 566
	async pushTags(remote?: string): Promise<void> {
		await this.run(Operation.Push, () => this.repository.push(remote, undefined, false, true));
567 568
	}

J
Joao Moreno 已提交
569 570
	@throttle
	async sync(): Promise<void> {
571 572 573
		await this.run(Operation.Sync, async () => {
			await this.repository.pull();

574
			const shouldPush = this.HEAD && typeof this.HEAD.ahead === 'number' ? this.HEAD.ahead > 0 : true;
575 576 577 578 579

			if (shouldPush) {
				await this.repository.push();
			}
		});
J
Joao Moreno 已提交
580 581
	}

582
	async show(ref: string, filePath: string): Promise<string> {
J
Joao Moreno 已提交
583
		return await this.run(Operation.Show, async () => {
584
			const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/');
J
Joao Moreno 已提交
585 586
			const configFiles = workspace.getConfiguration('files');
			const encoding = configFiles.get<string>('encoding');
J
Joao Moreno 已提交
587

J
Joao Moreno 已提交
588
			return await this.repository.buffer(`${ref}:${relativePath}`, encoding);
J
Joao Moreno 已提交
589 590 591
		});
	}

J
Joao Moreno 已提交
592 593 594 595 596 597 598 599 600 601
	async getStashes(): Promise<Stash[]> {
		return await this.repository.getStashes();
	}

	async createStash(message?: string): Promise<void> {
		return await this.run(Operation.Stash, () => this.repository.createStash(message));
	}

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

J
Joao Moreno 已提交
604 605 606 607
	async getCommitTemplate(): Promise<string> {
		return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
	}

J
Joao Moreno 已提交
608
	async ignore(files: Uri[]): Promise<void> {
N
NKumar2 已提交
609
		return await this.run(Operation.Ignore, async () => {
J
Joao Moreno 已提交
610 611
			const ignoreFile = `${this.repository.root}${path.sep}.gitignore`;
			const textToAppend = files
J
Joao Moreno 已提交
612
				.map(uri => path.relative(this.repository.root, uri.fsPath).replace(/\\/g, '/'))
J
Joao Moreno 已提交
613
				.join('\n');
N
NKumar2 已提交
614

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

J
Joao Moreno 已提交
619
			await window.showTextDocument(document);
J
Joao Moreno 已提交
620

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

J
Joao Moreno 已提交
625
			edit.insert(document.uri, lastLine.range.end, text);
J
Joao Moreno 已提交
626
			workspace.applyEdit(edit);
N
NKumar2 已提交
627 628 629
		});
	}

J
Joao Moreno 已提交
630
	private async run<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
631 632 633 634
		if (this.state !== State.Idle) {
			throw new Error('Repository not initialized');
		}

635
		const run = async () => {
J
Joao Moreno 已提交
636 637 638 639
			this._operations = this._operations.start(operation);
			this._onRunOperation.fire(operation);

			try {
640
				const result = await this.retryRun(runOperation);
J
Joao Moreno 已提交
641 642

				if (!isReadOnly(operation)) {
643
					await this.updateModelState();
J
Joao Moreno 已提交
644 645
				}

J
Joao Moreno 已提交
646
				return result;
J
Joao Moreno 已提交
647 648
			} catch (err) {
				if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) {
J
Joao Moreno 已提交
649
					this.state = State.Disposed;
J
Joao Moreno 已提交
650
				}
J
Joao Moreno 已提交
651 652

				throw err;
J
Joao Moreno 已提交
653 654 655 656
			} finally {
				this._operations = this._operations.end(operation);
				this._onDidRunOperation.fire(operation);
			}
657 658 659
		};

		return shouldShowProgress(operation)
660
			? window.withProgress({ location: ProgressLocation.SourceControl }, run)
661
			: run();
J
Joao Moreno 已提交
662
	}
663

664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
	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 已提交
682
	@throttle
683
	private async updateModelState(): Promise<void> {
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702
		const { status, didHitLimit } = await this.repository.getStatus();
		const config = workspace.getConfiguration('git');
		const shouldIgnore = config.get<boolean>('ignoreLimitWarning') === true;

		this.isRepositoryHuge = didHitLimit;

		if (didHitLimit && !shouldIgnore && !this.didWarnAboutLimit) {
			const ok = { title: localize('ok', "OK"), isCloseAffordance: true };
			const neverAgain = { title: localize('neveragain', "Never Show Again") };

			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), ok, neverAgain).then(result => {
				if (result === neverAgain) {
					config.update('ignoreLimitWarning', true, false);
				}
			});

			this.didWarnAboutLimit = true;
		}

J
Joao Moreno 已提交
703
		let HEAD: Branch | undefined;
J
Joao Moreno 已提交
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729

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

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

		const [refs, remotes] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes()]);

		this._HEAD = HEAD;
		this._refs = refs;
		this._remotes = remotes;

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

		status.forEach(raw => {
J
Joao Moreno 已提交
730 731
			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 已提交
732 733

			switch (raw.x + raw.y) {
734 735 736 737 738 739 740 741 742
				case '??': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.UNTRACKED));
				case '!!': return workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.IGNORED));
				case 'DD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_DELETED));
				case 'AU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_US));
				case 'UD': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM));
				case 'UA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM));
				case 'DU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.DELETED_BY_US));
				case 'AA': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_ADDED));
				case 'UU': return merge.push(new Resource(ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED));
J
Joao Moreno 已提交
743 744 745 746 747
			}

			let isModifiedInIndex = false;

			switch (raw.x) {
748 749 750 751 752
				case 'M': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break;
				case 'A': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_ADDED)); break;
				case 'D': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_DELETED)); break;
				case 'R': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_RENAMED, renameUri)); break;
				case 'C': index.push(new Resource(ResourceGroupType.Index, uri, Status.INDEX_COPIED, renameUri)); break;
J
Joao Moreno 已提交
753 754 755
			}

			switch (raw.y) {
756 757
				case 'M': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.MODIFIED, renameUri)); break;
				case 'D': workingTree.push(new Resource(ResourceGroupType.WorkingTree, uri, Status.DELETED, renameUri)); break;
J
Joao Moreno 已提交
758 759 760
			}
		});

J
Joao Moreno 已提交
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
		// 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;

		// set context key
		let stateContextKey = '';

		switch (this.state) {
			case State.Idle: stateContextKey = 'idle'; break;
J
Joao Moreno 已提交
782
			case State.Disposed: stateContextKey = 'norepo'; break;
J
Joao Moreno 已提交
783 784 785
		}

		commands.executeCommand('setContext', 'gitState', stateContextKey);
J
Joao Moreno 已提交
786 787 788
	}

	private onFSChange(uri: Uri): void {
J
Joao Moreno 已提交
789 790 791 792 793 794 795
		const config = workspace.getConfiguration('git');
		const autorefresh = config.get<boolean>('autorefresh');

		if (!autorefresh) {
			return;
		}

796 797 798 799
		if (this.isRepositoryHuge) {
			return;
		}

J
Joao Moreno 已提交
800 801 802 803 804 805 806
		if (!this.operations.isIdle()) {
			return;
		}

		this.eventuallyUpdateWhenIdleAndWait();
	}

807
	@debounce(1000)
J
Joao Moreno 已提交
808
	private eventuallyUpdateWhenIdleAndWait(): void {
809 810 811
		this.updateWhenIdleAndWait();
	}

J
Joao Moreno 已提交
812
	@throttle
813 814
	private async updateWhenIdleAndWait(): Promise<void> {
		await this.whenIdle();
J
Joao Moreno 已提交
815
		await this.status();
J
Joao Moreno 已提交
816
		await timeout(5000);
817 818
	}

819 820 821 822 823 824
	private async whenIdle(): Promise<void> {
		while (!this.operations.isIdle()) {
			await eventToPromise(this.onDidRunOperation);
		}
	}

J
Joao Moreno 已提交
825
	dispose(): void {
J
Joao Moreno 已提交
826
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
827
	}
828
}