提交 3af429fd 编写于 作者: J Joao Moreno

git: improve fs events reaction

上级 4e64aa3e
......@@ -126,7 +126,7 @@ export class CommandCenter {
@CommandCenter.Command('git.refresh')
@CommandCenter.CatchErrors
async refresh(): Promise<void> {
await this.model.update();
await this.model.status();
}
@CommandCenter.Command('git.openChange')
......
......@@ -5,44 +5,19 @@
'use strict';
import { ExtensionContext, workspace, Uri, window, Disposable, Event } from 'vscode';
import { ExtensionContext, workspace, window, Disposable } from 'vscode';
import { findGit, Git } from './git';
import { Model } from './model';
import { GitSCMProvider } from './scmProvider';
import { CommandCenter } from './commands';
import { CheckoutStatusBar, SyncStatusBar } from './statusbar';
import { filterEvent, anyEvent, throttle } from './util';
import { filterEvent, anyEvent } from './util';
import { GitContentProvider } from './contentProvider';
import { AutoFetcher } from './autofetch';
import * as nls from 'vscode-nls';
import { decorate, debounce } from 'core-decorators';
nls.config();
class Watcher {
private listener: Disposable;
constructor(private model: Model, onWorkspaceChange: Event<Uri>) {
this.listener = onWorkspaceChange(this.eventuallyUpdateModel, this);
}
@debounce(1000)
private eventuallyUpdateModel(): void {
this.updateModelAndWait();
}
@decorate(throttle)
private async updateModelAndWait(): Promise<void> {
await this.model.update();
await new Promise(c => setTimeout(c, 8000));
}
dispose(): void {
this.listener.dispose();
}
}
async function init(disposables: Disposable[]): Promise<void> {
const rootPath = workspace.rootPath;
......
......@@ -7,7 +7,8 @@
import { Uri, EventEmitter, Event, SCMResource, SCMResourceDecorations, SCMResourceGroup, Disposable } from 'vscode';
import { Repository, IRef, IBranch, IRemote, IPushOptions } from './git';
import { throttle, anyEvent, eventToPromise } from './util';
import { throttle, anyEvent, eventToPromise, filterEvent, mapEvent } from './util';
import { watch } from './watch';
import { decorate, memoize, debounce } from 'core-decorators';
import * as path from 'path';
......@@ -242,7 +243,22 @@ export class Model {
private repository: Repository,
onWorkspaceChange: Event<Uri>
) {
onWorkspaceChange(this.onWorkspaceChange, this, this.disposables);
/* We use the native Node `watch` for faster, non debounced events.
* That way we hopefully get the events during the operations we're
* performing, thus sparing useless `git status` calls to refresh
* the model's state.
*/
const gitPath = path.join(_repositoryRoot, '.git');
const { event, disposable } = watch(gitPath);
const onGitChange = mapEvent(event, ({ filename }) => Uri.file(path.join(gitPath, filename)));
const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.fsPath));
onRelevantGitChange(this.onFSChange, this, this.disposables);
this.disposables.push(disposable);
const onNonGitChange = filterEvent(onWorkspaceChange, uri => !/\/\.git\//.test(uri.fsPath));
onNonGitChange(this.onFSChange, this, this.disposables);
this.status();
}
get repositoryRoot(): string {
......@@ -265,90 +281,18 @@ export class Model {
}
@decorate(throttle)
async update(): Promise<void> {
await this.run(Operation.Status, async () => {
const status = await this.repository.getStatus();
let HEAD: IBranch | undefined;
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 => {
const uri = Uri.file(path.join(this.repositoryRoot, raw.path));
switch (raw.x + raw.y) {
case '??': return workingTree.push(new Resource(uri, Status.UNTRACKED));
case '!!': return workingTree.push(new Resource(uri, Status.IGNORED));
case 'DD': return merge.push(new Resource(uri, Status.BOTH_DELETED));
case 'AU': return merge.push(new Resource(uri, Status.ADDED_BY_US));
case 'UD': return merge.push(new Resource(uri, Status.DELETED_BY_THEM));
case 'UA': return merge.push(new Resource(uri, Status.ADDED_BY_THEM));
case 'DU': return merge.push(new Resource(uri, Status.DELETED_BY_US));
case 'AA': return merge.push(new Resource(uri, Status.BOTH_ADDED));
case 'UU': return merge.push(new Resource(uri, Status.BOTH_MODIFIED));
}
let isModifiedInIndex = false;
switch (raw.x) {
case 'M': index.push(new Resource(uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break;
case 'A': index.push(new Resource(uri, Status.INDEX_ADDED)); break;
case 'D': index.push(new Resource(uri, Status.INDEX_DELETED)); break;
case 'R': index.push(new Resource(uri, Status.INDEX_RENAMED/*, raw.rename*/)); break;
case 'C': index.push(new Resource(uri, Status.INDEX_COPIED)); break;
}
switch (raw.y) {
case 'M': workingTree.push(new Resource(uri, Status.MODIFIED/*, raw.rename*/)); break;
case 'D': workingTree.push(new Resource(uri, Status.DELETED/*, raw.rename*/)); break;
}
});
this._mergeGroup = new MergeGroup(merge);
this._indexGroup = new IndexGroup(index);
this._workingTreeGroup = new WorkingTreeGroup(workingTree);
this._onDidChange.fire(this.resources);
});
async status(): Promise<void> {
await this.run(Operation.Status);
}
@decorate(throttle)
async stage(...resources: Resource[]): Promise<void> {
await this.run(Operation.Stage, async () => {
const paths = resources.map(r => r.uri.fsPath);
await this.repository.add(paths);
await this.update();
});
await this.run(Operation.Stage, () => this.repository.add(resources.map(r => r.uri.fsPath)));
}
@decorate(throttle)
async unstage(...resources: Resource[]): Promise<void> {
await this.run(Operation.Unstage, async () => {
const paths = resources.map(r => r.uri.fsPath);
await this.repository.revertFiles('HEAD', paths);
await this.update();
});
await this.run(Operation.Unstage, () => this.repository.revertFiles('HEAD', resources.map(r => r.uri.fsPath)));
}
@decorate(throttle)
......@@ -359,7 +303,6 @@ export class Model {
}
await this.repository.commit(message, opts);
await this.update();
});
}
......@@ -393,72 +336,132 @@ export class Model {
}
await Promise.all(promises);
await this.update();
});
}
@decorate(throttle)
async branch(name: string): Promise<void> {
await this.run(Operation.Branch, async () => {
await this.repository.branch(name, true);
await this.update();
});
await this.run(Operation.Branch, () => this.repository.branch(name, true));
}
@decorate(throttle)
async checkout(treeish: string): Promise<void> {
await this.run(Operation.Checkout, async () => {
await this.repository.checkout(treeish, []);
await this.update();
});
await this.run(Operation.Checkout, () => this.repository.checkout(treeish, []));
}
@decorate(throttle)
async fetch(): Promise<void> {
await this.run(Operation.Fetch, async () => {
await this.repository.fetch();
await this.update();
});
await this.run(Operation.Fetch, () => this.repository.fetch());
}
@decorate(throttle)
async sync(): Promise<void> {
await this.run(Operation.Sync, async () => {
await this.repository.sync();
await this.update();
});
await this.run(Operation.Sync, () => this.repository.sync());
}
@decorate(throttle)
async push(remote?: string, name?: string, options?: IPushOptions): Promise<void> {
await this.run(Operation.Push, async () => {
await this.repository.push(remote, name, options);
await this.update();
});
await this.run(Operation.Push, () => this.repository.push(remote, name, options));
}
private async run(operation: Operation, fn: () => Promise<void>): Promise<void> {
private async run(operation: Operation, fn: () => Promise<void> = () => Promise.resolve()): Promise<void> {
this._operations = this._operations.start(operation);
this._onRunOperation.fire(operation);
try {
await fn();
await this.update();
} finally {
this._operations = this._operations.end(operation);
this._onDidRunOperation.fire(operation);
}
}
@decorate(throttle)
private async update(): Promise<void> {
const status = await this.repository.getStatus();
let HEAD: IBranch | undefined;
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 => {
const uri = Uri.file(path.join(this.repositoryRoot, raw.path));
switch (raw.x + raw.y) {
case '??': return workingTree.push(new Resource(uri, Status.UNTRACKED));
case '!!': return workingTree.push(new Resource(uri, Status.IGNORED));
case 'DD': return merge.push(new Resource(uri, Status.BOTH_DELETED));
case 'AU': return merge.push(new Resource(uri, Status.ADDED_BY_US));
case 'UD': return merge.push(new Resource(uri, Status.DELETED_BY_THEM));
case 'UA': return merge.push(new Resource(uri, Status.ADDED_BY_THEM));
case 'DU': return merge.push(new Resource(uri, Status.DELETED_BY_US));
case 'AA': return merge.push(new Resource(uri, Status.BOTH_ADDED));
case 'UU': return merge.push(new Resource(uri, Status.BOTH_MODIFIED));
}
let isModifiedInIndex = false;
switch (raw.x) {
case 'M': index.push(new Resource(uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break;
case 'A': index.push(new Resource(uri, Status.INDEX_ADDED)); break;
case 'D': index.push(new Resource(uri, Status.INDEX_DELETED)); break;
case 'R': index.push(new Resource(uri, Status.INDEX_RENAMED/*, raw.rename*/)); break;
case 'C': index.push(new Resource(uri, Status.INDEX_COPIED)); break;
}
switch (raw.y) {
case 'M': workingTree.push(new Resource(uri, Status.MODIFIED/*, raw.rename*/)); break;
case 'D': workingTree.push(new Resource(uri, Status.DELETED/*, raw.rename*/)); break;
}
});
this._mergeGroup = new MergeGroup(merge);
this._indexGroup = new IndexGroup(index);
this._workingTreeGroup = new WorkingTreeGroup(workingTree);
this._onDidChange.fire(this.resources);
}
private onFSChange(uri: Uri): void {
if (!this.operations.isIdle()) {
return;
}
this.eventuallyUpdateWhenIdleAndWait();
}
@debounce(1000)
private onWorkspaceChange(): void {
private eventuallyUpdateWhenIdleAndWait(): void {
this.updateWhenIdleAndWait();
}
@decorate(throttle)
private async updateWhenIdleAndWait(): Promise<void> {
await this.whenIdle();
await this.update();
await new Promise(c => setTimeout(c, 7000));
await this.status();
await new Promise(c => setTimeout(c, 5000));
}
private async whenIdle(): Promise<void> {
......
......@@ -18,7 +18,6 @@ export class GitSCMProvider implements SCMProvider {
get label(): string { return 'Git'; }
constructor(private model: Model, private commandCenter: CommandCenter) {
model.update();
scm.registerSCMProvider('git', this);
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { EventEmitter, Event, Disposable } from 'vscode';
import * as fs from 'fs';
export interface FSEvent {
eventType: string;
filename: string;
}
export function watch(path: string): { event: Event<FSEvent>; disposable: Disposable; } {
const emitter = new EventEmitter<FSEvent>();
const event = emitter.event;
const watcher = fs.watch(path, (eventType, filename) => emitter.fire({ eventType, filename }));
const disposable = new Disposable(() => watcher.close());
return { event, disposable };
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册