scm: move from pull to push model

This commit is contained in:
Joao Moreno
2017-03-29 22:44:25 +02:00
parent 12470dccf6
commit ccfcbe6c6f
15 changed files with 660 additions and 549 deletions

View File

@@ -112,7 +112,12 @@ export class CommandCenter {
await this.model.status();
}
async open(resource: Resource): Promise<void> {
@command('git.openResource')
async openResource(resource: Resource): Promise<void> {
await this._openResource(resource);
}
private async _openResource(resource: Resource): Promise<void> {
const left = this.getLeftResource(resource);
const right = this.getRightResource(resource);
const title = this.getTitle(resource);
@@ -137,7 +142,7 @@ export class CommandCenter {
return resource.original.with({ scheme: 'git', query: 'HEAD' });
case Status.MODIFIED:
return resource.sourceUri.with({ scheme: 'git', query: '~' });
return resource.resourceUri.with({ scheme: 'git', query: '~' });
}
}
@@ -146,34 +151,34 @@ export class CommandCenter {
case Status.INDEX_MODIFIED:
case Status.INDEX_ADDED:
case Status.INDEX_COPIED:
return resource.sourceUri.with({ scheme: 'git' });
return resource.resourceUri.with({ scheme: 'git' });
case Status.INDEX_RENAMED:
return resource.sourceUri.with({ scheme: 'git' });
return resource.resourceUri.with({ scheme: 'git' });
case Status.INDEX_DELETED:
case Status.DELETED:
return resource.sourceUri.with({ scheme: 'git', query: 'HEAD' });
return resource.resourceUri.with({ scheme: 'git', query: 'HEAD' });
case Status.MODIFIED:
case Status.UNTRACKED:
case Status.IGNORED:
const uriString = resource.sourceUri.toString();
const [indexStatus] = this.model.indexGroup.resources.filter(r => r.sourceUri.toString() === uriString);
const uriString = resource.resourceUri.toString();
const [indexStatus] = this.model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString);
if (indexStatus && indexStatus.rename) {
return indexStatus.rename;
if (indexStatus && indexStatus.renameResourceUri) {
return indexStatus.renameResourceUri;
}
return resource.sourceUri;
return resource.resourceUri;
case Status.BOTH_MODIFIED:
return resource.sourceUri;
return resource.resourceUri;
}
}
private getTitle(resource: Resource): string {
const basename = path.basename(resource.sourceUri.fsPath);
const basename = path.basename(resource.resourceUri.fsPath);
switch (resource.type) {
case Status.INDEX_MODIFIED:
@@ -251,7 +256,7 @@ export class CommandCenter {
return;
}
return await commands.executeCommand<void>('vscode.open', resource.sourceUri);
return await commands.executeCommand<void>('vscode.open', resource.resourceUri);
}
@command('git.openChange')
@@ -262,7 +267,7 @@ export class CommandCenter {
return;
}
return await this.open(resource);
return await this._openResource(resource);
}
@command('git.stage')
@@ -426,7 +431,7 @@ export class CommandCenter {
}
const message = resources.length === 1
? localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].sourceUri.fsPath))
? localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath))
: localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", resources.length);
const yes = localize('discard', "Discard Changes");
@@ -769,20 +774,6 @@ export class CommandCenter {
return undefined;
}
if (uri.scheme === 'git-resource') {
const {resourceGroupId} = JSON.parse(uri.query) as { resourceGroupId: string, sourceUri: string };
const [resourceGroup] = this.model.resources.filter(g => g.contextKey === resourceGroupId);
if (!resourceGroup) {
return;
}
const uriStr = uri.toString();
const [resource] = resourceGroup.resources.filter(r => r.uri.toString() === uriStr);
return resource;
}
if (uri.scheme === 'git') {
uri = uri.with({ scheme: 'file' });
}
@@ -790,8 +781,8 @@ export class CommandCenter {
if (uri.scheme === 'file') {
const uriString = uri.toString();
return this.model.workingTreeGroup.resources.filter(r => r.sourceUri.toString() === uriString)[0]
|| this.model.indexGroup.resources.filter(r => r.sourceUri.toString() === uriString)[0];
return this.model.workingTreeGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0]
|| this.model.indexGroup.resources.filter(r => r.resourceUri.toString() === uriString)[0];
}
}

View File

@@ -78,10 +78,10 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi
}
}
filterEvent(scm.onDidAcceptInputValue, () => scm.activeProvider === provider)
filterEvent(scm.onDidAcceptInputValue, () => scm.activeSourceControl === provider.sourceControl)
(commandCenter.commitWithInput, commandCenter, disposables);
if (scm.activeProvider === provider) {
if (scm.activeSourceControl === provider.sourceControl) {
scm.inputBox.value = await model.getCommitTemplate();
}
}

View File

@@ -62,7 +62,7 @@ class TextEditorMergeDecorator {
return;
}
if (this.model.mergeGroup.resources.some(r => r.type === Status.BOTH_MODIFIED && r.sourceUri.toString() === this.uri)) {
if (this.model.mergeGroup.resources.some(r => r.type === Status.BOTH_MODIFIED && r.resourceUri.toString() === this.uri)) {
decorations = decorate(this.editor.document);
}

View File

@@ -5,7 +5,7 @@
'use strict';
import { Uri, EventEmitter, Event, SCMResource, SCMResourceDecorations, SCMResourceGroup, Disposable, window, workspace } from 'vscode';
import { Uri, Command, EventEmitter, Event, SourceControlResourceState, SourceControlResourceDecorations, Disposable, window, workspace } from 'vscode';
import { Git, Repository, Ref, Branch, Remote, PushOptions, Commit, GitErrorCodes, GitError } from './git';
import { anyEvent, eventToPromise, filterEvent, mapEvent, EmptyDisposable, combinedDisposable, dispose } from './util';
import { memoize, throttle, debounce } from './decorators';
@@ -51,31 +51,29 @@ export enum Status {
BOTH_MODIFIED
}
export class Resource implements SCMResource {
export class Resource implements SourceControlResourceState {
@memoize
get uri(): Uri {
return new Uri().with({
scheme: 'git-resource',
query: JSON.stringify({
resourceGroupId: this.resourceGroupId,
sourceUri: this.sourceUri.toString()
})
});
get resourceUri(): Uri {
if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED)) {
return this.renameResourceUri;
}
return this._resourceUri;
}
@memoize
get sourceUri(): Uri {
if (this.rename && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED)) {
return this.rename;
}
return this._sourceUri;
get command(): Command {
return {
command: 'git.openResource',
title: localize('open', "Open"),
arguments: [this]
};
}
get type(): Status { return this._type; }
get original(): Uri { return this._sourceUri; }
get rename(): Uri | undefined { return this._rename; }
get original(): Uri { return this._resourceUri; }
get renameResourceUri(): Uri | undefined { return this._renameResourceUri; }
private static Icons = {
light: {
@@ -134,23 +132,19 @@ export class Resource implements SCMResource {
}
}
get decorations(): SCMResourceDecorations {
get decorations(): SourceControlResourceDecorations {
const light = { iconPath: this.getIconPath('light') };
const dark = { iconPath: this.getIconPath('dark') };
return { strikeThrough: this.strikeThrough, light, dark };
}
constructor(private resourceGroupId: string, private _sourceUri: Uri, private _type: Status, private _rename?: Uri) {
// console.log(this);
}
constructor(private resourceGroupId: string, private _resourceUri: Uri, private _type: Status, private _renameResourceUri?: Uri) { }
}
export class ResourceGroup implements SCMResourceGroup {
@memoize
get uri(): Uri { return Uri.parse(`git-resource-group:${this.contextKey}`); }
export class ResourceGroup {
get id(): string { return this._id; }
get contextKey(): string { return this._id; }
get label(): string { return this._label; }
get resources(): Resource[] { return this._resources; }
@@ -280,8 +274,8 @@ export class Model implements Disposable {
private _onDidChangeState = new EventEmitter<State>();
readonly onDidChangeState: Event<State> = this._onDidChangeState.event;
private _onDidChangeResources = new EventEmitter<SCMResourceGroup[]>();
readonly onDidChangeResources: Event<SCMResourceGroup[]> = this._onDidChangeResources.event;
private _onDidChangeResources = new EventEmitter<void>();
readonly onDidChangeResources: Event<void> = this._onDidChangeResources.event;
@memoize
get onDidChange(): Event<void> {
@@ -308,22 +302,6 @@ export class Model implements Disposable {
private _workingTreeGroup = new WorkingTreeGroup([]);
get workingTreeGroup(): WorkingTreeGroup { return this._workingTreeGroup; }
get resources(): ResourceGroup[] {
const result: ResourceGroup[] = [];
if (this._mergeGroup.resources.length > 0) {
result.push(this._mergeGroup);
}
if (this._indexGroup.resources.length > 0) {
result.push(this._indexGroup);
}
result.push(this._workingTreeGroup);
return result;
}
private _HEAD: Branch | undefined;
get HEAD(): Branch | undefined {
return this._HEAD;
@@ -356,7 +334,7 @@ export class Model implements Disposable {
this._mergeGroup = new MergeGroup();
this._indexGroup = new IndexGroup();
this._workingTreeGroup = new WorkingTreeGroup();
this._onDidChangeResources.fire(this.resources);
this._onDidChangeResources.fire();
}
private onWorkspaceChange: Event<Uri>;
@@ -412,7 +390,7 @@ export class Model implements Disposable {
@throttle
async add(...resources: Resource[]): Promise<void> {
await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.sourceUri.fsPath)));
await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.resourceUri.fsPath)));
}
@throttle
@@ -423,7 +401,7 @@ export class Model implements Disposable {
@throttle
async revertFiles(...resources: Resource[]): Promise<void> {
await this.run(Operation.RevertFiles, () => this.repository.revertFiles('HEAD', resources.map(r => r.sourceUri.fsPath)));
await this.run(Operation.RevertFiles, () => this.repository.revertFiles('HEAD', resources.map(r => r.resourceUri.fsPath)));
}
@throttle
@@ -447,11 +425,11 @@ export class Model implements Disposable {
switch (r.type) {
case Status.UNTRACKED:
case Status.IGNORED:
toClean.push(r.sourceUri.fsPath);
toClean.push(r.resourceUri.fsPath);
break;
default:
toCheckout.push(r.sourceUri.fsPath);
toCheckout.push(r.resourceUri.fsPath);
break;
}
});
@@ -671,7 +649,7 @@ export class Model implements Disposable {
this._mergeGroup = new MergeGroup(merge);
this._indexGroup = new IndexGroup(index);
this._workingTreeGroup = new WorkingTreeGroup(workingTree);
this._onDidChangeResources.fire(this.resources);
this._onDidChangeResources.fire();
}
private onFSChange(uri: Uri): void {

View File

@@ -5,17 +5,15 @@
'use strict';
import { scm, Uri, Disposable, SCMProvider, SCMResourceGroup, Event, workspace } from 'vscode';
import { Model, Resource, State } from './model';
import { scm, Uri, Disposable, SourceControl, SourceControlResourceGroup, Event, workspace, commands } from 'vscode';
import { Model, State } from './model';
import { CommandCenter } from './commands';
import { mapEvent } from './util';
export class GitSCMProvider implements SCMProvider {
export class GitSCMProvider {
private disposables: Disposable[] = [];
get contextKey(): string { return 'git'; }
get resources(): SCMResourceGroup[] { return this.model.resources; }
get onDidChange(): Event<this> {
return mapEvent(this.model.onDidChange, () => this);
@@ -38,16 +36,40 @@ export class GitSCMProvider implements SCMProvider {
switch (countBadge) {
case 'off': return 0;
case 'tracked': return this.model.indexGroup.resources.length;
default: return this.model.resources.reduce((r, g) => r + g.resources.length, 0);
default:
return this.model.mergeGroup.resources.length
+ this.model.indexGroup.resources.length
+ this.model.workingTreeGroup.resources.length;
}
}
constructor(private model: Model, private commandCenter: CommandCenter) {
scm.registerSCMProvider(this);
private _sourceControl: SourceControl;
get sourceControl(): SourceControl {
return this._sourceControl;
}
open(resource: Resource): void {
this.commandCenter.open(resource);
private mergeGroup: SourceControlResourceGroup;
private indexGroup: SourceControlResourceGroup;
private workingTreeGroup: SourceControlResourceGroup;
constructor(private model: Model, private commandCenter: CommandCenter) {
this._sourceControl = scm.createSourceControl('git', 'Git');
this._sourceControl.quickDiffProvider = this;
this.disposables.push(this._sourceControl);
this.mergeGroup = this._sourceControl.createResourceGroup(model.mergeGroup.id, model.mergeGroup.label);
this.indexGroup = this._sourceControl.createResourceGroup(model.indexGroup.id, model.indexGroup.label);
this.workingTreeGroup = this._sourceControl.createResourceGroup(model.workingTreeGroup.id, model.workingTreeGroup.label);
this.mergeGroup.hideWhenEmpty = true;
this.indexGroup.hideWhenEmpty = true;
this.disposables.push(this.mergeGroup);
this.disposables.push(this.indexGroup);
this.disposables.push(this.workingTreeGroup);
model.onDidChange(this.onDidModelChange, this, this.disposables);
}
provideOriginalResource(uri: Uri): Uri | undefined {
@@ -60,6 +82,14 @@ export class GitSCMProvider implements SCMProvider {
return new Uri().with({ scheme: 'git-original', query: uri.path, path: uri.path + '.git' });
}
private onDidModelChange(): void {
this.mergeGroup.resourceStates = this.model.mergeGroup.resources;
this.indexGroup.resourceStates = this.model.indexGroup.resources;
this.workingTreeGroup.resourceStates = this.model.workingTreeGroup.resources;
this._sourceControl.count = this.count;
commands.executeCommand('setContext', 'gitState', this.stateContextKey);
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
this.disposables = [];