code-server/extensions/git/src/autofetch.ts

126 lines
4.0 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, Uri } from 'vscode';
import { Repository, Operation } from './repository';
import { eventToPromise, filterEvent, onceEvent } from './util';
import * as nls from 'vscode-nls';
import { GitErrorCodes } from './api/git';
const localize = nls.loadMessageBundle();
function isRemoteOperation(operation: Operation): boolean {
return operation === Operation.Pull || operation === Operation.Push || operation === Operation.Sync || operation === Operation.Fetch;
}
export class AutoFetcher {
private static DidInformUser = 'autofetch.didInformUser';
private _onDidChange = new EventEmitter<boolean>();
private onDidChange = this._onDidChange.event;
private _enabled: boolean = false;
get enabled(): boolean { return this._enabled; }
set enabled(enabled: boolean) { this._enabled = enabled; this._onDidChange.fire(enabled); }
private disposables: Disposable[] = [];
constructor(private repository: Repository, private globalState: Memento) {
workspace.onDidChangeConfiguration(this.onConfiguration, this, this.disposables);
this.onConfiguration();
const onGoodRemoteOperation = filterEvent(repository.onDidRunOperation, ({ operation, error }) => !error && isRemoteOperation(operation));
const onFirstGoodRemoteOperation = onceEvent(onGoodRemoteOperation);
onFirstGoodRemoteOperation(this.onFirstGoodRemoteOperation, this, this.disposables);
}
private async onFirstGoodRemoteOperation(): Promise<void> {
const didInformUser = !this.globalState.get<boolean>(AutoFetcher.DidInformUser);
if (this.enabled && !didInformUser) {
this.globalState.update(AutoFetcher.DidInformUser, true);
}
const shouldInformUser = !this.enabled && didInformUser;
if (!shouldInformUser) {
return;
}
const yes: MessageItem = { title: localize('yes', "Yes") };
const no: MessageItem = { isCloseAffordance: true, title: localize('no', "No") };
const askLater: MessageItem = { title: localize('not now', "Ask Me Later") };
const result = await window.showInformationMessage(localize('suggest auto fetch', "Would you like Code to [periodically run 'git fetch']({0})?", 'https://go.microsoft.com/fwlink/?linkid=865294'), yes, no, askLater);
if (result === askLater) {
return;
}
if (result === yes) {
const gitConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
gitConfig.update('autofetch', true, ConfigurationTarget.Global);
}
this.globalState.update(AutoFetcher.DidInformUser, true);
}
private onConfiguration(): void {
const gitConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
if (gitConfig.get<boolean>('autofetch') === false) {
this.disable();
} else {
this.enable();
}
}
enable(): void {
if (this.enabled) {
return;
}
this.enabled = true;
this.run();
}
disable(): void {
this.enabled = false;
}
private async run(): Promise<void> {
while (this.enabled) {
await this.repository.whenIdleAndFocused();
if (!this.enabled) {
return;
}
try {
await this.repository.fetchDefault({ silent: true });
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
this.disable();
}
}
if (!this.enabled) {
return;
}
const period = workspace.getConfiguration('git', Uri.file(this.repository.root)).get<number>('autofetchPeriod', 180) * 1000;
const timeout = new Promise(c => setTimeout(c, period));
const whenDisabled = eventToPromise(filterEvent(this.onDidChange, enabled => !enabled));
await Promise.race([timeout, whenDisabled]);
}
}
dispose(): void {
this.disable();
this.disposables.forEach(d => d.dispose());
}
}