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

git: wrap up nogit state

上级 ac703e0d
......@@ -65,7 +65,8 @@ async function init(disposables: Disposable[]): Promise<void> {
checkoutStatusBar,
syncStatusBar,
autoFetcher,
mergeDecorator
mergeDecorator,
model
);
}
......
......@@ -7,7 +7,7 @@
import { Uri, EventEmitter, Event, SCMResource, SCMResourceDecorations, SCMResourceGroup, Disposable, window, workspace } from 'vscode';
import { Repository, Ref, Branch, Remote, PushOptions, Commit, GitErrorCodes } from './git';
import { anyEvent, eventToPromise, filterEvent, mapEvent } from './util';
import { anyEvent, eventToPromise, filterEvent, mapEvent, EmptyDisposable, combinedDisposable } from './util';
import { memoize, throttle, debounce } from './decorators';
import { watch } from './watch';
import * as path from 'path';
......@@ -20,6 +20,12 @@ function getIconUri(iconName: string, theme: string): Uri {
return Uri.file(path.join(iconsRootPath, theme, `${iconName}.svg`));
}
export enum State {
Uninitialized,
Idle,
NotAGitRepository
}
export enum Status {
INDEX_MODIFIED,
INDEX_ADDED,
......@@ -139,7 +145,7 @@ export class MergeGroup extends ResourceGroup {
static readonly ID = 'merge';
constructor(resources: Resource[]) {
constructor(resources: Resource[] = []) {
super(MergeGroup.ID, localize('merge changes', "Merge Changes"), resources);
}
}
......@@ -148,7 +154,7 @@ export class IndexGroup extends ResourceGroup {
static readonly ID = 'index';
constructor(resources: Resource[]) {
constructor(resources: Resource[] = []) {
super(IndexGroup.ID, localize('staged changes', "Staged Changes"), resources);
}
}
......@@ -157,7 +163,7 @@ export class WorkingTreeGroup extends ResourceGroup {
static readonly ID = 'workingTree';
constructor(resources: Resource[]) {
constructor(resources: Resource[] = []) {
super(WorkingTreeGroup.ID, localize('changes', "Changes"), resources);
}
}
......@@ -176,6 +182,7 @@ export enum Operation {
Push = 1 << 10,
Sync = 1 << 11,
Init = 1 << 12,
UpdateModel = 1 << 13
}
export interface Operations {
......@@ -212,16 +219,18 @@ export interface CommitOptions {
signoff?: boolean;
}
export enum State {
Uninitialized,
Idle,
NotAGitRepository
}
export class Model implements Disposable {
export class Model {
private _onDidChangeState = new EventEmitter<State>();
readonly onDidChangeState: Event<State> = this._onDidChangeState.event;
private _onDidChange = new EventEmitter<SCMResourceGroup[]>();
readonly onDidChange: Event<SCMResourceGroup[]> = this._onDidChange.event;
private _onDidChangeResources = new EventEmitter<SCMResourceGroup[]>();
readonly onDidChangeResources: Event<SCMResourceGroup[]> = this._onDidChangeResources.event;
@memoize
get onDidChange(): Event<void> {
return anyEvent<any>(this.onDidChangeState, this.onDidChangeResources);
}
private _onRunOperation = new EventEmitter<Operation>();
readonly onRunOperation: Event<Operation> = this._onRunOperation.event;
......@@ -229,9 +238,6 @@ export class Model {
private _onDidRunOperation = new EventEmitter<Operation>();
readonly onDidRunOperation: Event<Operation> = this._onDidRunOperation.event;
private _onDidChangeState = new EventEmitter<State>();
readonly onDidChangeState: Event<State> = this._onDidChangeState.event;
@memoize
get onDidChangeOperations(): Event<void> {
return anyEvent(this.onRunOperation as Event<any>, this.onDidRunOperation as Event<any>);
......@@ -262,6 +268,21 @@ export class Model {
return result;
}
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;
}
private _operations = new OperationsImpl();
get operations(): Operations { return this._operations; }
......@@ -272,65 +293,33 @@ export class Model {
set state(state: State) {
this._state = state;
this._onDidChangeState.fire(state);
this._HEAD = undefined;
this._refs = [];
this._remotes = [];
this._mergeGroup = new MergeGroup();
this._indexGroup = new IndexGroup();
this._workingTreeGroup = new WorkingTreeGroup();
this._onDidChangeResources.fire(this.resources);
}
private disposables: Disposable[] = [];
private repositoryDisposable: Disposable = EmptyDisposable;
constructor(
private repository: Repository,
private onWorkspaceChange: Event<Uri>
) {
this.initialize().catch(err => console.error(err));
}
private async initialize(): Promise<void> {
try {
this.repositoryRoot = await this.repository.getRoot();
this.state = State.Idle;
/* 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(this.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(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.fsPath));
onNonGitChange(this.onFSChange, this, this.disposables);
this.status();
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) {
this.state = State.NotAGitRepository;
} else {
throw err;
}
}
}
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;
this.status();
}
@throttle
async init(): Promise<void> {
await this.run(Operation.Init, () => this.repository.init());
if (this.state !== State.NotAGitRepository) {
return;
}
await this.repository.init();
await this.status();
}
@throttle
......@@ -432,14 +421,23 @@ export class Model {
await this.run(Operation.Sync, () => this.repository.sync());
}
private async run(operation: Operation, fn: () => Promise<void> = () => Promise.resolve()): Promise<void> {
private async run(operation: Operation, runOperation: () => Promise<void> = () => Promise.resolve()): Promise<void> {
return window.withScmProgress(async () => {
this._operations = this._operations.start(operation);
this._onRunOperation.fire(operation);
try {
await fn();
await this.assertIdleState();
await runOperation();
await this.update();
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) {
// TODO@Joao!
this.repositoryDisposable.dispose();
this.state = State.NotAGitRepository;
} else {
throw err;
}
} finally {
this._operations = this._operations.end(operation);
this._onDidRunOperation.fire(operation);
......@@ -447,6 +445,37 @@ export class Model {
});
}
/* 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.
*/
private async assertIdleState(): Promise<void> {
if (this.state === State.Idle) {
return;
}
const repositoryRoot = await this.repository.getRoot();
this.repositoryDisposable.dispose();
this.repositoryRoot = repositoryRoot;
const disposables: Disposable[] = [];
const gitPath = path.join(repositoryRoot, '.git');
const { event, disposable: watcher } = watch(gitPath);
disposables.push(watcher);
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, disposables);
const onNonGitChange = filterEvent(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.fsPath));
onNonGitChange(this.onFSChange, this, disposables);
this.repositoryDisposable = combinedDisposable(disposables);
this.state = State.Idle;
}
@throttle
private async update(): Promise<void> {
const status = await this.repository.getStatus();
......@@ -511,8 +540,7 @@ export class Model {
this._mergeGroup = new MergeGroup(merge);
this._indexGroup = new IndexGroup(index);
this._workingTreeGroup = new WorkingTreeGroup(workingTree);
this._onDidChange.fire(this.resources);
this._onDidChangeResources.fire(this.resources);
}
private onFSChange(uri: Uri): void {
......@@ -547,4 +575,8 @@ export class Model {
await eventToPromise(this.onDidRunOperation);
}
}
dispose(): void {
this.repositoryDisposable.dispose();
}
}
\ No newline at end of file
......@@ -8,7 +8,7 @@
import { scm, Uri, Disposable, SCMProvider, SCMResourceGroup, Event, ProviderResult, workspace } from 'vscode';
import { Model, Resource, ResourceGroup, State } from './model';
import { CommandCenter } from './commands';
import { anyEvent, mapEvent } from './util';
import { mapEvent } from './util';
export class GitSCMProvider implements SCMProvider {
......@@ -17,7 +17,7 @@ export class GitSCMProvider implements SCMProvider {
get resources(): SCMResourceGroup[] { return this.model.resources; }
get onDidChange(): Event<SCMResourceGroup[]> {
return mapEvent(anyEvent<any>(this.model.onDidChange, this.model.onDidChangeState), () => this.model.resources);
return mapEvent(this.model.onDidChange, () => this.model.resources);
}
get label(): string { return 'Git'; }
......
......@@ -30,9 +30,7 @@ export class CheckoutStatusBar {
const HEAD = this.model.HEAD;
if (!HEAD) {
this.raw.command = '';
this.raw.color = 'rgb(100, 100, 100)';
this.raw.text = 'unknown';
this.raw.hide();
return;
}
......@@ -48,6 +46,7 @@ export class CheckoutStatusBar {
(this.model.workingTreeGroup.resources.length > 0 ? '*' : '') +
(this.model.indexGroup.resources.length > 0 ? '+' : '') +
(this.model.mergeGroup.resources.length > 0 ? '!' : '');
this.raw.show();
}
dispose(): void {
......
......@@ -28,6 +28,8 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable {
return toDisposable(() => dispose(disposables));
}
export const EmptyDisposable = toDisposable(() => null);
export function mapEvent<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册