diff --git a/package.json b/package.json index 47d2ce5..c757945 100644 --- a/package.json +++ b/package.json @@ -1318,14 +1318,14 @@ "group": "navigation" }, { - "command": "gitlens.stashExplorer.refresh", + "command": "gitlens.stashSave", "when": "gitlens:enabled && view == gitlens.stashExplorer", - "group": "navigation" + "group": "navigation@1" }, { - "command": "gitlens.stashSave", + "command": "gitlens.stashExplorer.refresh", "when": "gitlens:enabled && view == gitlens.stashExplorer", - "group": "navigation" + "group": "navigation@2" } ], "view/item/context": [ diff --git a/src/commands/stashApply.ts b/src/commands/stashApply.ts index db47a1e..36159e7 100644 --- a/src/commands/stashApply.ts +++ b/src/commands/stashApply.ts @@ -2,7 +2,7 @@ import { Strings } from '../system'; import { MessageItem, window } from 'vscode'; import { GitService, GitStashCommit } from '../gitService'; -import { Command, Commands } from './common'; +import { Command, CommandContext, Commands } from './common'; import { GlyphChars } from '../constants'; import { CommitQuickPickItem, StashListQuickPick } from '../quickPicks'; import { Logger } from '../logger'; @@ -23,59 +23,58 @@ export class StashApplyCommand extends Command { super(Commands.StashApply); } - async execute(args: StashApplyCommandArgs | StashCommitNode = { confirm: true, deleteAfter: false }) { - if (!this.git.repoPath) return undefined; - - if (args instanceof StashCommitNode) { - try { - const ret = await this.git.stashApply(this.git.repoPath, args.commit.stashName, false); - args.refreshNode(); - return ret; - } catch (ex) { - return this._errorHandling(ex); - } - } else { + protected async preExecute(context: CommandContext, args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) { + if (context.type === 'view' && context.node instanceof StashCommitNode) { args = { ...args }; - if (args.stashItem === undefined || args.stashItem.stashName === undefined) { - const stash = await this.git.getStashList(this.git.repoPath); - if (stash === undefined) return window.showInformationMessage(`There are no stashed changes`); - const currentCommand = new CommandQuickPickItem({ - label: `go back ${GlyphChars.ArrowBack}`, - description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to apply stashed changes` - }, Commands.StashApply, [args]); + const stash = context.node.commit; + args.stashItem = { stashName: stash.stashName, message: stash.message }; - const pick = await StashListQuickPick.show(this.git, stash, 'apply', args.goBackCommand, currentCommand); - if (pick === undefined || !(pick instanceof CommitQuickPickItem)) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); + return this.execute(args); + } - args.goBackCommand = currentCommand; - args.stashItem = pick.commit as GitStashCommit; - } + return super.preExecute(context, args); + } - try { - if (args.confirm) { - const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}` : args.stashItem.message; - const result = await window.showWarningMessage(`Apply stashed changes '${message}' to your working tree?`, { title: 'Yes, delete after applying' } as MessageItem, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem); - if (result === undefined || result.title === 'No') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); + async execute(args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) { + if (!this.git.repoPath) return undefined; - args.deleteAfter = result.title !== 'Yes'; - } + args = { ...args }; + if (args.stashItem === undefined || args.stashItem.stashName === undefined) { + const stash = await this.git.getStashList(this.git.repoPath); + if (stash === undefined) return window.showInformationMessage(`There are no stashed changes`); - return await this.git.stashApply(this.git.repoPath, args.stashItem.stashName, args.deleteAfter); - } - catch (ex) { - return this._errorHandling(ex); - } + const currentCommand = new CommandQuickPickItem({ + label: `go back ${GlyphChars.ArrowBack}`, + description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to apply stashed changes` + }, Commands.StashApply, [args]); + + const pick = await StashListQuickPick.show(this.git, stash, 'apply', args.goBackCommand, currentCommand); + if (pick === undefined || !(pick instanceof CommitQuickPickItem)) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); + + args.goBackCommand = currentCommand; + args.stashItem = pick.commit as GitStashCommit; } - } - private _errorHandling(ex: any) { - Logger.error(ex, 'StashApplyCommand'); - if (ex.message.includes('Your local changes to the following files would be overwritten by merge')) { - return window.showErrorMessage(`Unable to apply stash. Your working tree changes would be overwritten.`); + try { + if (args.confirm) { + const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}` : args.stashItem.message; + const result = await window.showWarningMessage(`Apply stashed changes '${message}' to your working tree?`, { title: 'Yes, delete after applying' } as MessageItem, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem); + if (result === undefined || result.title === 'No') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); + + args.deleteAfter = result.title !== 'Yes'; + } + + return await this.git.stashApply(this.git.repoPath, args.stashItem.stashName, args.deleteAfter); } - else { - return window.showErrorMessage(`Unable to apply stash. See output channel for more details`); + catch (ex) { + Logger.error(ex, 'StashApplyCommand'); + if (ex.message.includes('Your local changes to the following files would be overwritten by merge')) { + return window.showErrorMessage(`Unable to apply stash. Your working tree changes would be overwritten.`); + } + else { + return window.showErrorMessage(`Unable to apply stash. See output channel for more details`); + } } } } \ No newline at end of file diff --git a/src/commands/stashDelete.ts b/src/commands/stashDelete.ts index 64185ef..bc45153 100644 --- a/src/commands/stashDelete.ts +++ b/src/commands/stashDelete.ts @@ -1,6 +1,6 @@ 'use strict'; import { MessageItem, window } from 'vscode'; -import { Command, Commands } from './common'; +import { Command, CommandContext, Commands } from './common'; import { GlyphChars } from '../constants'; import { GitService } from '../gitService'; import { Logger } from '../logger'; @@ -20,17 +20,22 @@ export class StashDeleteCommand extends Command { super(Commands.StashDelete); } - async execute(args: StashDeleteCommandArgs | StashCommitNode = { confirm: true }) { - if (!this.git.repoPath) return undefined; - let stashCommitNode = undefined; - if (args instanceof StashCommitNode) { - stashCommitNode = args; - args = { - confirm: true, - stashItem: args.commit - }; + protected async preExecute(context: CommandContext, args: StashDeleteCommandArgs = { confirm: true }) { + if (context.type === 'view' && context.node instanceof StashCommitNode) { + args = { ...args }; + + const stash = context.node.commit; + args.stashItem = { stashName: stash.stashName, message: stash.message }; + + return this.execute(args); } + return super.preExecute(context, args); + } + + async execute(args: StashDeleteCommandArgs = { confirm: true }) { + if (!this.git.repoPath) return undefined; + args = { ...args }; if (args.stashItem === undefined || args.stashItem.stashName === undefined) return undefined; @@ -45,11 +50,7 @@ export class StashDeleteCommand extends Command { if (result === undefined || result.title !== 'Yes') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); } - const ret = await this.git.stashDelete(this.git.repoPath, args.stashItem.stashName); - if (stashCommitNode) { - stashCommitNode.refreshNode(); - } - return ret; + return await this.git.stashDelete(this.git.repoPath, args.stashItem.stashName); } catch (ex) { Logger.error(ex, 'StashDeleteCommand'); diff --git a/src/commands/stashSave.ts b/src/commands/stashSave.ts index 87849f5..cb78d6b 100644 --- a/src/commands/stashSave.ts +++ b/src/commands/stashSave.ts @@ -18,7 +18,7 @@ export class StashSaveCommand extends Command { super(Commands.StashSave); } - async execute(args: StashSaveCommandArgs = { unstagedOnly : false }) { + async execute(args: StashSaveCommandArgs = { unstagedOnly: false }) { if (!this.git.repoPath) return undefined; args = { ...args }; diff --git a/src/gitService.ts b/src/gitService.ts index afd074a..5e995dd 100644 --- a/src/gitService.ts +++ b/src/gitService.ts @@ -65,16 +65,23 @@ export const GitRepoSearchBy = { Sha: 'sha' as GitRepoSearchBy }; +type RepoChangedReasons = 'stash' | 'unknown'; + export class GitService extends Disposable { + private _onDidBlameFail = new EventEmitter(); + get onDidBlameFail(): Event { + return this._onDidBlameFail.event; + } + private _onDidChangeGitCache = new EventEmitter(); get onDidChangeGitCache(): Event { return this._onDidChangeGitCache.event; } - private _onDidBlameFail = new EventEmitter(); - get onDidBlameFail(): Event { - return this._onDidBlameFail.event; + private _onDidChangeRepo = new EventEmitter(); + get onDidChangeRepo(): Event { + return this._onDidChangeRepo.event; } private _gitCache: Map; @@ -86,9 +93,9 @@ export class GitService extends Disposable { private _codeLensProvider: GitCodeLensProvider | undefined; private _codeLensProviderDisposable: Disposable | undefined; private _disposable: Disposable | undefined; - private _fireGitCacheChangeDebounced: () => void; - private _fsWatcher: FileSystemWatcher | undefined; private _gitignore: Promise; + private _repoWatcher: FileSystemWatcher | undefined; + private _stashWatcher: FileSystemWatcher | undefined; static EmptyPromise: Promise = Promise.resolve(undefined); @@ -99,8 +106,6 @@ export class GitService extends Disposable { this._remotesCache = new Map(); this._uriCache = new Map(); - this._fireGitCacheChangeDebounced = Functions.debounce(this._fireGitCacheChange, 50); - this._onConfigurationChanged(); const subscriptions: Disposable[] = []; @@ -120,8 +125,11 @@ export class GitService extends Disposable { this._cacheDisposable && this._cacheDisposable.dispose(); this._cacheDisposable = undefined; - this._fsWatcher && this._fsWatcher.dispose(); - this._fsWatcher = undefined; + this._repoWatcher && this._repoWatcher.dispose(); + this._repoWatcher = undefined; + + this._stashWatcher && this._stashWatcher.dispose(); + this._stashWatcher = undefined; this._gitCache.clear(); this._remotesCache.clear(); @@ -165,14 +173,16 @@ export class GitService extends Disposable { if (cfg.advanced.caching.enabled) { this._cacheDisposable && this._cacheDisposable.dispose(); - this._fsWatcher = this._fsWatcher || workspace.createFileSystemWatcher('**/.git/index', true, false, true); + this._repoWatcher = this._repoWatcher || workspace.createFileSystemWatcher('**/.git/index', true, false, true); + this._stashWatcher = this._stashWatcher || workspace.createFileSystemWatcher('**/.git/refs/stash', true, false, true); const disposables: Disposable[] = []; disposables.push(workspace.onDidCloseTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentClosed))); disposables.push(workspace.onDidChangeTextDocument(this._onTextDocumentChanged, this)); disposables.push(workspace.onDidSaveTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentSaved))); - disposables.push(this._fsWatcher.onDidChange(this._onGitChanged, this)); + disposables.push(this._repoWatcher.onDidChange(this._onRepoChanged, this)); + disposables.push(this._stashWatcher.onDidChange(this._onStashChanged, this)); this._cacheDisposable = Disposable.from(...disposables); } @@ -180,8 +190,11 @@ export class GitService extends Disposable { this._cacheDisposable && this._cacheDisposable.dispose(); this._cacheDisposable = undefined; - this._fsWatcher && this._fsWatcher.dispose(); - this._fsWatcher = undefined; + this._repoWatcher && this._repoWatcher.dispose(); + this._repoWatcher = undefined; + + this._stashWatcher && this._stashWatcher.dispose(); + this._stashWatcher = undefined; this._gitCache.clear(); this._remotesCache.clear(); @@ -229,19 +242,51 @@ export class GitService extends Disposable { }, 1); } - private _onGitChanged() { + private _onRepoChanged() { this._gitCache.clear(); - this._fireGitCacheChangeDebounced(); + this._fireRepoChange(); + this._fireGitCacheChange(); } + private _onStashChanged() { + this._fireRepoChange('stash'); + } + + private _fireGitCacheChangeDebounced: (() => void) | undefined = undefined; + private _fireGitCacheChange() { - setTimeout(() => { - // Refresh the code lenses - this._codeLensProvider && this._codeLensProvider.reset(); + if (this._fireGitCacheChangeDebounced === undefined) { + this._fireGitCacheChangeDebounced = Functions.debounce(this._fireGitCacheChangeCore, 50); + } - this._onDidChangeGitCache.fire(); - }, 1); + return this._fireGitCacheChangeDebounced(); + } + + private _fireGitCacheChangeCore() { + // Refresh the code lenses + this._codeLensProvider && this._codeLensProvider.reset(); + + this._onDidChangeGitCache.fire(); + } + + private _fireRepoChangeDebounced: (() => void) | undefined = undefined; + private _repoChangedReasons: RepoChangedReasons[] = []; + + private _fireRepoChange(reason: RepoChangedReasons = 'unknown') { + if (this._fireRepoChangeDebounced === undefined) { + this._fireRepoChangeDebounced = Functions.debounce(this._fireRepoChangeCore, 50); + } + + this._repoChangedReasons.push(reason); + return this._fireRepoChangeDebounced(); + } + + private _fireRepoChangeCore() { + const reasons = this._repoChangedReasons; + this._repoChangedReasons = []; + + this._onDidChangeRepo.fire(reasons); } private _removeCachedEntry(document: TextDocument, reason: RemoveCacheReason) { @@ -260,7 +305,7 @@ export class GitService extends Disposable { Logger.log(`Clear cache entry for '${cacheKey}', reason=${RemoveCacheReason[reason]}`); if (reason === RemoveCacheReason.DocumentSaved) { - this._fireGitCacheChangeDebounced(); + this._fireGitCacheChange(); } } } diff --git a/src/views/explorerNode.ts b/src/views/explorerNode.ts index 2c67f03..2f36fd8 100644 --- a/src/views/explorerNode.ts +++ b/src/views/explorerNode.ts @@ -13,5 +13,5 @@ export abstract class ExplorerNode { abstract getTreeItem(): TreeItem | Promise; onDidChangeTreeData?: Event; - refreshNode?(): void; + refresh?(): void; } \ No newline at end of file diff --git a/src/views/gitExplorer.ts b/src/views/gitExplorer.ts index 29e4a60..2ebdceb 100644 --- a/src/views/gitExplorer.ts +++ b/src/views/gitExplorer.ts @@ -1,4 +1,5 @@ 'use strict'; +// import { Functions } from '../system'; import { commands, Event, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, Uri } from 'vscode'; import { UriComparer } from '../comparers'; import { ExplorerNode, FileHistoryNode, RepositoryNode, ResourceType, StashNode } from './explorerNodes'; @@ -8,6 +9,8 @@ export * from './explorerNodes'; export class GitExplorer implements TreeDataProvider { + // private _refreshDebounced: () => void; + private _onDidChangeTreeData = new EventEmitter(); public get onDidChangeTreeData(): Event { return this._onDidChangeTreeData.event; @@ -18,6 +21,8 @@ export class GitExplorer implements TreeDataProvider { constructor(private context: ExtensionContext, private git: GitService) { commands.registerCommand('gitlens.gitExplorer.refresh', () => this.refresh()); + // this._refreshDebounced = Functions.debounce(this.refresh.bind(this), 250); + // const editor = window.activeTextEditor; // const uri = (editor !== undefined && editor.document !== undefined) @@ -29,6 +34,9 @@ export class GitExplorer implements TreeDataProvider { } async getTreeItem(node: ExplorerNode): Promise { + // if (node.onDidChangeTreeData !== undefined) { + // node.onDidChangeTreeData(() => setTimeout(this._refreshDebounced, 1)); + // } return node.getTreeItem(); } @@ -51,12 +59,12 @@ export class GitExplorer implements TreeDataProvider { if (!this._roots.some(_ => _.resourceType === type.rootType && UriComparer.equals(uri, _.uri))) { this._roots.push(new type(uri, this.context, this.git)); } - this._onDidChangeTreeData.fire(); + this.refresh(); } clear() { this._roots = []; - this._onDidChangeTreeData.fire(); + this.refresh(); } refresh() { diff --git a/src/views/stashCommitNode.ts b/src/views/stashCommitNode.ts index d41817d..1db86f8 100644 --- a/src/views/stashCommitNode.ts +++ b/src/views/stashCommitNode.ts @@ -13,10 +13,6 @@ export class StashCommitNode extends ExplorerNode { return this._onDidChangeTreeData.event; } - public refreshNode() { - this._onDidChangeTreeData.fire(); - } - constructor(public readonly commit: GitStashCommit, context: ExtensionContext, git: GitService) { super(new GitUri(commit.uri, commit), context, git); } @@ -43,4 +39,8 @@ export class StashCommitNode extends ExplorerNode { // }; return item; } + + refresh() { + this._onDidChangeTreeData.fire(); + } } \ No newline at end of file diff --git a/src/views/stashExplorer.ts b/src/views/stashExplorer.ts index db4f23f..c8a21e3 100644 --- a/src/views/stashExplorer.ts +++ b/src/views/stashExplorer.ts @@ -1,4 +1,5 @@ 'use strict'; +// import { Functions } from '../system'; import { commands, Event, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, Uri } from 'vscode'; import { ExplorerNode, StashNode } from './explorerNodes'; import { GitService, GitUri } from '../gitService'; @@ -8,6 +9,8 @@ export * from './explorerNodes'; export class StashExplorer implements TreeDataProvider { private _node: ExplorerNode; + // private _refreshDebounced: () => void; + private _onDidChangeTreeData = new EventEmitter(); public get onDidChangeTreeData(): Event { return this._onDidChangeTreeData.event; @@ -16,6 +19,14 @@ export class StashExplorer implements TreeDataProvider { constructor(private context: ExtensionContext, private git: GitService) { commands.registerCommand('gitlens.stashExplorer.refresh', () => this.refresh()); + context.subscriptions.push(this.git.onDidChangeRepo(reasons => { + if (!reasons.includes('stash')) return; + + this.refresh(); + }, this)); + + // this._refreshDebounced = Functions.debounce(this.refresh.bind(this), 250); + // const editor = window.activeTextEditor; // const uri = (editor !== undefined && editor.document !== undefined) @@ -27,11 +38,9 @@ export class StashExplorer implements TreeDataProvider { } async getTreeItem(node: ExplorerNode): Promise { - if (node.onDidChangeTreeData) { - node.onDidChangeTreeData(() => { - this._onDidChangeTreeData.fire(); - }); - } + // if (node.onDidChangeTreeData !== undefined) { + // node.onDidChangeTreeData(() => setTimeout(this._refreshDebounced, 1)); + // } return node.getTreeItem(); }