From 1353643fb341a76718362c066e615072ad37661d Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Sat, 21 Oct 2017 02:21:04 -0400 Subject: [PATCH] Adds more multi-root support --- package.json | 92 +++++++++++++++---------------- src/constants.ts | 5 +- src/git/gitContextTracker.ts | 39 ++++++++++---- src/git/models/models.ts | 1 + src/git/models/repository.ts | 106 ++++++++++++++++++++++++++++++++++++ src/gitService.ts | 125 +++++++++++++++---------------------------- src/views/repositoryNode.ts | 2 +- src/views/statusNode.ts | 21 ++++---- 8 files changed, 239 insertions(+), 152 deletions(-) create mode 100644 src/git/models/repository.ts diff --git a/package.json b/package.json index 883826c..2a2f7a9 100644 --- a/package.json +++ b/package.json @@ -1243,31 +1243,31 @@ }, { "command": "gitlens.diffWithBranch", - "when": "gitlens:isTracked" + "when": "gitlens:activeIsTracked" }, { "command": "gitlens.diffWithNext", - "when": "gitlens:isTracked" + "when": "gitlens:activeIsTracked" }, { "command": "gitlens.diffWithPrevious", - "when": "gitlens:isTracked" + "when": "gitlens:activeIsTracked" }, { "command": "gitlens.diffLineWithPrevious", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.diffWithRevision", - "when": "gitlens:isTracked" + "when": "gitlens:activeIsTracked" }, { "command": "gitlens.diffWithWorking", - "when": "gitlens:isTracked" + "when": "gitlens:activeIsTracked" }, { "command": "gitlens.diffLineWithWorking", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.externalDiffAll", @@ -1275,15 +1275,15 @@ }, { "command": "gitlens.showFileBlame", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.showLineBlame", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.toggleFileBlame", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.clearFileAnnotations", @@ -1295,15 +1295,15 @@ }, { "command": "gitlens.toggleFileRecentChanges", - "when": "gitlens:isTracked" + "when": "gitlens:activeIsTracked" }, { "command": "gitlens.toggleLineBlame", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.toggleCodeLens", - "when": "gitlens:isTracked && gitlens:canToggleCodeLens" + "when": "gitlens:activeIsTracked && gitlens:canToggleCodeLens" }, { "command": "gitlens.showLastQuickPick", @@ -1311,15 +1311,15 @@ }, { "command": "gitlens.showQuickCommitDetails", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.showQuickCommitFileDetails", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.showQuickFileHistory", - "when": "gitlens:isTracked" + "when": "gitlens:activeIsTracked" }, { "command": "gitlens.showQuickBranchHistory", @@ -1339,11 +1339,11 @@ }, { "command": "gitlens.copyShaToClipboard", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.copyMessageToClipboard", - "when": "gitlens:isBlameable" + "when": "gitlens:activeIsBlameable" }, { "command": "gitlens.closeUnchangedFiles", @@ -1355,23 +1355,23 @@ }, { "command": "gitlens.openBranchesInRemote", - "when": "gitlens:hasRemotes" + "when": "gitlens:activeHasRemotes" }, { "command": "gitlens.openBranchInRemote", - "when": "gitlens:hasRemotes" + "when": "gitlens:activeHasRemotes" }, { "command": "gitlens.openCommitInRemote", - "when": "gitlens:isBlameable && gitlens:hasRemotes" + "when": "gitlens:activeIsBlameable && gitlens:activeHasRemotes" }, { "command": "gitlens.openFileInRemote", - "when": "gitlens:isTracked && gitlens:hasRemotes" + "when": "gitlens:activeIsTracked && gitlens:activeHasRemotes" }, { "command": "gitlens.openRepoInRemote", - "when": "gitlens:hasRemotes" + "when": "gitlens:activeHasRemotes" }, { "command": "gitlens.stashApply", @@ -1461,52 +1461,52 @@ "editor/context": [ { "command": "gitlens.openFileInRemote", - "when": "editorTextFocus && gitlens:isTracked && gitlens:hasRemotes && config.gitlens.advanced.menus.editorContext.remote", + "when": "editorTextFocus && gitlens:activeHasRemotes && config.gitlens.advanced.menus.editorContext.remote", "group": "navigation@100" }, { "command": "gitlens.diffLineWithPrevious", - "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.lineDiff", + "when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.lineDiff", "group": "1_gitlens@1" }, { "command": "gitlens.diffLineWithWorking", - "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.lineDiff", + "when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.lineDiff", "group": "1_gitlens@2" }, { "command": "gitlens.showQuickCommitFileDetails", - "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.details", + "when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.details", "group": "1_gitlens@3" }, { "command": "gitlens.diffWithPrevious", - "when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorContext.fileDiff", + "when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.fileDiff", "group": "1_gitlens_1@1" }, { "command": "gitlens.diffWithWorking", - "when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorContext.fileDiff", + "when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.fileDiff", "group": "1_gitlens_1@2" }, { "command": "gitlens.showQuickFileHistory", - "when": "gitlens:isTracked && config.gitlens.advanced.menus.editorContext.history", + "when": "gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.history", "group": "3_gitlens@1" }, { "command": "gitlens.toggleFileBlame", - "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.blame", + "when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.blame", "group": "3_gitlens@2" }, { "command": "gitlens.copyShaToClipboard", - "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.copy", + "when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.copy", "group": "9_gitlens@1" }, { "command": "gitlens.copyMessageToClipboard", - "when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.copy", + "when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.copy", "group": "9_gitlens@2" } ], @@ -1514,7 +1514,7 @@ { "command": "gitlens.toggleFileBlame", "alt": "gitlens.toggleFileRecentChanges", - "when": "gitlens:isBlameable && !gitlens:annotationStatus && config.gitlens.advanced.menus.editorTitle.blame", + "when": "gitlens:activeIsBlameable && !gitlens:annotationStatus && config.gitlens.advanced.menus.editorTitle.blame", "group": "navigation@100" }, { @@ -1529,27 +1529,27 @@ }, { "command": "gitlens.openFileInRemote", - "when": "gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.editorTitle.remote", + "when": "gitlens:enabled && gitlens:activeHasRemotes && config.gitlens.advanced.menus.editorTitle.remote", "group": "1_gitlens" }, { "command": "gitlens.openRepoInRemote", - "when": "gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.editorTitle.remote", + "when": "gitlens:enabled && gitlens:activeHasRemotes && config.gitlens.advanced.menus.editorTitle.remote", "group": "1_gitlens" }, { "command": "gitlens.diffWithPrevious", - "when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.fileDiff", + "when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.fileDiff", "group": "2_gitlens" }, { "command": "gitlens.diffWithWorking", - "when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.fileDiff", + "when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.fileDiff", "group": "2_gitlens" }, { "command": "gitlens.showQuickFileHistory", - "when": "editorFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.history", + "when": "editorFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.history", "group": "2_gitlens_1" }, { @@ -1990,12 +1990,12 @@ { "command": "gitlens.toggleFileBlame", "key": "alt+b", - "when": "editorTextFocus && gitlens:isBlameable" + "when": "editorTextFocus && gitlens:activeIsBlameable" }, { "command": "gitlens.toggleCodeLens", "key": "shift+alt+b", - "when": "editorTextFocus && gitlens:isTracked && gitlens:canToggleCodeLens" + "when": "editorTextFocus && gitlens:activeIsTracked && gitlens:canToggleCodeLens" }, { "command": "gitlens.showLastQuickPick", @@ -2030,27 +2030,27 @@ { "command": "gitlens.diffWithNext", "key": "alt+.", - "when": "editorTextFocus && gitlens:isTracked" + "when": "editorTextFocus && gitlens:activeIsTracked" }, { "command": "gitlens.diffLineWithPrevious", "key": "shift+alt+,", - "when": "editorTextFocus && gitlens:isTracked" + "when": "editorTextFocus && gitlens:activeIsTracked" }, { "command": "gitlens.diffWithPrevious", "key": "alt+,", - "when": "editorTextFocus && gitlens:isTracked" + "when": "editorTextFocus && gitlens:activeIsTracked" }, { "command": "gitlens.diffLineWithWorking", "key": "alt+w", - "when": "editorTextFocus && gitlens:isTracked" + "when": "editorTextFocus && gitlens:activeIsTracked" }, { "command": "gitlens.diffWithWorking", "key": "shift+alt+w", - "when": "editorTextFocus && gitlens:isTracked" + "when": "editorTextFocus && gitlens:activeIsTracked" } ], "views": { diff --git a/src/constants.ts b/src/constants.ts index 3ddfe8b..4f55749 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -33,8 +33,9 @@ export enum CommandContext { GitExplorerView = 'gitlens:gitExplorer:view', HasRemotes = 'gitlens:hasRemotes', HasRepository = 'gitlens:hasRepository', - IsBlameable = 'gitlens:isBlameable', - IsTracked = 'gitlens:isTracked', + ActiveHasRemotes = 'gitlens:activeHasRemotes', + ActiveIsBlameable = 'gitlens:activeIsBlameable', + ActiveFileIsTracked = 'gitlens:activeIsTracked', Key = 'gitlens:key' } diff --git a/src/git/gitContextTracker.ts b/src/git/gitContextTracker.ts index c85baf1..7a2d483 100644 --- a/src/git/gitContextTracker.ts +++ b/src/git/gitContextTracker.ts @@ -53,7 +53,8 @@ export class GitContextTracker extends Disposable { } async _onRepoChanged(reasons: RepoChangedReasons[]) { - if (!reasons.includes(RepoChangedReasons.Remotes)) return; + // TODO: Support multi-root + if (!reasons.includes(RepoChangedReasons.Remotes) && !reasons.includes(RepoChangedReasons.Repositories)) return; const gitUri = this._editor === undefined ? undefined : await GitUri.fromUri(this._editor.document.uri, this.git); this._updateContextHasRemotes(gitUri); @@ -129,18 +130,36 @@ export class GitContextTracker extends Disposable { private async _updateContextHasRemotes(uri: GitUri | undefined) { try { - let repoPath = this.git.repoPath; + const repositories = await this.git.getRepositories(); + + let hasRemotes = false; if (uri !== undefined && this.git.isTrackable(uri)) { - repoPath = uri.repoPath || this.git.repoPath; + const remotes = await this.git.getRemotes(uri.repoPath); + + await setCommandContext(CommandContext.ActiveHasRemotes, remotes.length !== 0); + } + else { + if (repositories.length === 1) { + const remotes = await this.git.getRemotes(repositories[0].path); + hasRemotes = remotes.length !== 0; + + await setCommandContext(CommandContext.ActiveHasRemotes, hasRemotes); + } + else { + await setCommandContext(CommandContext.ActiveHasRemotes, false); + } } - let hasRemotes = false; - if (repoPath) { - const remotes = await this.git.getRemotes(repoPath); - hasRemotes = remotes.length !== 0; + if (!hasRemotes) { + for (const repo of repositories) { + const remotes = await this.git.getRemotes(repo.path); + hasRemotes = remotes.length !== 0; + + if (hasRemotes) break; + } } - setCommandContext(CommandContext.HasRemotes, hasRemotes); + await setCommandContext(CommandContext.HasRemotes, hasRemotes); } catch (ex) { Logger.error(ex, 'GitEditorTracker._updateContextHasRemotes'); @@ -150,7 +169,7 @@ export class GitContextTracker extends Disposable { private async _updateEditorContext(uri: GitUri | undefined, editor: TextEditor | undefined) { try { const tracked = uri === undefined ? false : await this.git.isTracked(uri); - setCommandContext(CommandContext.IsTracked, tracked); + setCommandContext(CommandContext.ActiveFileIsTracked, tracked); let blameable = tracked && (editor !== undefined && editor.document !== undefined && !editor.document.isDirty); if (blameable) { @@ -168,7 +187,7 @@ export class GitContextTracker extends Disposable { if (!force && this._isBlameable === blameable) return; try { - setCommandContext(CommandContext.IsBlameable, blameable); + setCommandContext(CommandContext.ActiveIsBlameable, blameable); this._onDidChangeBlameability.fire({ blameable: blameable, editor: this._editor diff --git a/src/git/models/models.ts b/src/git/models/models.ts index a1535ac..1ebb99c 100644 --- a/src/git/models/models.ts +++ b/src/git/models/models.ts @@ -7,6 +7,7 @@ export * from './diff'; export * from './log'; export * from './logCommit'; export * from './remote'; +export * from './repository'; export * from './stash'; export * from './stashCommit'; export * from './status'; \ No newline at end of file diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts new file mode 100644 index 0000000..90b54cf --- /dev/null +++ b/src/git/models/repository.ts @@ -0,0 +1,106 @@ +'use strict'; +import { Functions } from '../../system'; +import { Disposable, Event, EventEmitter, RelativePattern, Uri, workspace, WorkspaceFolder } from 'vscode'; + +export enum RepositoryStorage { + StatusNode = 'statusNode' +} + +export class Repository extends Disposable { + + private _onDidChangeFileSystem = new EventEmitter(); + get onDidChangeFileSystem(): Event { + return this._onDidChangeFileSystem.event; + } + + readonly index: number; + readonly name: string; + readonly storage: Map = new Map(); + + private readonly _disposable: Disposable; + private _fsWatcherDisposable: Disposable | undefined; + private _pendingChanges: { repo: boolean, fs: boolean } = { repo: false, fs: false }; + private _suspended: boolean; + + constructor( + private readonly folder: WorkspaceFolder, + public readonly path: string, + readonly onRepoChanged: (uri: Uri) => void, + suspended: boolean + ) { + super(() => this.dispose()); + + this.index = folder.index; + this.name = folder.name; + this._suspended = suspended; + + const watcher = workspace.createFileSystemWatcher(new RelativePattern(folder, '**/.git/{index,HEAD,refs/stash,refs/heads/**,refs/remotes/**}')); + const subscriptions = [ + watcher, + watcher.onDidChange(onRepoChanged), + watcher.onDidCreate(onRepoChanged), + watcher.onDidDelete(onRepoChanged) + ]; + + this._disposable = Disposable.from(...subscriptions); + } + + dispose() { + this.stopWatchingFileSystem(); + + // Clean up any disposables in storage + for (const item of this.storage.values()) { + if (item != null && typeof item.dispose === 'function') { + item.dispose(); + } + } + + this._disposable && this._disposable.dispose(); + } + + resume() { + if (!this._suspended) return; + + this._suspended = false; + + // If we've come back into focus and we are dirty, fire the change events + if (this._pendingChanges.fs) { + this._pendingChanges.fs = false; + this._onDidChangeFileSystem.fire(); + } + } + + startWatchingFileSystem() { + if (this._fsWatcherDisposable !== undefined) return; + + const debouncedFn = Functions.debounce((uri: Uri) => this._onDidChangeFileSystem.fire(uri), 2500); + const fn = (uri: Uri) => { + // Ignore .git changes + if (/\.git/.test(uri.fsPath)) return; + + if (this._suspended) { + this._pendingChanges.fs = true; + return; + } + + debouncedFn(uri); + }; + + const watcher = workspace.createFileSystemWatcher(new RelativePattern(this.folder, `**`)); + this._fsWatcherDisposable = Disposable.from( + watcher, + watcher.onDidChange(fn), + watcher.onDidCreate(fn), + watcher.onDidDelete(fn) + ); + } + + stopWatchingFileSystem() { + this._fsWatcherDisposable && this._fsWatcherDisposable.dispose(); + this._fsWatcherDisposable = undefined; + } + + suspend() { + this._suspended = true; + } +} diff --git a/src/gitService.ts b/src/gitService.ts index ac6eafb..62b0735 100644 --- a/src/gitService.ts +++ b/src/gitService.ts @@ -1,10 +1,10 @@ 'use strict'; import { Functions, Iterables, Objects } from './system'; -import { Disposable, Event, EventEmitter, FileSystemWatcher, Range, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, window, WindowState, workspace, WorkspaceFoldersChangeEvent } from 'vscode'; +import { Disposable, Event, EventEmitter, Range, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, window, WindowState, workspace, WorkspaceFoldersChangeEvent } from 'vscode'; import { IConfig } from './configuration'; import { CommandContext, DocumentSchemes, ExtensionKey, setCommandContext } from './constants'; import { RemoteProviderFactory } from './git/remotes/factory'; -import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitCommitType, GitDiff, GitDiffChunkLine, GitDiffParser, GitDiffShortStat, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git'; +import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitCommitType, GitDiff, GitDiffChunkLine, GitDiffParser, GitDiffShortStat, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, Repository, setDefaultEncoding } from './git/git'; import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri'; import { Logger } from './logger'; import * as fs from 'fs'; @@ -51,12 +51,6 @@ interface CachedBlame extends CachedItem { } interface CachedDiff extends CachedItem { } interface CachedLog extends CachedItem { } -export interface Repository { - readonly name: string; - readonly index: number; - readonly path: string; -} - enum RemoveCacheReason { DocumentClosed, DocumentSaved @@ -94,11 +88,7 @@ export class GitService extends Disposable { return this._onDidChangeGitCache.event; } - private _onDidChangeFileSystem = new EventEmitter(); - get onDidChangeFileSystem(): Event { - return this._onDidChangeFileSystem.event; - } - + // TODO: Support multi-root { repo, reasons }[]? private _onDidChangeRepo = new EventEmitter(); get onDidChangeRepo(): Event { return this._onDidChangeRepo.event; @@ -106,14 +96,13 @@ export class GitService extends Disposable { private _cacheDisposable: Disposable | undefined; private _disposable: Disposable | undefined; - private _focused: boolean = true; private _gitCache: Map; + private _pendingChanges: { repo: boolean } = { repo: false }; private _remotesCache: Map; private _repositories: Map; private _repositoriesPromise: Promise | undefined; - private _repoWatcher: FileSystemWatcher | undefined; + private _suspended: boolean = false; private _trackedCache: Map; - private _unfocusedChanges: { repo: boolean, fs: boolean } = { repo: false, fs: false }; private _versionedUriCache: Map; constructor() { @@ -138,10 +127,7 @@ export class GitService extends Disposable { } dispose() { - this.stopWatchingFileSystem(); - - this._repoWatcher && this._repoWatcher.dispose(); - this._repoWatcher = undefined; + this._repositories.forEach(r => r && r.dispose()); this._disposable && this._disposable.dispose(); @@ -161,15 +147,6 @@ export class GitService extends Disposable { return repo === undefined ? undefined : repo.path; } - public async getRepositories(): Promise { - if (this._repositoriesPromise !== undefined) { - await this._repositoriesPromise; - this._repositoriesPromise = undefined; - } - - return [...Iterables.filter(this._repositories.values(), r => r !== undefined) as Iterable]; - } - public get UseCaching() { return this.config.advanced.caching.enabled; } @@ -184,15 +161,10 @@ export class GitService extends Disposable { if (cfg.advanced.caching.enabled) { this._cacheDisposable && this._cacheDisposable.dispose(); - this._repoWatcher = this._repoWatcher || workspace.createFileSystemWatcher('**/.git/{index,HEAD,refs/stash,refs/heads/**,refs/remotes/**}'); - const subscriptions: Disposable[] = [ workspace.onDidCloseTextDocument(d => this.removeCachedEntry(d, RemoveCacheReason.DocumentClosed)), workspace.onDidChangeTextDocument(this.onTextDocumentChanged, this), - workspace.onDidSaveTextDocument(d => this.removeCachedEntry(d, RemoveCacheReason.DocumentSaved)), - this._repoWatcher.onDidChange(this.onRepoChanged, this), - this._repoWatcher.onDidCreate(this.onRepoChanged, this), - this._repoWatcher.onDidDelete(this.onRepoChanged, this) + workspace.onDidSaveTextDocument(d => this.removeCachedEntry(d, RemoveCacheReason.DocumentSaved)) ]; this._cacheDisposable = Disposable.from(...subscriptions); } @@ -200,9 +172,6 @@ export class GitService extends Disposable { this._cacheDisposable && this._cacheDisposable.dispose(); this._cacheDisposable = undefined; - this._repoWatcher && this._repoWatcher.dispose(); - this._repoWatcher = undefined; - this._gitCache.clear(); } } @@ -223,19 +192,22 @@ export class GitService extends Disposable { } private onWindowStateChanged(e: WindowState) { - const focusChanged = e.focused !== this._focused; - this._focused = e.focused; + if (e.focused) { + this._repositories.forEach(r => r && r.resume()); + } + else { + this._repositories.forEach(r => r && r.suspend()); + } - if (!focusChanged || !e.focused) return; + const suspended = !e.focused; + const changed = suspended !== this._suspended; + this._suspended = suspended; - // If we've come back into focus and we are dirty, fire the change events - if (this._unfocusedChanges.fs) { - this._unfocusedChanges.fs = false; - this._onDidChangeFileSystem.fire(); - } + if (suspended || !changed) return; - if (this._unfocusedChanges.repo) { - this._unfocusedChanges.repo = false; + // If we've come back into focus and we are dirty, fire the change events + if (this._pendingChanges.repo) { + this._pendingChanges.repo = false; this._fireRepoChangeDebounced!(); } } @@ -243,11 +215,9 @@ export class GitService extends Disposable { private async onWorkspaceFoldersChanged(e?: WorkspaceFoldersChangeEvent) { let initializing = false; if (e === undefined) { - if (workspace.workspaceFolders === undefined) return; - initializing = true; e = { - added: workspace.workspaceFolders, + added: workspace.workspaceFolders || [], removed: [] } as WorkspaceFoldersChangeEvent; } @@ -259,13 +229,21 @@ export class GitService extends Disposable { const rp = await this.getRepoPathCore(fsPath, true); if (rp === undefined) { Logger.log(`onWorkspaceFoldersChanged(${fsPath})`, 'No repository found'); + this._repositories.set(fsPath, undefined); + } + else { + this._repositories.set(fsPath, new Repository(f, rp, this.onRepoChanged.bind(this), this._suspended)); } - this._repositories.set(fsPath, { name: f.name, index: f.index, path: rp } as Repository); } for (const f of e.removed) { if (f.uri.scheme !== DocumentSchemes.File) continue; + const repo = this._repositories.get(f.uri.fsPath); + if (repo !== undefined) { + repo.dispose(); + } + this._repositories.delete(f.uri.fsPath); } @@ -333,8 +311,8 @@ export class GitService extends Disposable { this._repoChangedReasons.push(reason); } - if (!this._focused) { - this._unfocusedChanges.repo = true; + if (this._suspended) { + this._pendingChanges.repo = true; return; } @@ -369,6 +347,15 @@ export class GitService extends Disposable { } } + public async getRepositories(): Promise { + if (this._repositoriesPromise !== undefined) { + await this._repositoriesPromise; + this._repositoriesPromise = undefined; + } + + return [...Iterables.filter(this._repositories.values(), r => r !== undefined) as Iterable]; + } + checkoutFile(uri: GitUri, sha?: string) { sha = sha || uri.sha; Logger.log(`checkoutFile('${uri.repoPath}', '${uri.fsPath}', ${sha})`); @@ -1111,36 +1098,8 @@ export class GitService extends Disposable { return Git.difftool_dirDiff(repoPath, sha1, sha2); } - private _fsWatcherDisposable: Disposable | undefined; - - startWatchingFileSystem() { - if (this._fsWatcherDisposable !== undefined) return; - - const debouncedFn = Functions.debounce((uri: Uri) => this._onDidChangeFileSystem.fire(uri), 2500); - const fn = (uri: Uri) => { - // Ignore .git changes - if (/\.git/.test(uri.fsPath)) return; - - if (!this._focused) { - this._unfocusedChanges.fs = true; - return; - } - - debouncedFn(uri); - }; - - const watcher = workspace.createFileSystemWatcher(`**`); - this._fsWatcherDisposable = Disposable.from( - watcher, - watcher.onDidChange(fn), - watcher.onDidCreate(fn), - watcher.onDidDelete(fn) - ); - } - stopWatchingFileSystem() { - this._fsWatcherDisposable && this._fsWatcherDisposable.dispose(); - this._fsWatcherDisposable = undefined; + this._repositories.forEach(r => r && r.stopWatchingFileSystem()); } stashApply(repoPath: string, stashName: string, deleteAfter: boolean = false) { diff --git a/src/views/repositoryNode.ts b/src/views/repositoryNode.ts index e5daf3c..8b428a2 100644 --- a/src/views/repositoryNode.ts +++ b/src/views/repositoryNode.ts @@ -24,7 +24,7 @@ export class RepositoryNode extends ExplorerNode { async getChildren(): Promise { return [ - new StatusNode(this.uri, this.context, this.git), + new StatusNode(this.uri, this.repo, this.context, this.git), new BranchesNode(this.uri, this.context, this.git), new RemotesNode(this.uri, this.context, this.git), new StashesNode(this.uri, this.context, this.git) diff --git a/src/views/statusNode.ts b/src/views/statusNode.ts index 9831a01..9115418 100644 --- a/src/views/statusNode.ts +++ b/src/views/statusNode.ts @@ -1,18 +1,17 @@ -import { commands, Disposable, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; +import { commands, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; import { WorkspaceState } from '../constants'; import { ExplorerNode, ResourceType } from './explorerNode'; -import { GitService, GitStatus, GitUri } from '../gitService'; +import { GitService, GitStatus, GitUri, Repository, RepositoryStorage } from '../gitService'; import { StatusFilesNode } from './statusFilesNode'; import { StatusUpstreamNode } from './statusUpstreamNode'; -let _eventDisposable: Disposable | undefined; - export class StatusNode extends ExplorerNode { readonly resourceType: ResourceType = 'gitlens:status'; constructor( uri: GitUri, + private repo: Repository, protected readonly context: ExtensionContext, protected readonly git: GitService ) { @@ -49,19 +48,21 @@ export class StatusNode extends ExplorerNode { const status = await this.git.getStatusForRepo(this.uri.repoPath!); if (status === undefined) return new TreeItem('No repo status'); - if (_eventDisposable !== undefined) { - _eventDisposable.dispose(); - _eventDisposable = undefined; + const subscription = this.repo.storage.get(RepositoryStorage.StatusNode); + if (subscription !== undefined) { + subscription.dispose(); + this.repo.storage.delete(RepositoryStorage.StatusNode); } if (this.includeWorkingTree) { this._status = status; if (this.git.config.gitExplorer.autoRefresh && this.context.workspaceState.get(WorkspaceState.GitExplorerAutoRefresh, true)) { - _eventDisposable = this.git.onDidChangeFileSystem(this.onFileSystemChanged, this); - this.context.subscriptions.push(_eventDisposable); + const subscription = this.repo.onDidChangeFileSystem(this.onFileSystemChanged, this); + this.repo.storage.set(RepositoryStorage.StatusNode, subscription); + this.context.subscriptions.push(subscription); - this.git.startWatchingFileSystem(); + this.repo.startWatchingFileSystem(); } }