repository.ts 26.1 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, 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';
M
Matt Bierner 已提交
17
import { StatusBarCommands } from './statusbar';
J
Joao Moreno 已提交
18

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

J
Joao Moreno 已提交
21
const localize = nls.loadMessageBundle();
J
Joao Moreno 已提交
22 23 24 25 26 27
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 已提交
28
export enum RepositoryState {
J
Joao Moreno 已提交
29
	Idle,
J
Joao Moreno 已提交
30
	Disposed
J
Joao Moreno 已提交
31 32
}

J
Joao Moreno 已提交
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
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 已提交
54 55 56 57 58 59
export enum ResourceGroupType {
	Merge,
	Index,
	WorkingTree
}

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

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

		return this._resourceUri;
69 70 71
	}

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

J
Joao Moreno 已提交
80
	get resourceGroupType(): ResourceGroupType { return this._resourceGroupType; }
J
Joao Moreno 已提交
81
	get type(): Status { return this._type; }
J
Joao Moreno 已提交
82 83
	get original(): Uri { return this._resourceUri; }
	get renameResourceUri(): Uri | undefined { return this._renameResourceUri; }
J
Joao Moreno 已提交
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 129

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

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 {
J
Joao Moreno 已提交
174 175
		const light = { iconPath: this.getIconPath('light') };
		const dark = { iconPath: this.getIconPath('dark') };
176
		const tooltip = this.tooltip;
177 178
		const strikeThrough = this.strikeThrough;
		const faded = this.faded;
J
Joao Moreno 已提交
179

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

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

191
export enum Operation {
J
Joao Moreno 已提交
192
	Status = 1 << 0,
J
Joao Moreno 已提交
193 194
	Add = 1 << 1,
	RevertFiles = 1 << 2,
J
Joao Moreno 已提交
195 196 197 198
	Commit = 1 << 3,
	Clean = 1 << 4,
	Branch = 1 << 5,
	Checkout = 1 << 6,
J
Joao Moreno 已提交
199 200 201 202 203
	Reset = 1 << 7,
	Fetch = 1 << 8,
	Pull = 1 << 9,
	Push = 1 << 10,
	Sync = 1 << 11,
J
Joao Moreno 已提交
204 205 206 207 208 209 210 211
	Show = 1 << 12,
	Stage = 1 << 13,
	GetCommitTemplate = 1 << 14,
	DeleteBranch = 1 << 15,
	Merge = 1 << 16,
	Ignore = 1 << 17,
	Tag = 1 << 18,
	Stash = 1 << 19
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 已提交
299 300
	private _onDidChangeState = new EventEmitter<RepositoryState>();
	readonly onDidChangeState: Event<RepositoryState> = this._onDidChangeState.event;
J
Joao Moreno 已提交
301

J
Joao Moreno 已提交
302 303
	private _onDidChangeStatus = new EventEmitter<void>();
	readonly onDidChangeStatus: Event<void> = this._onDidChangeStatus.event;
J
Joao Moreno 已提交
304

305 306 307 308 309 310 311 312 313 314 315
	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 已提交
316 317 318
	private _sourceControl: SourceControl;
	get sourceControl(): SourceControl { return this._sourceControl; }

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

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

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

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

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

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

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

		this._HEAD = undefined;
		this._refs = [];
		this._remotes = [];
J
Joao Moreno 已提交
357 358 359 360
		this.mergeGroup.resourceStates = [];
		this.indexGroup.resourceStates = [];
		this.workingTreeGroup.resourceStates = [];
		this._sourceControl.count = 0;
J
Joao Moreno 已提交
361 362
	}

363 364 365 366
	get root(): string {
		return this.repository.root;
	}

367 368
	private isRepositoryHuge = false;
	private didWarnAboutLimit = false;
J
Joao Moreno 已提交
369
	private disposables: Disposable[] = [];
J
Joao Moreno 已提交
370

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

377
		const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
J
Joao 已提交
378 379
		const onRepositoryChange = filterEvent(onWorkspaceChange, uri => !/^\.\./.test(path.relative(repository.root, uri.fsPath)));
		onRepositoryChange(this.onFSChange, this, this.disposables);
380

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

J
Joao 已提交
385
		const label = `${path.basename(repository.root)} (Git)`;
386

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

J
Joao Moreno 已提交
405 406 407 408 409
		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 已提交
410
		this.updateCommitTemplate();
J
Joao Moreno 已提交
411
		this.status();
J
Joao Moreno 已提交
412 413
	}

J
Joao Moreno 已提交
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
	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
		}
	}

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

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

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

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

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

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

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

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

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

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

				if (!scmResource) {
					return;
				}

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
527 528 529 530 531 532 533 534
	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 已提交
535
	@throttle
J
Joao Moreno 已提交
536
	async fetch(): Promise<void> {
J
Joao Moreno 已提交
537
		try {
538
			await this.run(Operation.Fetch, () => this.repository.fetch());
J
Joao Moreno 已提交
539 540
		} catch (err) {
			// noop
541
		}
J
Joao Moreno 已提交
542 543
	}

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
594 595 596 597 598 599 600 601 602 603
	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));
604 605
	}

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

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

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

J
Joao Moreno 已提交
621
			await window.showTextDocument(document);
J
Joao Moreno 已提交
622

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

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

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

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

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

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

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

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

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

666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
	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 已提交
684
	@throttle
685
	private async updateModelState(): Promise<void> {
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
		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 已提交
705
		let HEAD: Branch | undefined;
J
Joao Moreno 已提交
706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731

		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 已提交
732 733
			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 已提交
734 735

			switch (raw.x + raw.y) {
736 737 738 739 740 741 742 743 744
				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 已提交
745 746 747 748 749
			}

			let isModifiedInIndex = false;

			switch (raw.x) {
750 751 752 753 754
				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 已提交
755 756 757
			}

			switch (raw.y) {
758 759
				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 已提交
760 761 762
			}
		});

J
Joao Moreno 已提交
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
		// 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) {
J
Joao 已提交
783 784
			case RepositoryState.Idle: stateContextKey = 'idle'; break;
			case RepositoryState.Disposed: stateContextKey = 'norepo'; break;
J
Joao Moreno 已提交
785 786
		}

J
Joao Moreno 已提交
787
		this._onDidChangeStatus.fire();
J
Joao Moreno 已提交
788 789 790
	}

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

		if (!autorefresh) {
			return;
		}

798 799 800 801
		if (this.isRepositoryHuge) {
			return;
		}

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

		this.eventuallyUpdateWhenIdleAndWait();
	}

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

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

J
Joao 已提交
821 822 823 824 825 826 827 828 829 830 831 832 833 834
	private async whenIdleAndFocused(): Promise<void> {
		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;
835 836 837
		}
	}

J
Joao Moreno 已提交
838
	dispose(): void {
J
Joao Moreno 已提交
839
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
840
	}
841
}