Browse Source

Moves onDidChangeRepo to Repository.onDidChange

Adds onDidChange to GitService
Adds better multi-root support
Reworks custom view eventing
main
Eric Amodio 7 years ago
parent
commit
b935a05375
12 changed files with 537 additions and 293 deletions
  1. +4
    -1
      CHANGELOG.md
  2. +12
    -3
      package.json
  3. +57
    -38
      src/git/gitContextTracker.ts
  4. +137
    -31
      src/git/models/repository.ts
  5. +76
    -109
      src/gitService.ts
  6. +24
    -3
      src/views/explorerNode.ts
  7. +40
    -8
      src/views/fileHistoryNode.ts
  8. +75
    -44
      src/views/gitExplorer.ts
  9. +13
    -7
      src/views/historyNode.ts
  10. +9
    -6
      src/views/repositoriesNode.ts
  11. +46
    -9
      src/views/repositoryNode.ts
  12. +44
    -34
      src/views/statusNode.ts

+ 4
- 1
CHANGELOG.md View File

@ -19,6 +19,8 @@ ATTENTION! To support multi-root workspaces some underlying fundamentals had to
- Adds support to the `Compare File with Branch...` command (`gitlens.diffWithBranch`) work with renamed files -- closes [#165](https://github.com/eamodio/vscode-gitlens/issues/165)
- Adds `Compare File with Branch...` command (`gitlens.diffWithBranch`) to source control resource context menu
- Adds `Open Repository in Remote` command (`gitlens.openRepoInRemote`) to repository node(s) of the `GitLens` custom view
- Adds `Enable Automatic Refresh` command (`gitlens.gitExplorer.setAutoRefreshToOn`) to the `GitLens` custom view regardless of the current view
- Adds `Disable Automatic Refresh` command (`gitlens.gitExplorer.setAutoRefreshToOff`) to the `GitLens` custom view regardless of the current view
### Changed
- `GitLens` custom view will no longer show if there is no Git repository -- closes [#159](https://github.com/eamodio/vscode-gitlens/issues/159)
@ -28,7 +30,8 @@ ATTENTION! To support multi-root workspaces some underlying fundamentals had to
### Fixed
- Fixes jumpy code lens when deleting characters from a line with a Git code lens
- Fixes? [#178](https://github.com/eamodio/vscode-gitlens/issues/178) - Slight but noticeable keyboard lag with Gitlens
- Fixes [#178](https://github.com/eamodio/vscode-gitlens/issues/178) - Slight but noticeable keyboard lag with Gitlens
- Fixes issue where using the `Refresh` command on a `GitLens` custom view node refreshed the whole view, rather than just the node
## [5.7.1] - 2017-10-19
### Fixed

+ 12
- 3
package.json View File

@ -1148,6 +1148,11 @@
}
},
{
"command": "gitlens.gitExplorer.refreshNode",
"title": "Refresh",
"category": "GitLens"
},
{
"command": "gitlens.gitExplorer.setFilesLayoutToAuto",
"title": "Show Files in Automatic View",
"category": "GitLens"
@ -1410,6 +1415,10 @@
"when": "false"
},
{
"command": "gitlens.gitExplorer.refreshNode",
"when": "false"
},
{
"command": "gitlens.gitExplorer.switchToHistoryView",
"when": "gitlens:gitExplorer:view == repository"
},
@ -1709,12 +1718,12 @@
},
{
"command": "gitlens.gitExplorer.setAutoRefreshToOn",
"when": "view == gitlens.gitExplorer && gitlens:gitExplorer:view == repository && config.gitlens.gitExplorer.autoRefresh && !gitlens:gitExplorer:autoRefresh",
"when": "view == gitlens.gitExplorer && config.gitlens.gitExplorer.autoRefresh && !gitlens:gitExplorer:autoRefresh",
"group": "2_gitlens"
},
{
"command": "gitlens.gitExplorer.setAutoRefreshToOff",
"when": "view == gitlens.gitExplorer && gitlens:gitExplorer:view == repository && config.gitlens.gitExplorer.autoRefresh && gitlens:gitExplorer:autoRefresh",
"when": "view == gitlens.gitExplorer && config.gitlens.gitExplorer.autoRefresh && gitlens:gitExplorer:autoRefresh",
"group": "2_gitlens"
}
],
@ -1965,7 +1974,7 @@
"group": "1_gitlens@2"
},
{
"command": "gitlens.gitExplorer.refresh",
"command": "gitlens.gitExplorer.refreshNode",
"when": "view == gitlens.gitExplorer && viewItem != gitlens:commit-file && viewItem != gitlens:stash-file && viewItem != gitlens:status-file",
"group": "9_gitlens@1"
}

+ 57
- 38
src/git/gitContextTracker.ts View File

@ -3,7 +3,7 @@ import { Functions } from '../system';
import { Disposable, Event, EventEmitter, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode';
import { TextDocumentComparer } from '../comparers';
import { CommandContext, isTextEditor, setCommandContext } from '../constants';
import { GitService, GitUri, RepoChangedReasons } from '../gitService';
import { GitChangeEvent, GitChangeReason, GitService, GitUri, Repository, RepositoryChangeEvent } from '../gitService';
// import { Logger } from '../logger';
export enum BlameabilityChangeReason {
@ -19,6 +19,20 @@ export interface BlameabilityChangeEvent {
reason: BlameabilityChangeReason;
}
interface Context {
editor?: TextEditor;
repo?: Repository;
repoDisposable?: Disposable;
state: ContextState;
uri?: GitUri;
}
interface ContextState {
blameable?: boolean;
dirty: boolean;
tracked?: boolean;
}
export class GitContextTracker extends Disposable {
private _onDidChangeBlameability = new EventEmitter<BlameabilityChangeEvent>();
@ -26,7 +40,7 @@ export class GitContextTracker extends Disposable {
return this._onDidChangeBlameability.event;
}
private _context: { editor?: TextEditor, uri?: GitUri, blameable?: boolean, dirty: boolean, tracked?: boolean } = { dirty: false };
private _context: Context = { state: { dirty: false } };
private _disposable: Disposable | undefined;
private _gitEnabled: boolean;
@ -81,28 +95,26 @@ export class GitContextTracker extends Disposable {
this.updateBlameability(BlameabilityChangeReason.BlameFailed, false);
}
private onRepoChanged(reasons: RepoChangedReasons[]) {
if (reasons.includes(RepoChangedReasons.CacheReset) || reasons.includes(RepoChangedReasons.Unknown)) {
this.updateContext(BlameabilityChangeReason.RepoChanged, this._context.editor);
return;
private onGitChanged(e: GitChangeEvent) {
if (e.reason === GitChangeReason.RemoteCache || e.reason === GitChangeReason.Repositories) {
this.updateRemotes();
}
}
// TODO: Support multi-root
if (!reasons.includes(RepoChangedReasons.Remotes) && !reasons.includes(RepoChangedReasons.Repositories)) return;
this.updateRemotes(this._context.uri);
private onRepoChanged(e: RepositoryChangeEvent) {
this.updateContext(BlameabilityChangeReason.RepoChanged, this._context.editor);
this.updateRemotes();
}
private onTextDocumentChanged(e: TextDocumentChangeEvent) {
if (this._context.editor === undefined || !TextDocumentComparer.equals(this._context.editor.document, e.document)) return;
// If we haven't changed state, kick out
if (this._context.dirty === e.document.isDirty) return;
if (this._context.state.dirty === e.document.isDirty) return;
// Logger.log('GitContextTracker.onTextDocumentChanged', 'Dirty state changed', e);
this._context.dirty = e.document.isDirty;
this._context.state.dirty = e.document.isDirty;
this.updateBlameability(BlameabilityChangeReason.DocumentChanged);
}
@ -110,16 +122,29 @@ export class GitContextTracker extends Disposable {
let tracked: boolean;
if (force || this._context.editor !== editor) {
this._context.editor = editor;
this._context.repo = undefined;
if (this._context.repoDisposable !== undefined) {
this._context.repoDisposable.dispose();
this._context.repoDisposable = undefined;
}
if (editor !== undefined) {
this._context.uri = await GitUri.fromUri(editor.document.uri, this.git);
this._context.dirty = editor.document.isDirty;
const uri = editor.document.uri;
const repo = await this.git.getRepository(uri);
if (repo !== undefined) {
this._context.repo = repo;
this._context.repoDisposable = repo.onDidChange(this.onRepoChanged, this);
}
this._context.uri = await GitUri.fromUri(uri, this.git);
this._context.state.dirty = editor.document.isDirty;
tracked = await this.git.isTracked(this._context.uri);
}
else {
this._context.uri = undefined;
this._context.dirty = false;
this._context.blameable = false;
this._context.state.dirty = false;
this._context.state.blameable = false;
tracked = false;
}
}
@ -130,23 +155,23 @@ export class GitContextTracker extends Disposable {
: false;
}
if (this._context.tracked !== tracked) {
this._context.tracked = tracked;
if (this._context.state.tracked !== tracked) {
this._context.state.tracked = tracked;
setCommandContext(CommandContext.ActiveFileIsTracked, tracked);
}
this.updateBlameability(reason, undefined, force);
this.updateRemotes(this._context.uri);
this.updateRemotes();
}
private updateBlameability(reason: BlameabilityChangeReason, blameable?: boolean, force: boolean = false) {
if (blameable === undefined) {
blameable = this._context.tracked && !this._context.dirty;
blameable = this._context.state.tracked && !this._context.state.dirty;
}
if (!force && this._context.blameable === blameable) return;
if (!force && this._context.state.blameable === blameable) return;
this._context.blameable = blameable;
this._context.state.blameable = blameable;
setCommandContext(CommandContext.ActiveIsBlameable, blameable);
this._onDidChangeBlameability.fire({
@ -156,29 +181,23 @@ export class GitContextTracker extends Disposable {
});
}
private async updateRemotes(uri: GitUri | undefined) {
const repositories = await this.git.getRepositories();
private async updateRemotes() {
let hasRemotes = false;
if (uri !== undefined && this.git.isTrackable(uri)) {
const remotes = await this.git.getRemotes(uri.repoPath);
if (this._context.repo !== undefined) {
const remotes = await this.git.getRemotes(this._context.repo.path);
setCommandContext(CommandContext.ActiveHasRemotes, remotes.length !== 0);
hasRemotes = remotes.length !== 0;
setCommandContext(CommandContext.ActiveHasRemotes, hasRemotes);
}
else {
if (repositories.length === 1) {
const remotes = await this.git.getRemotes(repositories[0].path);
hasRemotes = remotes.length !== 0;
setCommandContext(CommandContext.ActiveHasRemotes, hasRemotes);
}
else {
setCommandContext(CommandContext.ActiveHasRemotes, false);
}
setCommandContext(CommandContext.ActiveHasRemotes, false);
}
if (!hasRemotes) {
const repositories = await this.git.getRepositories();
for (const repo of repositories) {
if (repo === this._context.repo) continue;
const remotes = await this.git.getRemotes(repo.path);
hasRemotes = remotes.length !== 0;

+ 137
- 31
src/git/models/repository.ts View File

@ -2,14 +2,52 @@
import { Functions } from '../../system';
import { Disposable, Event, EventEmitter, RelativePattern, Uri, workspace, WorkspaceFolder } from 'vscode';
export enum RepositoryChange {
// FileSystem = 'file-system',
Repository = 'repository',
Stashes = 'stashes'
}
export class RepositoryChangeEvent {
readonly changes: RepositoryChange[] = [];
constructor(public repository?: Repository) { }
changed(change: RepositoryChange, solely: boolean = false) {
if (solely) return this.changes.length === 1 && this.changes[0] === change;
return this.changes.includes(change);
// const changed = this.changes.includes(change);
// if (changed) return true;
// if (change === RepositoryChange.Repository) {
// return this.changes.includes(RepositoryChange.Stashes);
// }
// return false;
}
}
export interface RepositoryFileSystemChangeEvent {
repository?: Repository;
uris: Uri[];
}
export enum RepositoryStorage {
StatusNode = 'statusNode'
}
export class Repository extends Disposable {
private _onDidChangeFileSystem = new EventEmitter<Uri | undefined>();
get onDidChangeFileSystem(): Event<Uri | undefined> {
private _onDidChange = new EventEmitter<RepositoryChangeEvent>();
get onDidChange(): Event<RepositoryChangeEvent> {
return this._onDidChange.event;
}
private _onDidChangeFileSystem = new EventEmitter<RepositoryFileSystemChangeEvent>();
get onDidChangeFileSystem(): Event<RepositoryFileSystemChangeEvent> {
return this._onDidChangeFileSystem.event;
}
@ -18,14 +56,17 @@ export class Repository extends Disposable {
readonly storage: Map<string, any> = new Map();
private readonly _disposable: Disposable;
private _fireChangeDebounced: ((e: RepositoryChangeEvent) => void) | undefined = undefined;
private _fireFileSystemChangeDebounced: ((e: RepositoryFileSystemChangeEvent) => void) | undefined = undefined;
private _fsWatchCounter = 0;
private _fsWatcherDisposable: Disposable | undefined;
private _pendingChanges: { repo: boolean, fs: boolean } = { repo: false, fs: false };
private _pendingChanges: { repo?: RepositoryChangeEvent, fs?: RepositoryFileSystemChangeEvent } = { };
private _suspended: boolean;
constructor(
private readonly folder: WorkspaceFolder,
public readonly path: string,
readonly onRepoChanged: (uri: Uri) => void,
private readonly onAnyRepositoryChanged: () => void,
suspended: boolean
) {
super(() => this.dispose());
@ -35,14 +76,12 @@ export class Repository extends Disposable {
this._suspended = suspended;
const watcher = workspace.createFileSystemWatcher(new RelativePattern(folder, '**/.git/{index,HEAD,refs/stash,refs/heads/**,refs/remotes/**}'));
const subscriptions = [
this._disposable = Disposable.from(
watcher,
watcher.onDidChange(onRepoChanged),
watcher.onDidCreate(onRepoChanged),
watcher.onDidDelete(onRepoChanged)
];
this._disposable = Disposable.from(...subscriptions);
watcher.onDidChange(this.onRepositoryChanged, this),
watcher.onDidCreate(this.onRepositoryChanged, this),
watcher.onDidDelete(this.onRepositoryChanged, this)
);
}
dispose() {
@ -58,45 +97,112 @@ export class Repository extends Disposable {
this._disposable && this._disposable.dispose();
}
private onFileSystemChanged(uri: Uri) {
// Ignore .git changes
if (/\.git/.test(uri.fsPath)) return;
this.fireFileSystemChange(uri);
}
private onRepositoryChanged(uri: Uri) {
if (uri !== undefined && uri.path.endsWith('ref/stash')) {
this.fireChange(RepositoryChange.Stashes);
return;
}
this.onAnyRepositoryChanged();
this.fireChange(RepositoryChange.Repository);
}
private fireChange(reason: RepositoryChange) {
if (this._fireChangeDebounced === undefined) {
this._fireChangeDebounced = Functions.debounce(this.fireChangeCore, 250);
}
if (this._pendingChanges.repo === undefined) {
this._pendingChanges.repo = new RepositoryChangeEvent(this);
}
const e = this._pendingChanges.repo;
if (!e.changes.includes(reason)) {
e.changes.push(reason);
}
if (this._suspended) return;
this._fireChangeDebounced(e);
}
private fireChangeCore(e: RepositoryChangeEvent) {
this._pendingChanges.repo = undefined;
this._onDidChange.fire(e);
}
private fireFileSystemChange(uri: Uri) {
if (this._fireFileSystemChangeDebounced === undefined) {
this._fireFileSystemChangeDebounced = Functions.debounce(this.fireFileSystemChangeCore, 2500);
}
if (this._pendingChanges.fs === undefined) {
this._pendingChanges.fs = { repository: this, uris: [] };
}
const e = this._pendingChanges.fs;
e.uris.push(uri);
if (this._suspended) return;
this._fireFileSystemChangeDebounced(e);
}
private fireFileSystemChangeCore(e: RepositoryFileSystemChangeEvent) {
this._pendingChanges.fs = undefined;
this._onDidChangeFileSystem.fire(e);
}
containsUri(uri: Uri) {
return this.folder === workspace.getWorkspaceFolder(uri);
}
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();
if (this._pendingChanges.repo !== undefined) {
this._fireChangeDebounced!(this._pendingChanges.repo);
}
if (this._pendingChanges.fs !== undefined) {
this._fireFileSystemChangeDebounced!(this._pendingChanges.fs);
}
}
startWatchingFileSystem() {
this._fsWatchCounter++;
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)
watcher.onDidChange(this.onFileSystemChanged, this),
watcher.onDidCreate(this.onFileSystemChanged, this),
watcher.onDidDelete(this.onFileSystemChanged, this)
);
}
stopWatchingFileSystem() {
this._fsWatcherDisposable && this._fsWatcherDisposable.dispose();
if (this._fsWatcherDisposable === undefined) return;
if (--this._fsWatchCounter > 0) return;
this._fsWatcherDisposable.dispose();
this._fsWatcherDisposable = undefined;
}

+ 76
- 109
src/gitService.ts View File

@ -65,12 +65,14 @@ export enum GitRepoSearchBy {
Sha = 'sha'
}
export enum RepoChangedReasons {
CacheReset = 'cache-reset',
Remotes = 'remotes',
Repositories = 'Repositories',
Stash = 'stash',
Unknown = ''
export enum GitChangeReason {
GitCache = 'git-cache',
RemoteCache = 'remote-cache',
Repositories = 'repositories'
}
export interface GitChangeEvent {
reason: GitChangeReason;
}
export class GitService extends Disposable {
@ -86,17 +88,15 @@ export class GitService extends Disposable {
return this._onDidBlameFail.event;
}
// TODO: Support multi-root { repo, reasons }[]?
private _onDidChangeRepo = new EventEmitter<RepoChangedReasons[]>();
get onDidChangeRepo(): Event<RepoChangedReasons[]> {
return this._onDidChangeRepo.event;
private _onDidChange = new EventEmitter<GitChangeEvent>();
get onDidChange(): Event<GitChangeEvent> {
return this._onDidChange.event;
}
private _cacheDisposable: Disposable | undefined;
private _disposable: Disposable | undefined;
private _documentKeyMap: Map<TextDocument, string>;
private _gitCache: Map<string, GitCacheEntry>;
private _pendingChanges: { repo: boolean } = { repo: false };
private _remotesCache: Map<string, GitRemote[]>;
private _repositories: Map<string, Repository | undefined>;
private _repositoriesPromise: Promise<void> | undefined;
@ -140,17 +140,22 @@ export class GitService extends Disposable {
this._versionedUriCache.clear();
}
public get repoPath(): string | undefined {
get repoPath(): string | undefined {
if (this._repositories.size !== 1) return undefined;
const repo = Iterables.first(this._repositories.values());
return repo === undefined ? undefined : repo.path;
}
public get UseCaching() {
get UseCaching() {
return this.config.advanced.caching.enabled;
}
private onAnyRepositoryChanged() {
this._gitCache.clear();
this._trackedCache.clear();
}
private onConfigurationChanged() {
const encoding = workspace.getConfiguration('files').get<string>('encoding', 'utf8');
setDefaultEncoding(encoding);
@ -184,13 +189,40 @@ export class GitService extends Disposable {
if (this.config.blame.ignoreWhitespace !== ignoreWhitespace) {
this._gitCache.clear();
this.fireRepoChange(RepoChangedReasons.CacheReset);
this.fireChange(GitChangeReason.GitCache);
}
}
private onRemoteProvidersChanged() {
this._remotesCache.clear();
this.fireRepoChange(RepoChangedReasons.Remotes);
this.fireChange(GitChangeReason.RemoteCache);
}
private onTextDocumentChanged(e: TextDocumentChangeEvent) {
let key = this._documentKeyMap.get(e.document);
if (key === undefined) {
key = this.getCacheEntryKey(e.document.uri);
this._documentKeyMap.set(e.document, key);
}
// Don't remove broken blame on change (since otherwise we'll have to run the broken blame again)
const entry = this._gitCache.get(key);
if (entry === undefined || entry.hasErrors) return;
if (this._gitCache.delete(key)) {
Logger.log(`Clear cache entry for '${key}', reason=${RemoveCacheReason[RemoveCacheReason.DocumentChanged]}`);
}
}
private onTextDocumentClosed(document: TextDocument) {
this._documentKeyMap.delete(document);
const key = this.getCacheEntryKey(document.uri);
if (this._gitCache.delete(key)) {
Logger.log(`Clear cache entry for '${key}', reason=${RemoveCacheReason[RemoveCacheReason.DocumentClosed]}`);
}
}
private onWindowStateChanged(e: WindowState) {
@ -201,17 +233,7 @@ export class GitService extends Disposable {
this._repositories.forEach(r => r && r.suspend());
}
const suspended = !e.focused;
const changed = suspended !== this._suspended;
this._suspended = suspended;
if (suspended || !changed) return;
// 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!();
}
this._suspended = !e.focused;
}
private async onWorkspaceFoldersChanged(e?: WorkspaceFoldersChangeEvent) {
@ -234,7 +256,7 @@ export class GitService extends Disposable {
this._repositories.set(fsPath, undefined);
}
else {
this._repositories.set(fsPath, new Repository(f, rp, this.onRepoChanged.bind(this), this._suspended));
this._repositories.set(fsPath, new Repository(f, rp, this.onAnyRepositoryChanged.bind(this), this._suspended));
}
}
@ -253,82 +275,12 @@ export class GitService extends Disposable {
await setCommandContext(CommandContext.HasRepository, hasRepository);
if (!initializing) {
this.fireRepoChange(RepoChangedReasons.Repositories);
this.fireChange(GitChangeReason.Repositories);
}
}
private onTextDocumentChanged(e: TextDocumentChangeEvent) {
let key = this._documentKeyMap.get(e.document);
if (key === undefined) {
key = this.getCacheEntryKey(e.document.uri);
this._documentKeyMap.set(e.document, key);
}
// Don't remove broken blame on change (since otherwise we'll have to run the broken blame again)
const entry = this._gitCache.get(key);
if (entry === undefined || entry.hasErrors) return;
if (this._gitCache.delete(key)) {
Logger.log(`Clear cache entry for '${key}', reason=${RemoveCacheReason[RemoveCacheReason.DocumentChanged]}`);
}
}
private onTextDocumentClosed(document: TextDocument) {
this._documentKeyMap.delete(document);
const key = this.getCacheEntryKey(document.uri);
if (this._gitCache.delete(key)) {
Logger.log(`Clear cache entry for '${key}', reason=${RemoveCacheReason[RemoveCacheReason.DocumentClosed]}`);
}
}
private onRepoChanged(uri: Uri) {
if (uri !== undefined && uri.path.endsWith('ref/stash')) {
this.fireRepoChange(RepoChangedReasons.Stash);
return;
}
this._gitCache.clear();
this._trackedCache.clear();
this.fireRepoChange();
}
private _fireRepoChangeDebounced: (() => void) | undefined = undefined;
private _repoChangedReasons: RepoChangedReasons[] = [];
private fireRepoChange(reason: RepoChangedReasons = RepoChangedReasons.Unknown) {
if (this._fireRepoChangeDebounced === undefined) {
this._fireRepoChangeDebounced = Functions.debounce(this.fireRepoChangeCore, 250);
}
if (!this._repoChangedReasons.includes(reason)) {
this._repoChangedReasons.push(reason);
}
if (this._suspended) {
this._pendingChanges.repo = true;
return;
}
return this._fireRepoChangeDebounced();
}
private fireRepoChangeCore() {
const reasons = this._repoChangedReasons;
this._repoChangedReasons = [];
this._onDidChangeRepo.fire(reasons);
}
public async getRepositories(): Promise<Repository[]> {
if (this._repositoriesPromise !== undefined) {
await this._repositoriesPromise;
this._repositoriesPromise = undefined;
}
return [...Iterables.filter(this._repositories.values(), r => r !== undefined) as Iterable<Repository>];
private fireChange(reason: GitChangeReason) {
this._onDidChange.fire({ reason: reason });
}
checkoutFile(uri: GitUri, sha?: string) {
@ -962,15 +914,8 @@ export class GitService extends Disposable {
if (typeof filePathOrUri === 'string') return this.getRepoPathCore(filePathOrUri, false);
const folder = workspace.getWorkspaceFolder(filePathOrUri);
if (folder !== undefined) {
if (this._repositoriesPromise !== undefined) {
await this._repositoriesPromise;
}
const rp = this._repositories.get(folder.uri.fsPath);
if (rp !== undefined) return rp.path;
}
const repo = await this.getRepository(filePathOrUri);
if (repo !== undefined) return repo.path;
return this.getRepoPathCore(filePathOrUri.fsPath, false);
}
@ -979,6 +924,28 @@ export class GitService extends Disposable {
return Git.revparse_toplevel(isDirectory ? filePath : path.dirname(filePath));
}
async getRepositories(): Promise<Repository[]> {
const repositories = await this.getRepositoriesCore();
return [...Iterables.filter(repositories.values(), r => r !== undefined) as Iterable<Repository>];
}
private async getRepositoriesCore(): Promise<Map<string, Repository | undefined>> {
if (this._repositoriesPromise !== undefined) {
await this._repositoriesPromise;
this._repositoriesPromise = undefined;
}
return this._repositories;
}
async getRepository(uri: Uri): Promise<Repository | undefined> {
const folder = workspace.getWorkspaceFolder(uri);
if (folder === undefined) return undefined;
const repositories = await this.getRepositoriesCore();
return repositories.get(folder.uri.fsPath);
}
async getStashList(repoPath: string | undefined): Promise<GitStash | undefined> {
Logger.log(`getStashList('${repoPath}')`);
if (repoPath === undefined) return undefined;

+ 24
- 3
src/views/explorerNode.ts View File

@ -1,5 +1,5 @@
'use strict';
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Command, Disposable, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { GlyphChars } from '../constants';
import { GitUri } from '../gitService';
import { RefreshNodeCommandArgs } from './gitExplorer';
@ -27,11 +27,25 @@ export declare type ResourceType =
'gitlens:status-file-commits' |
'gitlens:status-upstream';
export abstract class ExplorerNode {
export abstract class ExplorerNode extends Disposable {
abstract readonly resourceType: ResourceType;
constructor(public readonly uri: GitUri) { }
protected children: ExplorerNode[] | undefined;
protected disposable: Disposable | undefined;
constructor(public readonly uri: GitUri) {
super(() => this.dispose());
}
dispose() {
if (this.disposable !== undefined) {
this.disposable.dispose();
this.disposable = undefined;
}
this.resetChildren();
}
abstract getChildren(): ExplorerNode[] | Promise<ExplorerNode[]>;
abstract getTreeItem(): TreeItem | Promise<TreeItem>;
@ -39,6 +53,13 @@ export abstract class ExplorerNode {
getCommand(): Command | undefined {
return undefined;
}
resetChildren() {
if (this.children !== undefined) {
this.children.forEach(c => c.dispose());
this.children = undefined;
}
}
}
export class MessageNode extends ExplorerNode {

+ 40
- 8
src/views/fileHistoryNode.ts View File

@ -1,9 +1,11 @@
'use strict';
import { Iterables } from '../system';
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Disposable, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
import { ExplorerNode, MessageNode, ResourceType } from './explorerNode';
import { GitService, GitUri } from '../gitService';
import { GitUri, Repository, RepositoryChange, RepositoryChangeEvent } from '../gitService';
import { GitExplorer } from './gitExplorer';
import { Logger } from '../logger';
export class FileHistoryNode extends ExplorerNode {
@ -11,28 +13,58 @@ export class FileHistoryNode extends ExplorerNode {
constructor(
uri: GitUri,
protected readonly context: ExtensionContext,
protected readonly git: GitService
private repo: Repository,
private readonly explorer: GitExplorer
) {
super(uri);
}
async getChildren(): Promise<ExplorerNode[]> {
const log = await this.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, this.uri.sha);
const log = await this.explorer.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, this.uri.sha);
if (log === undefined) return [new MessageNode('No file history')];
return [...Iterables.map(log.commits.values(), c => new CommitFileNode(c.fileStatuses[0], c, this.context, this.git, CommitFileNodeDisplayAs.CommitLabel | CommitFileNodeDisplayAs.StatusIcon))];
return [...Iterables.map(log.commits.values(), c => new CommitFileNode(c.fileStatuses[0], c, this.explorer.context, this.explorer.git, CommitFileNodeDisplayAs.CommitLabel | CommitFileNodeDisplayAs.StatusIcon))];
}
getTreeItem(): TreeItem {
if (this.disposable !== undefined) {
this.disposable.dispose();
this.disposable = undefined;
}
// We only need to subscribe if auto-refresh is enabled, because if it becomes enabled we will be refreshed
if (this.explorer.autoRefresh) {
this.disposable = Disposable.from(
this.explorer.onDidChangeAutoRefresh(this.onAutoRefreshChanged, this),
this.repo.onDidChange(this.onRepoChanged, this)
);
}
const item = new TreeItem(`${this.uri.getFormattedPath()}`, TreeItemCollapsibleState.Expanded);
item.contextValue = this.resourceType;
item.iconPath = {
dark: this.context.asAbsolutePath('images/dark/icon-history.svg'),
light: this.context.asAbsolutePath('images/light/icon-history.svg')
dark: this.explorer.context.asAbsolutePath('images/dark/icon-history.svg'),
light: this.explorer.context.asAbsolutePath('images/light/icon-history.svg')
};
return item;
}
private onAutoRefreshChanged() {
if (this.disposable === undefined) return;
// If auto-refresh changes, just kill the subscriptions
// (if it was enabled -- we will get refreshed so we don't have to worry about re-hooking it up here)
this.disposable.dispose();
this.disposable = undefined;
}
private onRepoChanged(e: RepositoryChangeEvent) {
if (e.changed(RepositoryChange.Stashes, true)) return;
Logger.log(`RepositoryNode.onRepoChanged(${e.changes.join()}); triggering node refresh`);
this.explorer.refreshNode(this);
}
}

+ 75
- 44
src/views/gitExplorer.ts View File

@ -6,7 +6,7 @@ import { UriComparer } from '../comparers';
import { ExtensionKey, GitExplorerFilesLayout, IConfig } from '../configuration';
import { CommandContext, GlyphChars, setCommandContext, WorkspaceState } from '../constants';
import { BranchHistoryNode, CommitFileNode, CommitNode, ExplorerNode, HistoryNode, MessageNode, RepositoriesNode, RepositoryNode, StashNode } from './explorerNodes';
import { GitService, GitUri, RepoChangedReasons } from '../gitService';
import { GitChangeEvent, GitChangeReason, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
export * from './explorerNodes';
@ -42,14 +42,19 @@ export class GitExplorer implements TreeDataProvider {
private _root?: ExplorerNode;
private _view: GitExplorerView | undefined;
private _onDidChangeAutoRefresh = new EventEmitter<void>();
public get onDidChangeAutoRefresh(): Event<void> {
return this._onDidChangeAutoRefresh.event;
}
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>();
public get onDidChangeTreeData(): Event<ExplorerNode> {
return this._onDidChangeTreeData.event;
}
constructor(private readonly context: ExtensionContext, private readonly git: GitService) {
constructor(public readonly context: ExtensionContext, public readonly git: GitService) {
commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOn', () => this.setAutoRefresh(this.git.config.gitExplorer.autoRefresh, true), this);
commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOff', () => this.setAutoRefresh(this.git.config.gitExplorer.autoRefresh, true), this);
commands.registerCommand('gitlens.gitExplorer.setAutoRefreshToOff', () => this.setAutoRefresh(this.git.config.gitExplorer.autoRefresh, false), this);
commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToAuto', () => this.setFilesLayout(GitExplorerFilesLayout.Auto), this);
commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToList', () => this.setFilesLayout(GitExplorerFilesLayout.List), this);
commands.registerCommand('gitlens.gitExplorer.setFilesLayoutToTree', () => this.setFilesLayout(GitExplorerFilesLayout.Tree), this);
@ -79,6 +84,14 @@ export class GitExplorer implements TreeDataProvider {
this.onConfigurationChanged();
}
get autoRefresh() {
return this._config.gitExplorer.autoRefresh && this.context.workspaceState.get<boolean>(WorkspaceState.GitExplorerAutoRefresh, true);
}
get config() {
return this._config.gitExplorer;
}
async getTreeItem(node: ExplorerNode): Promise<TreeItem> {
return node.getTreeItem();
}
@ -116,10 +129,10 @@ export class GitExplorer implements TreeDataProvider {
if (repositories.length === 1) {
const repo = repositories[0];
return new RepositoryNode(new GitUri(Uri.file(repo.path), { repoPath: repo.path, fileName: repo.path }), repo, this.context, this.git);
return new RepositoryNode(new GitUri(Uri.file(repo.path), { repoPath: repo.path, fileName: repo.path }), repo, this);
}
return new RepositoriesNode(repositories, this.context, this.git);
return new RepositoriesNode(repositories, this);
}
}
}
@ -130,27 +143,22 @@ export class GitExplorer implements TreeDataProvider {
// If we do have a visible trackable editor, don't change from the last state (avoids issues when focus switches to the problems/output/debug console panes)
if (editor.document === undefined || !this.git.isTrackable(editor.document.uri)) return this._root;
let uri = this.git.getGitUriForFile(editor.document.uri);
if (uri === undefined) {
const repoPath = await this.git.getRepoPath(editor.document.uri);
if (repoPath === undefined) return undefined;
uri = new GitUri(editor.document.uri, { repoPath: repoPath, fileName: editor.document.uri.fsPath });
}
const repo = await this.git.getRepository(editor.document.uri);
if (repo === undefined) return undefined;
const uri = this.git.getGitUriForFile(editor.document.uri) || new GitUri(editor.document.uri, { repoPath: repo.path, fileName: editor.document.uri.fsPath });
if (UriComparer.equals(uri, this._root && this._root.uri)) return this._root;
return new HistoryNode(uri, this.context, this.git);
return new HistoryNode(uri, repo, this);
}
private async onActiveEditorChanged(editor: TextEditor | undefined) {
if (this._view !== GitExplorerView.History) return;
const root = await this.getRootNode(editor);
if (root === this._root) return;
if (!this.setRoot(root)) return;
this._root = root;
this.refresh(RefreshReason.ActiveEditorChanged, undefined, root);
this.refresh(RefreshReason.ActiveEditorChanged, root);
}
private onConfigurationChanged() {
@ -178,64 +186,83 @@ export class GitExplorer implements TreeDataProvider {
}
}
private onRepoChanged(reasons: RepoChangedReasons[]) {
if (this._view !== GitExplorerView.Repository) return;
private onGitChanged(e: GitChangeEvent) {
if (this._root === undefined || this._view !== GitExplorerView.Repository || e.reason !== GitChangeReason.Repositories) return;
// If we are changing the set of repositories then force a root node reset
if (reasons.includes(RepoChangedReasons.Repositories)) {
this._root = undefined;
}
this.clearRoot();
Logger.log(`GitExplorer[view=${this._view}].onRepoChanged(${reasons.join()})`);
Logger.log(`GitExplorer[view=${this._view}].onGitChanged(${e.reason})`);
this.refresh(RefreshReason.RepoChanged);
}
private onVisibleEditorsChanged(editors: TextEditor[]) {
if (this._view !== GitExplorerView.History) return;
if (this._root === undefined || this._view !== GitExplorerView.History) return;
// If we have no visible editors, or no trackable visible editors reset the view
if (editors.length === 0 || !editors.some(e => e.document && this.git.isTrackable(e.document.uri))) {
if (this._root === undefined) return;
this.clearRoot();
this._root = undefined;
this.refresh(RefreshReason.VisibleEditorsChanged);
}
}
async refresh(reason: RefreshReason | undefined, node?: ExplorerNode, root?: ExplorerNode) {
async refresh(reason: RefreshReason | undefined, root?: ExplorerNode) {
if (reason === undefined) {
reason = RefreshReason.Command;
}
Logger.log(`GitExplorer[view=${this._view}].refresh`, `reason='${reason}'`);
if (this._root === undefined || (root === undefined && this._view === GitExplorerView.History)) {
this._root = await this.getRootNode(window.activeTextEditor);
this.clearRoot();
this.setRoot(await this.getRootNode(window.activeTextEditor));
}
this._onDidChangeTreeData.fire(node);
this._onDidChangeTreeData.fire();
}
refreshNode(node: ExplorerNode, args: RefreshNodeCommandArgs) {
if (node instanceof BranchHistoryNode) {
refreshNode(node: ExplorerNode, args?: RefreshNodeCommandArgs) {
Logger.log(`GitExplorer[view=${this._view}].refreshNode`);
if (args !== undefined && node instanceof BranchHistoryNode) {
node.maxCount = args.maxCount;
}
this.refresh(RefreshReason.NodeCommand, node);
this._onDidChangeTreeData.fire(node);
}
async reset(view: GitExplorerView, force: boolean = false) {
this.setView(view);
if (force) {
this._root = undefined;
if (force && this._root !== undefined) {
this.clearRoot();
}
this._root = await this.getRootNode(window.activeTextEditor);
this.setRoot(await this.getRootNode(window.activeTextEditor));
if (force) {
this.refresh(RefreshReason.ViewChanged);
}
}
private clearRoot() {
if (this._root === undefined) return;
this._root.dispose();
this._root = undefined;
}
private setRoot(root: ExplorerNode | undefined): boolean {
if (this._root === root) return false;
if (this._root !== undefined) {
this._root.dispose();
}
this._root = root;
return true;
}
setView(view: GitExplorerView) {
if (this._view === view) return;
@ -347,29 +374,33 @@ export class GitExplorer implements TreeDataProvider {
private _autoRefreshDisposable: Disposable | undefined;
private async setAutoRefresh(enabled: boolean, userToggle: boolean = false) {
private async setAutoRefresh(enabled: boolean, workspaceEnabled?: boolean) {
if (this._autoRefreshDisposable !== undefined) {
this._autoRefreshDisposable.dispose();
this._autoRefreshDisposable = undefined;
}
let toggled = false;
if (enabled) {
enabled = this.context.workspaceState.get<boolean>(WorkspaceState.GitExplorerAutoRefresh, true);
if (workspaceEnabled === undefined) {
workspaceEnabled = this.context.workspaceState.get<boolean>(WorkspaceState.GitExplorerAutoRefresh, true);
}
else {
toggled = workspaceEnabled;
await this.context.workspaceState.update(WorkspaceState.GitExplorerAutoRefresh, workspaceEnabled);
if (userToggle) {
enabled = !enabled;
await this.context.workspaceState.update(WorkspaceState.GitExplorerAutoRefresh, enabled);
this._onDidChangeAutoRefresh.fire();
}
if (enabled) {
this._autoRefreshDisposable = this.git.onDidChangeRepo(this.onRepoChanged, this);
if (workspaceEnabled) {
this._autoRefreshDisposable = this.git.onDidChange(this.onGitChanged, this);
this.context.subscriptions.push(this._autoRefreshDisposable);
}
}
setCommandContext(CommandContext.GitExplorerAutoRefresh, enabled);
setCommandContext(CommandContext.GitExplorerAutoRefresh, enabled && workspaceEnabled);
if (userToggle) {
if (toggled) {
this.refresh(RefreshReason.AutoRefreshChanged);
}
}

+ 13
- 7
src/views/historyNode.ts View File

@ -1,8 +1,9 @@
'use strict';
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { ExplorerNode, ResourceType } from './explorerNode';
import { FileHistoryNode } from './fileHistoryNode';
import { GitService, GitUri } from '../gitService';
import { GitUri, Repository } from '../gitService';
import { GitExplorer } from './gitExplorer';
export class HistoryNode extends ExplorerNode {
@ -10,14 +11,19 @@ export class HistoryNode extends ExplorerNode {
constructor(
uri: GitUri,
protected readonly context: ExtensionContext,
protected readonly git: GitService
private repo: Repository,
private readonly explorer: GitExplorer
) {
super(uri);
}
async getChildren(): Promise<ExplorerNode[]> {
return [new FileHistoryNode(this.uri, this.context, this.git)];
this.resetChildren();
this.children = [
new FileHistoryNode(this.uri, this.repo, this.explorer)
];
return this.children;
}
getTreeItem(): TreeItem {
@ -25,8 +31,8 @@ export class HistoryNode extends ExplorerNode {
item.contextValue = this.resourceType;
item.iconPath = {
dark: this.context.asAbsolutePath('images/dark/icon-history.svg'),
light: this.context.asAbsolutePath('images/light/icon-history.svg')
dark: this.explorer.context.asAbsolutePath('images/dark/icon-history.svg'),
light: this.explorer.context.asAbsolutePath('images/light/icon-history.svg')
};
return item;

+ 9
- 6
src/views/repositoriesNode.ts View File

@ -1,7 +1,8 @@
'use strict';
import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { ExplorerNode, ResourceType } from './explorerNode';
import { GitService, GitUri, Repository } from '../gitService';
import { GitExplorer } from './gitExplorer';
import { GitUri, Repository } from '../gitService';
import { RepositoryNode } from './repositoryNode';
export class RepositoriesNode extends ExplorerNode {
@ -10,16 +11,18 @@ export class RepositoriesNode extends ExplorerNode {
constructor(
private readonly repositories: Repository[],
protected readonly context: ExtensionContext,
protected readonly git: GitService
private readonly explorer: GitExplorer
) {
super(undefined!);
}
async getChildren(): Promise<ExplorerNode[]> {
return this.repositories
this.resetChildren();
this.children = this.repositories
.sort((a, b) => a.index - b.index)
.map(repo => new RepositoryNode(new GitUri(Uri.file(repo.path), { repoPath: repo.path, fileName: repo.path }), repo, this.context, this.git));
.map(repo => new RepositoryNode(new GitUri(Uri.file(repo.path), { repoPath: repo.path, fileName: repo.path }), repo, this.explorer));
return this.children;
}
getTreeItem(): TreeItem {

+ 46
- 9
src/views/repositoryNode.ts View File

@ -1,13 +1,15 @@
'use strict';
import { Strings } from '../system';
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Disposable, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { BranchesNode } from './branchesNode';
import { GlyphChars } from '../constants';
import { ExplorerNode, ResourceType } from './explorerNode';
import { GitService, GitUri, Repository } from '../gitService';
import { GitExplorer } from './gitExplorer';
import { GitUri, Repository, RepositoryChange, RepositoryChangeEvent } from '../gitService';
import { RemotesNode } from './remotesNode';
import { StatusNode } from './statusNode';
import { StashesNode } from './stashesNode';
import { Logger } from '../logger';
export class RepositoryNode extends ExplorerNode {
@ -16,24 +18,59 @@ export class RepositoryNode extends ExplorerNode {
constructor(
uri: GitUri,
private repo: Repository,
protected readonly context: ExtensionContext,
protected readonly git: GitService
private readonly explorer: GitExplorer
) {
super(uri);
}
async getChildren(): Promise<ExplorerNode[]> {
return [
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)
this.resetChildren();
this.children = [
new StatusNode(this.uri, this.repo, this, this.explorer),
new BranchesNode(this.uri, this.explorer.context, this.explorer.git),
new RemotesNode(this.uri, this.explorer.context, this.explorer.git),
new StashesNode(this.uri, this.explorer.context, this.explorer.git)
];
return this.children;
}
getTreeItem(): TreeItem {
if (this.disposable !== undefined) {
this.disposable.dispose();
this.disposable = undefined;
}
// We only need to subscribe if auto-refresh is enabled, because if it becomes enabled we will be refreshed
if (this.explorer.autoRefresh) {
this.disposable = Disposable.from(
this.explorer.onDidChangeAutoRefresh(this.onAutoRefreshChanged, this),
this.repo.onDidChange(this.onRepoChanged, this)
);
}
const item = new TreeItem(`Repository ${Strings.pad(GlyphChars.Dash, 1, 1)} ${this.repo.name || this.uri.repoPath}`, TreeItemCollapsibleState.Expanded);
item.contextValue = this.resourceType;
return item;
}
private onAutoRefreshChanged() {
if (this.disposable === undefined) return;
// If auto-refresh changes, just kill the subscriptions
// (if it was enabled -- we will get refreshed so we don't have to worry about re-hooking it up here)
this.disposable.dispose();
this.disposable = undefined;
}
private onRepoChanged(e: RepositoryChangeEvent) {
Logger.log(`RepositoryNode.onRepoChanged(${e.changes.join()}); triggering node refresh`);
let node: ExplorerNode | undefined;
if (this.children !== undefined && e.changed(RepositoryChange.Stashes, true)) {
node = this.children.find(c => c instanceof StashesNode);
}
this.explorer.refreshNode(node || this);
}
}

+ 44
- 34
src/views/statusNode.ts View File

@ -1,7 +1,7 @@
import { commands, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { WorkspaceState } from '../constants';
import { commands, Disposable, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { ExplorerNode, ResourceType } from './explorerNode';
import { GitService, GitStatus, GitUri, Repository, RepositoryStorage } from '../gitService';
import { GitExplorer } from './gitExplorer';
import { GitStatus, GitUri, Repository, RepositoryFileSystemChangeEvent } from '../gitService';
import { Logger } from '../logger';
import { StatusFilesNode } from './statusFilesNode';
import { StatusUpstreamNode } from './statusUpstreamNode';
@ -13,58 +13,59 @@ export class StatusNode extends ExplorerNode {
constructor(
uri: GitUri,
private repo: Repository,
protected readonly context: ExtensionContext,
protected readonly git: GitService
private parent: ExplorerNode,
private readonly explorer: GitExplorer
) {
super(uri);
}
async getChildren(): Promise<ExplorerNode[]> {
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
if (status === undefined) return [];
this.resetChildren();
const children: ExplorerNode[] = [];
this.children = [];
const status = await this.explorer.git.getStatusForRepo(this.uri.repoPath!);
if (status === undefined) return this.children;
if (status.state.behind) {
children.push(new StatusUpstreamNode(status, 'behind', this.context, this.git));
this.children.push(new StatusUpstreamNode(status, 'behind', this.explorer.context, this.explorer.git));
}
if (status.state.ahead) {
children.push(new StatusUpstreamNode(status, 'ahead', this.context, this.git));
this.children.push(new StatusUpstreamNode(status, 'ahead', this.explorer.context, this.explorer.git));
}
if (status.state.ahead || (status.files.length !== 0 && this.includeWorkingTree)) {
const range = status.upstream
? `${status.upstream}..${status.branch}`
: undefined;
children.push(new StatusFilesNode(status, range, this.context, this.git));
this.children.push(new StatusFilesNode(status, range, this.explorer.context, this.explorer.git));
}
return children;
return this.children;
}
private _status: GitStatus | undefined;
async getTreeItem(): Promise < TreeItem > {
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
if (status === undefined) return new TreeItem('No repo status');
const subscription = this.repo.storage.get(RepositoryStorage.StatusNode);
if (subscription !== undefined) {
subscription.dispose();
this.repo.storage.delete(RepositoryStorage.StatusNode);
if (this.disposable !== undefined) {
this.disposable.dispose();
this.disposable = undefined;
}
if (this.includeWorkingTree) {
const status = await this.explorer.git.getStatusForRepo(this.uri.repoPath!);
if (status === undefined) return new TreeItem('No repo status');
if (this.explorer.autoRefresh && this.includeWorkingTree) {
this._status = status;
if (this.git.config.gitExplorer.autoRefresh && this.context.workspaceState.get<boolean>(WorkspaceState.GitExplorerAutoRefresh, true)) {
const subscription = this.repo.onDidChangeFileSystem(this.onFileSystemChanged, this);
this.repo.storage.set(RepositoryStorage.StatusNode, subscription);
this.context.subscriptions.push(subscription);
this.disposable = Disposable.from(
this.explorer.onDidChangeAutoRefresh(this.onAutoRefreshChanged, this),
this.repo.onDidChangeFileSystem(this.onFileSystemChanged, this),
{ dispose: () => this.repo.stopWatchingFileSystem() }
);
this.repo.startWatchingFileSystem();
}
this.repo.startWatchingFileSystem();
}
let hasChildren = false;
@ -98,32 +99,41 @@ export class StatusNode extends ExplorerNode {
item.contextValue = this.resourceType;
item.iconPath = {
dark: this.context.asAbsolutePath(`images/dark/icon-repo${iconSuffix}.svg`),
light: this.context.asAbsolutePath(`images/light/icon-repo${iconSuffix}.svg`)
dark: this.explorer.context.asAbsolutePath(`images/dark/icon-repo${iconSuffix}.svg`),
light: this.explorer.context.asAbsolutePath(`images/light/icon-repo${iconSuffix}.svg`)
};
return item;
}
private get includeWorkingTree(): boolean {
return this.git.config.gitExplorer.includeWorkingTree;
return this.explorer.config.includeWorkingTree;
}
private onAutoRefreshChanged() {
if (this.disposable === undefined) return;
// If auto-refresh changes, just kill the subscriptions
// (if it was enabled -- we will get refreshed so we don't have to worry about re-hooking it up here)
this.disposable.dispose();
this.disposable = undefined;
}
private async onFileSystemChanged(uri?: Uri) {
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
private async onFileSystemChanged(e: RepositoryFileSystemChangeEvent) {
const status = await this.explorer.git.getStatusForRepo(this.uri.repoPath!);
// If we haven't changed from having some working changes to none or vice versa then just refresh the node
// This is because of https://github.com/Microsoft/vscode/issues/34789
if (this._status !== undefined && status !== undefined &&
((this._status.files.length === status.files.length) || (this._status.files.length > 0 && status.files.length > 0))) {
Logger.log(`GitExplorer.StatusNode.onFileSystemChanged(${uri && uri.fsPath}); triggering node refresh`);
Logger.log(`StatusNode.onFileSystemChanged; triggering node refresh`);
commands.executeCommand('gitlens.gitExplorer.refreshNode', this);
return;
}
Logger.log(`GitExplorer.StatusNode.onFileSystemChanged(${uri && uri.fsPath}); triggering refresh`);
commands.executeCommand('gitlens.gitExplorer.refresh');
Logger.log(`StatusNode.onFileSystemChanged; triggering parent node refresh`);
commands.executeCommand('gitlens.gitExplorer.refreshNode', this.parent);
}
}

Loading…
Cancel
Save