diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index aa0cd238b38d7e93572753f46820845d4000aefb..79ecc2c2ae4a0263fb5b4511837e5b4c067c9056 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -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('enabled') === true; - const workspaceRootPath = workspace.rootPath; - const pathHint = workspace.getConfiguration('git').get('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); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 33d71d2127c211a98f92e5fa2004f4237da27017..3aa6f35e5f82da3af52f44c85e97644976a83fbb 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -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 = new Map(); + private repositories: Map = new Map(); - 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 { + // 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 diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index b6fee70464271105a59b2d7612dffe547ab6d576..5a76865e733612995e9a81211e4e4dda94485c7e 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -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(); readonly onDidChangeRepository: Event = 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; + 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 { - if (this.state !== State.NotAGitRepository) { - return; - } + // @throttle + // async init(): Promise { + // 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 { @@ -594,13 +600,15 @@ export class Repository implements Disposable { } private async run(operation: Operation, runOperation: () => Promise = () => Promise.resolve(null)): Promise { + 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 { - 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 { 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); } } diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 650a3ea33089b5de2161342bd79f60956cbb7a99..de59b293e6c4eded79e8d8f5fa5a719829310498 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -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) { diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 2af470e880132fabc377cbe256c81e9b9e55ab71..f0169943edf77586ed55e3605f3f169186d1d58f 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -63,6 +63,9 @@ export interface ISCMService { readonly _serviceBrand: any; readonly onDidChangeProvider: Event; + + // TODO@joao fix name + readonly onDidChangeProviders: Event; readonly providers: ISCMProvider[]; readonly input: ISCMInput; activeProvider: ISCMProvider | undefined; diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index 06cdc638fe1864d97e05959b3d3c1323baa5a73b..9bfae91061e4d7fac1a4322ec2cfd01ecf43431d 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -51,6 +51,9 @@ export class SCMService implements ISCMService { private _providers: ISCMProvider[] = []; get providers(): ISCMProvider[] { return [...this._providers]; } + private _onDidChangeProviders = new Emitter(); + get onDidChangeProviders(): Event { return this._onDidChangeProviders.event; } + private _onDidChangeProvider = new Emitter(); get onDidChangeProvider(): Event { 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);