提交 e3b15fca 编写于 作者: J Joao Moreno

wip: register multiple source control providers

上级 eb04eaa1
......@@ -12,8 +12,8 @@ import { findGit, Git, IGit } from './git';
import { Repository } from './repository';
import { Model } from './model';
import { CommandCenter } from './commands';
import { GitContentProvider } from './contentProvider';
import { AutoFetcher } from './autofetch';
// import { GitContentProvider } from './contentProvider';
// import { AutoFetcher } from './autofetch';
import { Askpass } from './askpass';
import { toDisposable } from './util';
import TelemetryReporter from 'vscode-extension-telemetry';
......@@ -28,24 +28,26 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi
const config = workspace.getConfiguration('git');
const enabled = config.get<boolean>('enabled') === true;
const workspaceRootPath = workspace.rootPath;
const pathHint = workspace.getConfiguration('git').get<string>('path');
const info = await findGit(pathHint);
const askpass = new Askpass();
const env = await askpass.getEnv();
const git = new Git({ gitPath: info.path, version: info.version, env });
const model = new Model();
disposables.push(model);
if (!workspaceRootPath || !enabled) {
if (!enabled) {
const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter);
disposables.push(commandCenter);
return;
}
const workspaceRoot = Uri.file(workspaceRootPath);
const repository = new Repository(git, workspaceRoot);
model.register(workspaceRoot, repository);
for (const folder of workspace.workspaceFolders || []) {
const repositoryRoot = await git.getRepositoryRoot(folder.uri.fsPath);
const repository = new Repository(git.open(repositoryRoot));
model.register(repository);
}
outputChannel.appendLine(localize('using git', "Using git {0} from {1}", info.version, info.path));
......@@ -54,14 +56,14 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi
disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput)));
const commandCenter = new CommandCenter(git, model, outputChannel, telemetryReporter);
const contentProvider = new GitContentProvider(repository);
const autoFetcher = new AutoFetcher(repository);
// const contentProvider = new GitContentProvider(repository);
// const autoFetcher = new AutoFetcher(repository);
disposables.push(
commandCenter,
contentProvider,
autoFetcher,
repository
// contentProvider,
// autoFetcher,
// repository
);
await checkGitVersion(info);
......
......@@ -15,28 +15,30 @@ import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
class RepositoryPick implements QuickPickItem {
@memoize get label(): string { return path.basename(this.repositoryRoot.fsPath); }
@memoize get description(): string { return path.dirname(this.repositoryRoot.fsPath); }
constructor(protected repositoryRoot: Uri, public readonly repository: Repository) { }
@memoize get label(): string { return path.basename(this.repositoryRoot); }
@memoize get description(): string { return path.dirname(this.repositoryRoot); }
constructor(protected repositoryRoot: string, public readonly repository: Repository) { }
}
export class Model {
private repositories: Map<Uri, Repository> = new Map<Uri, Repository>();
private repositories: Map<string, Repository> = new Map<string, Repository>();
register(uri: Uri, repository: Repository): Disposable {
if (this.repositories.has(uri)) {
register(repository: Repository): Disposable {
const root = repository.root;
if (this.repositories.has(root)) {
// TODO@Joao: what should happen?
throw new Error('Cant register repository with the same URI');
}
this.repositories.set(uri, repository);
this.repositories.set(root, repository);
const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.NotAGitRepository);
const listener = onDidDisappearRepository(() => disposable.dispose());
const disposable = toDisposable(once(() => {
this.repositories.delete(uri);
this.repositories.delete(root);
listener.dispose();
}));
......@@ -72,8 +74,7 @@ export class Model {
const resourcePath = hint.fsPath;
for (let [root, repository] of this.repositories) {
const repositoryRootPath = root.fsPath;
const relativePath = path.relative(repositoryRootPath, resourcePath);
const relativePath = path.relative(root, resourcePath);
if (!/^\./.test(relativePath)) {
return repository;
......@@ -95,4 +96,36 @@ export class Model {
return undefined;
}
// private async assertIdleState(): Promise<void> {
// if (this.state === State.Idle) {
// return;
// }
// const disposables: Disposable[] = [];
// const repositoryRoot = await this.git.getRepositoryRoot(this.workspaceRoot.fsPath);
// this.repository = this.git.open(repositoryRoot);
// const onGitChange = filterEvent(this.onWorkspaceChange, uri => /\/\.git\//.test(uri.path));
// const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.path));
// onRelevantGitChange(this.onFSChange, this, disposables);
// onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, disposables);
// const onNonGitChange = filterEvent(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.path));
// onNonGitChange(this.onFSChange, this, disposables);
// this.repositoryDisposable = combinedDisposable(disposables);
// this.isRepositoryHuge = false;
// this.didWarnAboutLimit = false;
// this.state = State.Idle;
// }
dispose(): void {
for (let [, repository] of this.repositories) {
repository.dispose();
}
this.repositories.clear();
}
}
\ No newline at end of file
......@@ -141,8 +141,10 @@ export class Resource implements SourceControlResourceState {
@memoize
private get faded(): boolean {
const workspaceRootPath = this.workspaceRoot.fsPath;
return this.resourceUri.fsPath.substr(0, workspaceRootPath.length) !== workspaceRootPath;
// TODO@joao
return false;
// const workspaceRootPath = this.workspaceRoot.fsPath;
// return this.resourceUri.fsPath.substr(0, workspaceRootPath.length) !== workspaceRootPath;
}
get decorations(): SourceControlResourceDecorations {
......@@ -155,7 +157,6 @@ export class Resource implements SourceControlResourceState {
}
constructor(
private workspaceRoot: Uri,
private _resourceGroupType: ResourceGroupType,
private _resourceUri: Uri,
private _type: Status,
......@@ -269,6 +270,8 @@ export interface GitResourceGroup extends SourceControlResourceGroup {
export class Repository implements Disposable {
private static handle = 0;
private _onDidChangeRepository = new EventEmitter<Uri>();
readonly onDidChangeRepository: Event<Uri> = this._onDidChangeRepository.event;
......@@ -321,8 +324,6 @@ export class Repository implements Disposable {
private _operations = new OperationsImpl();
get operations(): Operations { return this._operations; }
private repository: BaseRepository;
private _state = State.Uninitialized;
get state(): State { return this._state; }
set state(state: State) {
......@@ -339,22 +340,27 @@ export class Repository implements Disposable {
commands.executeCommand('setContext', 'gitState', '');
}
private onWorkspaceChange: Event<Uri>;
get root(): string {
return this.repository.root;
}
private isRepositoryHuge = false;
private didWarnAboutLimit = false;
private repositoryDisposable: Disposable = EmptyDisposable;
private disposables: Disposable[] = [];
constructor(
private _git: Git,
private workspaceRoot: Uri
private readonly repository: BaseRepository
) {
const fsWatcher = workspace.createFileSystemWatcher('**');
this.onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
this.disposables.push(fsWatcher);
const label = `Git - ${path.basename(workspaceRoot.fsPath)}`;
this._sourceControl = scm.createSourceControl('git', label);
const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete);
onWorkspaceChange(this.onFSChange, this, this.disposables);
const id = `git${Repository.handle++}`;
const label = `Git - ${path.basename(repository.root)}`;
this._sourceControl = scm.createSourceControl(id, label);
this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit") };
this._sourceControl.quickDiffProvider = this;
this.disposables.push(this._sourceControl);
......@@ -391,15 +397,15 @@ export class Repository implements Disposable {
}
}
@throttle
async init(): Promise<void> {
if (this.state !== State.NotAGitRepository) {
return;
}
// @throttle
// async init(): Promise<void> {
// if (this.state !== State.NotAGitRepository) {
// return;
// }
await this._git.init(this.workspaceRoot.fsPath);
await this.status();
}
// await this.git.init(this.workspaceRoot.fsPath);
// await this.status();
// }
@throttle
async status(): Promise<void> {
......@@ -594,13 +600,15 @@ export class Repository implements Disposable {
}
private async run<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
if (this.state !== State.Idle) {
throw new Error('Repository not initialized');
}
const run = async () => {
this._operations = this._operations.start(operation);
this._onRunOperation.fire(operation);
try {
await this.assertIdleState();
const result = await this.retryRun(runOperation);
if (!isReadOnly(operation)) {
......@@ -610,12 +618,6 @@ export class Repository implements Disposable {
return result;
} catch (err) {
if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) {
this.repositoryDisposable.dispose();
const disposables: Disposable[] = [];
this.onWorkspaceChange(this.onFSChange, this, disposables);
this.repositoryDisposable = combinedDisposable(disposables);
this.state = State.NotAGitRepository;
}
......@@ -649,37 +651,6 @@ export class Repository implements Disposable {
}
}
/* 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;
}
this.repositoryDisposable.dispose();
const disposables: Disposable[] = [];
const repositoryRoot = await this._git.getRepositoryRoot(this.workspaceRoot.fsPath);
this.repository = this._git.open(repositoryRoot);
const onGitChange = filterEvent(this.onWorkspaceChange, uri => /\/\.git\//.test(uri.path));
const onRelevantGitChange = filterEvent(onGitChange, uri => !/\/\.git\/index\.lock$/.test(uri.path));
onRelevantGitChange(this.onFSChange, this, disposables);
onRelevantGitChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, disposables);
const onNonGitChange = filterEvent(this.onWorkspaceChange, uri => !/\/\.git\//.test(uri.path));
onNonGitChange(this.onFSChange, this, disposables);
this.repositoryDisposable = combinedDisposable(disposables);
this.isRepositoryHuge = false;
this.didWarnAboutLimit = false;
this.state = State.Idle;
}
@throttle
private async updateModelState(): Promise<void> {
const { status, didHitLimit } = await this.repository.getStatus();
......@@ -732,30 +703,30 @@ export class Repository implements Disposable {
const renameUri = raw.rename ? Uri.file(path.join(this.repository.root, raw.rename)) : undefined;
switch (raw.x + raw.y) {
case '??': return workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED));
case '!!': return workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.IGNORED));
case 'DD': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_DELETED));
case 'AU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.ADDED_BY_US));
case 'UD': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM));
case 'UA': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM));
case 'DU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.DELETED_BY_US));
case 'AA': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_ADDED));
case 'UU': return merge.push(new Resource(this.workspaceRoot, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED));
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));
}
let isModifiedInIndex = false;
switch (raw.x) {
case 'M': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED)); isModifiedInIndex = true; break;
case 'A': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_ADDED)); break;
case 'D': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_DELETED)); break;
case 'R': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, renameUri)); break;
case 'C': index.push(new Resource(this.workspaceRoot, ResourceGroupType.Index, uri, Status.INDEX_COPIED, renameUri)); break;
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;
}
switch (raw.y) {
case 'M': workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, renameUri)); break;
case 'D': workingTree.push(new Resource(this.workspaceRoot, ResourceGroupType.WorkingTree, uri, Status.DELETED, renameUri)); break;
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;
}
});
......@@ -825,7 +796,6 @@ export class Repository implements Disposable {
}
dispose(): void {
this.repositoryDisposable.dispose();
this.disposables = dispose(this.disposables);
}
}
......@@ -11,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { chain } from 'vs/base/common/event';
import { memoize } from 'vs/base/common/decorators';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, empty as EmptyDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { ComposedViewsViewlet, CollapsibleView, ICollapsibleViewOptions, IViewletViewOptions, IView, IViewOptions } from 'vs/workbench/parts/views/browser/views';
import { append, $, toggleClass } from 'vs/base/browser/dom';
......@@ -399,10 +399,10 @@ class InstallAdditionalSCMProvidersAction extends Action {
export class SCMViewlet extends ComposedViewsViewlet {
private activeProvider: ISCMProvider | undefined;
private cachedDimension: Dimension;
private inputBoxContainer: HTMLElement;
private inputBox: InputBox;
// private activeProvider: ISCMProvider | undefined;
// private cachedDimension: Dimension;
// private inputBoxContainer: HTMLElement;
// private inputBox: InputBox;
private providerChangeDisposable: IDisposable = EmptyDisposable;
private disposables: IDisposable[] = [];
......@@ -428,30 +428,37 @@ export class SCMViewlet extends ComposedViewsViewlet {
telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService);
}
private setActiveProvider(activeProvider: ISCMProvider | undefined): void {
private onDidProvidersChange(): void {
this.providerChangeDisposable.dispose();
this.activeProvider = activeProvider;
// this.activeProvider = activeProvider;
if (activeProvider) {
const disposables = [];
const providers = this.scmService.providers;
const ids = providers.map(provider => provider.id);
const views = providers.map(provider => new SourceControlViewDescriptor(provider));
// if (activeProvider.onDidChangeCommitTemplate) {
// disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this));
// }
ViewsRegistry.registerViews(views);
this.providerChangeDisposable = toDisposable(() => ViewsRegistry.deregisterViews(ids, ViewLocation.SCM));
const id = activeProvider.id;
ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]);
// if (activeProvider) {
// const disposables = [];
disposables.push({
dispose: () => {
ViewsRegistry.deregisterViews([id], ViewLocation.SCM);
}
});
// // if (activeProvider.onDidChangeCommitTemplate) {
// // disposables.push(activeProvider.onDidChangeCommitTemplate(this.updateInputBox, this));
// // }
this.providerChangeDisposable = combinedDisposable(disposables);
} else {
this.providerChangeDisposable = EmptyDisposable;
}
// const id = activeProvider.id;
// ViewsRegistry.registerViews([new SourceControlViewDescriptor(activeProvider)]);
// disposables.push({
// dispose: () => {
// ViewsRegistry.deregisterViews([id], ViewLocation.SCM);
// }
// });
// this.providerChangeDisposable = combinedDisposable(disposables);
// } else {
// this.providerChangeDisposable = EmptyDisposable;
// }
// this.updateInputBox();
// this.updateTitleArea();
......@@ -483,8 +490,8 @@ export class SCMViewlet extends ComposedViewsViewlet {
// .on(this.onDidAcceptInput, this, this.disposables);
this.setActiveProvider(this.scmService.activeProvider);
this.scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables);
this.onDidProvidersChange();
this.scmService.onDidChangeProviders(this.onDidProvidersChange, this, this.disposables);
// this.themeService.onThemeChange(this.update, this, this.disposables);
// return TPromise.as(null);
......@@ -498,33 +505,33 @@ export class SCMViewlet extends ComposedViewsViewlet {
return this.instantiationService.createInstance(viewDescriptor.ctor, options);
}
private onDidAcceptInput(): void {
if (!this.activeProvider) {
return;
}
// private onDidAcceptInput(): void {
// if (!this.activeProvider) {
// return;
// }
if (!this.activeProvider.acceptInputCommand) {
return;
}
// if (!this.activeProvider.acceptInputCommand) {
// return;
// }
const id = this.activeProvider.acceptInputCommand.id;
const args = this.activeProvider.acceptInputCommand.arguments;
// const id = this.activeProvider.acceptInputCommand.id;
// const args = this.activeProvider.acceptInputCommand.arguments;
this.commandService.executeCommand(id, ...args)
.done(undefined, onUnexpectedError);
}
// this.commandService.executeCommand(id, ...args)
// .done(undefined, onUnexpectedError);
// }
private updateInputBox(): void {
if (!this.activeProvider) {
return;
}
// private updateInputBox(): void {
// if (!this.activeProvider) {
// return;
// }
if (typeof this.activeProvider.commitTemplate === 'undefined') {
return;
}
// if (typeof this.activeProvider.commitTemplate === 'undefined') {
// return;
// }
this.inputBox.value = this.activeProvider.commitTemplate;
}
// this.inputBox.value = this.activeProvider.commitTemplate;
// }
// layout(dimension: Dimension = this.cachedDimension): void {
// if (!dimension) {
......
......@@ -63,6 +63,9 @@ export interface ISCMService {
readonly _serviceBrand: any;
readonly onDidChangeProvider: Event<ISCMProvider>;
// TODO@joao fix name
readonly onDidChangeProviders: Event<void>;
readonly providers: ISCMProvider[];
readonly input: ISCMInput;
activeProvider: ISCMProvider | undefined;
......
......@@ -51,6 +51,9 @@ export class SCMService implements ISCMService {
private _providers: ISCMProvider[] = [];
get providers(): ISCMProvider[] { return [...this._providers]; }
private _onDidChangeProviders = new Emitter<void>();
get onDidChangeProviders(): Event<void> { return this._onDidChangeProviders.event; }
private _onDidChangeProvider = new Emitter<ISCMProvider>();
get onDidChangeProvider(): Event<ISCMProvider> { return this._onDidChangeProvider.event; }
......@@ -91,6 +94,8 @@ export class SCMService implements ISCMService {
this.setActiveSCMProvider(provider);
}
this._onDidChangeProviders.fire();
return toDisposable(() => {
const index = this._providers.indexOf(provider);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册