diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2020-01-08 21:02:01 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2020-01-08 21:34:02 +1100 |
commit | 1ce5c69cd2ece7bb306f1340df3eac20bd74070e (patch) | |
tree | 5cd32b77141b2f570f8d980768d6dcfde8746f6b | |
parent | 205d731d7be974abcece5e02cc351bd26101168c (diff) |
improve file watching
By default, macs have 256 open files allowed by a given process.
This sucks when you end up with over 256 files modified in a repo
because after you've watched all of them, lots of other calls to
the command line will fail due to violating the limit.
Given there's no easy platform agnostic way to see what you've got
configured for how many files a process can have open, I'm going to
arbitrarily set the max to 200 and when we hit the limit we start
unwatching older files to make way for new ones.
WIP
-rw-r--r-- | pkg/gui/file_watching.go | 108 | ||||
-rw-r--r-- | pkg/gui/files_panel.go | 2 | ||||
-rw-r--r-- | pkg/gui/gui.go | 5 |
3 files changed, 88 insertions, 27 deletions
diff --git a/pkg/gui/file_watching.go b/pkg/gui/file_watching.go index 90fb32639..9ad7f68c4 100644 --- a/pkg/gui/file_watching.go +++ b/pkg/gui/file_watching.go @@ -4,24 +4,103 @@ import ( "os" "path/filepath" + "github.com/davecgh/go-spew/spew" "github.com/fsnotify/fsnotify" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/sirupsen/logrus" ) +// macs for some bizarre reason cap the number of watchable files to 256. +// there's no obvious platform agonstic way to check the situation of the user's +// computer so we're just arbitrarily capping at 200. This isn't so bad because +// file watching is only really an added bonus for faster refreshing. +const MAX_WATCHED_FILES = 200 + +type fileWatcher struct { + Watcher *fsnotify.Watcher + WatchedFilenames []string + Log *logrus.Entry +} + +func NewFileWatcher(log *logrus.Entry) *fileWatcher { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Error(err) + return nil + } + + return &fileWatcher{ + Watcher: watcher, + Log: log, + WatchedFilenames: make([]string, 0, MAX_WATCHED_FILES), + } +} + +func (w *fileWatcher) watchingFilename(filename string) bool { + for _, watchedFilename := range w.WatchedFilenames { + if watchedFilename == filename { + return true + } + } + return false +} + +func (w *fileWatcher) popOldestFilename() { + // shift the last off the array to make way for this one + oldestFilename := w.WatchedFilenames[0] + w.WatchedFilenames = w.WatchedFilenames[1:] + if err := w.Watcher.Remove(oldestFilename); err != nil { + // swallowing errors here because it doesn't really matter if we can't unwatch a file + w.Log.Warn(err) + } +} + +func (w *fileWatcher) watchFilename(filename string) { + w.Log.Warn(filename) + if err := w.Watcher.Add(filename); err != nil { + // swallowing errors here because it doesn't really matter if we can't watch a file + w.Log.Warn(err) + } + + // assume we're watching it now to be safe + w.WatchedFilenames = append(w.WatchedFilenames, filename) +} + +func (w *fileWatcher) addFilesToFileWatcher(files []*commands.File) error { + // watch the files for changes + dirName, err := os.Getwd() + if err != nil { + return err + } + + for _, file := range files { + filename := filepath.Join(dirName, file.Name) + if w.watchingFilename(filename) { + continue + } + if len(w.WatchedFilenames) > MAX_WATCHED_FILES { + w.popOldestFilename() + } + + w.watchFilename(filename) + w.Log.Warn(spew.Sdump(w.WatchedFilenames)) + } + + return nil +} + // NOTE: given that we often edit files ourselves, this may make us end up refreshing files too often // TODO: consider watching the whole directory recursively (could be more expensive) func (gui *Gui) watchFilesForChanges() { - var err error - gui.fileWatcher, err = fsnotify.NewWatcher() - if err != nil { - gui.Log.Error(err) + gui.fileWatcher = NewFileWatcher(gui.Log) + if gui.fileWatcher == nil { return } go func() { for { select { // watch for events - case event := <-gui.fileWatcher.Events: + case event := <-gui.fileWatcher.Watcher.Events: if event.Op == fsnotify.Chmod { // for some reason we pick up chmod events when they don't actually happen continue @@ -37,7 +116,7 @@ func (gui *Gui) watchFilesForChanges() { } // watch for errors - case err := <-gui.fileWatcher.Errors: + case err := <-gui.fileWatcher.Watcher.Errors: if err != nil { gui.Log.Warn(err) } @@ -45,20 +124,3 @@ func (gui *Gui) watchFilesForChanges() { } }() } - -func (gui *Gui) addFilesToFileWatcher(files []*commands.File) error { - // watch the files for changes - dirName, err := os.Getwd() - if err != nil { - return err - } - - for _, file := range files { - if err := gui.fileWatcher.Add(filepath.Join(dirName, file.Name)); err != nil { - // swallowing errors here because it doesn't really matter if we can't watch a file - gui.Log.Warn(err) - } - } - - return nil -} diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index aa2e8592e..82a81654c 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -356,7 +356,7 @@ func (gui *Gui) refreshStateFiles() error { files := gui.GitCommand.GetStatusFiles() gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files) - if err := gui.addFilesToFileWatcher(files); err != nil { + if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil { return err } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index fc24bbd6b..530c00383 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -15,7 +15,6 @@ import ( "strings" "time" - "github.com/fsnotify/fsnotify" "github.com/go-errors/errors" // "strings" @@ -82,7 +81,7 @@ type Gui struct { statusManager *statusManager credentials credentials waitForIntro sync.WaitGroup - fileWatcher *fsnotify.Watcher + fileWatcher *fileWatcher } // for now the staging panel state, unlike the other panel states, is going to be @@ -840,7 +839,7 @@ func (gui *Gui) RunWithSubprocesses() error { } } - gui.fileWatcher.Close() + gui.fileWatcher.Watcher.Close() break } else if err == gui.Errors.ErrSwitchRepo { |