From 93011ff01cbe7a5e51baec6cfd3b73c477db33bc Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Aug 2017 17:35:24 +0200 Subject: [PATCH] git: improved multirepo model --- extensions/git/src/main.ts | 11 +- extensions/git/src/model.ts | 142 +++++++++++++----- .../parts/scm/electron-browser/scmViewlet.ts | 8 +- .../services/scm/common/scmService.ts | 2 + 4 files changed, 111 insertions(+), 52 deletions(-) diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index cdbbe503405..d9c2fb70291 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -9,7 +9,6 @@ import * as nls from 'vscode-nls'; const localize = nls.config(process.env.VSCODE_NLS_CONFIG)(); import { ExtensionContext, workspace, window, Disposable, commands, Uri } from 'vscode'; import { findGit, Git, IGit } from './git'; -import { Repository } from './repository'; import { Model } from './model'; import { CommandCenter } from './commands'; import { GitContentProvider } from './contentProvider'; @@ -33,7 +32,7 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi const askpass = new Askpass(); const env = await askpass.getEnv(); const git = new Git({ gitPath: info.path, version: info.version, env }); - const model = new Model(); + const model = new Model(git); disposables.push(model); if (!enabled) { @@ -42,13 +41,6 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi return; } - 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)); const onOutput = str => outputChannel.append(str); @@ -63,7 +55,6 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi commandCenter, contentProvider, // autoFetcher, - // repository ); await checkGitVersion(info); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 61740cdc3fd..1017c3318ae 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -5,19 +5,20 @@ 'use strict'; -import { Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup } from 'vscode'; -import { Repository, State } from './repository'; +import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor } from 'vscode'; +import { Repository } from './repository'; import { memoize } from './decorators'; -import { toDisposable, filterEvent, once } from './util'; +import { dispose } from './util'; +import { Git, GitErrorCodes } from './git'; import * as path from 'path'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); class RepositoryPick implements QuickPickItem { - @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) { } + @memoize get label(): string { return path.basename(this.repository.root); } + @memoize get description(): string { return path.dirname(this.repository.root); } + constructor(public readonly repository: Repository) { } } export interface ModelChangeEvent { @@ -25,47 +26,101 @@ export interface ModelChangeEvent { uri: Uri; } +interface OpenRepository extends Disposable { + repository: Repository; +} + export class Model { private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; - private repositories: Map = new Map(); + private openRepositories: OpenRepository[] = []; + get repositories(): Repository[] { return this.openRepositories.map(r => r.repository); } - register(repository: Repository): Disposable { - const root = repository.root; + private disposables: Disposable[] = []; - if (this.repositories.has(root)) { - // TODO@Joao: what should happen? - throw new Error('Cant register repository with the same URI'); + constructor(private git: Git) { + workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables); + this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }); + + window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables); + this.onDidChangeVisibleTextEditors(window.visibleTextEditors); + } + + private async onDidChangeWorkspaceFolders({ added, removed }: WorkspaceFoldersChangeEvent): Promise { + const possibleRepositoryFolders = added + .filter(folder => !this.getOpenRepository(folder.uri)); + + const activeRepositoriesList = window.visibleTextEditors + .map(editor => this.getRepository(editor.document.uri)) + .filter(repository => !!repository) as Repository[]; + + const activeRepositories = new Set(activeRepositoriesList); + const openRepositoriesToDispose = removed + .map(folder => this.getOpenRepository(folder.uri)) + .filter(r => !!r && !activeRepositories.has(r.repository)) as OpenRepository[]; + + console.log('lets dispose', openRepositoriesToDispose); + + possibleRepositoryFolders.forEach(p => this.findRepository(p.uri.fsPath)); + openRepositoriesToDispose.forEach(r => r.dispose()); + } + + private onDidChangeVisibleTextEditors(editors: TextEditor[]): void { + editors.forEach(editor => { + const uri = editor.document.uri; + + if (uri.scheme !== 'file') { + return; + } + + const repository = this.getRepository(uri); + + if (repository) { + return; + } + + this.findRepository(path.dirname(uri.fsPath)); + }); + } + + private async findRepository(dirPath: string): Promise { + try { + const repositoryRoot = await this.git.getRepositoryRoot(dirPath); + const repository = new Repository(this.git.open(repositoryRoot)); + + this.open(repository); + } catch (err) { + if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { + return; + } + + console.error('Failed to find repository:', err); } + } - this.repositories.set(root, repository); - - const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.Disposed); - const disappearListener = onDidDisappearRepository(() => disposable.dispose()); + private open(repository: Repository): void { + // const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === State.Disposed); + // const disappearListener = onDidDisappearRepository(() => disposable.dispose()); const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); - - const disposable = toDisposable(once(() => { - disappearListener.dispose(); + const dispose = () => { + // disappearListener.dispose(); changeListener.dispose(); - this.repositories.delete(root); - })); + repository.dispose(); + this.openRepositories = this.openRepositories.filter(e => e !== openRepository); + }; - return disposable; + const openRepository = { repository, dispose }; + this.openRepositories.push(openRepository); } async pickRepository(): Promise { - if (this.repositories.size === 0) { + if (this.openRepositories.length === 0) { throw new Error(localize('no repositories', "There are no available repositories")); } - // TODO@joao enable this code - // if (this.repositories.size === 1) { - // return this.repositories.values().next().value; - // } - - const picks = Array.from(this.repositories.entries(), ([uri, model]) => new RepositoryPick(uri, model)); + const picks = this.openRepositories.map(e => new RepositoryPick(e.repository)); const placeHolder = localize('pick repo', "Choose a repository"); const pick = await window.showQuickPick(picks, { placeHolder }); @@ -76,6 +131,14 @@ export class Model { getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; getRepository(resource: Uri): Repository | undefined; getRepository(hint: any): Repository | undefined { + const liveRepository = this.getOpenRepository(hint); + return liveRepository && liveRepository.repository; + } + + private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined; + private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined; + private getOpenRepository(resource: Uri): OpenRepository | undefined; + private getOpenRepository(hint: any): OpenRepository | undefined { if (!hint) { return undefined; } @@ -83,24 +146,26 @@ export class Model { if (hint instanceof Uri) { const resourcePath = hint.fsPath; - for (let [root, repository] of this.repositories) { - const relativePath = path.relative(root, resourcePath); + for (const liveRepository of this.openRepositories) { + const relativePath = path.relative(liveRepository.repository.root, resourcePath); if (!/^\./.test(relativePath)) { - return repository; + return liveRepository; } } return undefined; } - for (let [, repository] of this.repositories) { + for (const liveRepository of this.openRepositories) { + const repository = liveRepository.repository; + if (hint === repository.sourceControl) { - return repository; + return liveRepository; } if (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup) { - return repository; + return liveRepository; } } @@ -108,10 +173,7 @@ export class Model { } dispose(): void { - for (let [, repository] of this.repositories) { - repository.dispose(); - } - - this.repositories.clear(); + [...this.openRepositories].forEach(r => r.dispose()); + this.disposables = dispose(this.disposables); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 832900597a5..4fd51991c96 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -228,7 +228,8 @@ function resourceSorter(a: ISCMResource, b: ISCMResource): number { class SourceControlViewDescriptor implements IViewDescriptor { get provider(): ISCMProvider { return this._provider; } - get id(): string { return this._provider.id; } + // TODO@joao change this so we dont need IDS + get id(): string { return this._provider.label; } get name(): string { return this._provider.label; } get ctor(): any { return null; } get location(): ViewLocation { return ViewLocation.SCM; } @@ -438,9 +439,12 @@ export class SCMViewlet extends ComposedViewsViewlet { return; } - const ids = providers.map(provider => provider.id); + // TODO@joao change this so we dont need ids + const ids = providers.map(provider => provider.label); const views = providers.map(provider => new SourceControlViewDescriptor(provider)); + // console.log(provider.label); + ViewsRegistry.registerViews(views); this.providerChangeDisposable = toDisposable(() => ViewsRegistry.deregisterViews(ids, ViewLocation.SCM)); diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index 9bfae91061e..7e02c3a6626 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -108,6 +108,8 @@ export class SCMService implements ISCMService { if (this.activeProvider === provider) { this.activeProvider = this._providers[0]; } + + this._onDidChangeProviders.fire(); }); }