33 * GPL-3.0-only. See LICENSE.md in the project root for license details.
44 */
55
6- import { Disposable , Uri , WorkspaceFolder , WorkspaceFoldersChangeEvent , window } from 'vscode'
7- import { FSWatcher , existsSync , watch } from 'fs'
6+ import { Disposable , WorkspaceFolder , WorkspaceFoldersChangeEvent , window } from 'vscode'
7+ import { existsSync , WatchEventType } from 'fs'
8+ import { PathWatcher , WatcherCallback } from './Foundation/PathWatcher'
89import { join } from 'path'
910
10- type CallbackFunction = ( event : Uri ) => void
11+ type RepoWatcherCallback = ( event : WatchEventType , filename : string ) => void
1112
1213// https://github.com/Microsoft/vscode/issues/3025
1314export default class implements Disposable {
14- private callback : CallbackFunction
15- private watchers : Map < string , FSWatcher > = new Map ( ) as Map < string , FSWatcher >
15+ private callback : RepoWatcherCallback
16+ private watchers : Map < string , PathWatcher [ ] > = new Map ( ) as Map < string , PathWatcher [ ] >
1617
1718 /**
1819 * Creates a new watcher.
1920 *
20- * @param repositories the open repositories when starting the extension
21- * @param callback the callback to run when identifying changes
21+ * @param repositories the repositories to watch
22+ * @param callback the callback to run when identifying changes
23+ * @param withSubmodules Monitor submodules too
2224 */
23- constructor ( repositories : Promise < string [ ] > , callback : CallbackFunction ) {
25+ constructor ( repositories : Promise < string [ ] > , callback : RepoWatcherCallback ) {
2426 this . callback = callback
2527
2628 void repositories . then ( ( directories ) => {
2729 directories . forEach ( ( directory ) => {
28- this . registerProjectWatcher ( directory )
30+ this . registerProjectWatchers ( directory )
2931 } )
3032 } )
3133 }
@@ -38,67 +40,77 @@ export default class implements Disposable {
3840 public configure ( directoryChanges : WorkspaceFoldersChangeEvent ) : void {
3941 directoryChanges . added . forEach ( ( changedDirectory : WorkspaceFolder ) => {
4042 const directory = changedDirectory . uri . fsPath
41- this . registerProjectWatcher ( directory )
43+ this . registerProjectWatchers ( directory )
4244 } )
4345
4446 directoryChanges . removed . forEach ( ( changedDirectory : WorkspaceFolder ) => {
4547 const directory = changedDirectory . uri . fsPath
46- this . removeProjectWatcher ( directory )
48+ this . removeProjectWatchers ( directory )
4749 } )
4850 }
4951
5052 /**
5153 * Disposes this object.
54+ * @see Disposable.dispose()
5255 */
5356 public dispose ( ) : void {
5457 for ( const path of this . watchers . keys ( ) ) {
55- this . removeProjectWatcher ( path )
58+ this . removeProjectWatchers ( path )
5659 }
5760 }
5861
5962 /**
60- * Registers a new project directory watcher .
63+ * Registers the project directory watchers .
6164 *
6265 * @param projectPath the directory path
6366 */
64- private registerProjectWatcher ( projectPath : string ) : void {
67+ private registerProjectWatchers ( projectPath : string ) : void {
6568 global . dbg ( `[FSWatch] Watch ${ projectPath } ...` )
66- if ( this . watchers . has ( projectPath ) ) {
69+ const pathToMonitor = join ( projectPath , '.git' , 'refs' )
70+ if ( ! existsSync ( pathToMonitor ) ) {
6771 return
6872 }
6973
70- const pathToMonitor = join ( projectPath , '.git' , 'refs' )
74+ this . registerPathWatcher ( pathToMonitor , projectPath )
75+ }
7176
72- if ( ! existsSync ( pathToMonitor ) ) {
77+ /**
78+ * Creates a FS Watcher for a directory.
79+ * @param pathToMonitor the path to monitor
80+ * @param projectPath the path to use in the callback
81+ */
82+ private registerPathWatcher ( pathToMonitor : string , projectPath : string ) {
83+ const watchers = this . watchers . get ( projectPath )
84+ ?? this . watchers . set ( projectPath , [ ] ) . get ( projectPath ) ! // eslint-disable-line @typescript-eslint/no-non-null-assertion
85+
86+ if ( watchers . length && watchers . find ( ( watcher ) => watcher . for ( pathToMonitor ) ) ) {
7387 return
7488 }
7589
7690 try {
77- const watcher = watch ( pathToMonitor , ( event : string , filename ) => {
91+ // Create the watcher callback which will review if a reaction is needed
92+ // based on the changed (stash) file.
93+ const callback : WatcherCallback = ( event , filename ) => {
7894 if ( filename ?. includes ( 'stash' ) ) {
79- this . callback ( Uri . file ( projectPath ) )
95+ this . callback ( event , projectPath )
8096 }
81- } )
97+ }
8298
83- this . watchers . set ( projectPath , watcher )
99+ watchers . push ( PathWatcher . watch ( pathToMonitor , callback ) )
84100 }
85101 catch ( error ) {
102+ const msg = `Unable to create a stashes monitor for ${ pathToMonitor } .`
103+ + ' This may happen on NFS or if the path is a link.'
104+ + ' See the console for details'
105+ console . error ( msg )
86106 console . error ( error )
87- void window . showErrorMessage ( `Unable to a create a stashes monitor for
88- ${ projectPath } . This may happen on NFS or if the path is a link` )
107+ void window . showErrorMessage ( msg )
89108 }
90109 }
91110
92- /**
93- * Removes an active project directory watcher.
94- *
95- * @param path the directory path
96- */
97- private removeProjectWatcher ( path : string ) : void {
98- if ( this . watchers . has ( path ) ) {
99- global . dbg ( `[FSWatch] Stop watching ${ path } ...` )
100- this . watchers . get ( path ) ?. close ( )
101- this . watchers . delete ( path )
102- }
111+ private removeProjectWatchers ( path : string ) : void {
112+ global . dbg ( `[FSWatch] Stop watching ${ path } ...` )
113+ this . watchers . get ( path ) ?. forEach ( ( watcher ) => { watcher . dispose ( ) } )
114+ this . watchers . delete ( path )
103115 }
104116}
0 commit comments