summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2020-01-08 21:02:01 +1100
committerJesse Duffield <jessedduffield@gmail.com>2020-01-08 21:34:02 +1100
commit1ce5c69cd2ece7bb306f1340df3eac20bd74070e (patch)
tree5cd32b77141b2f570f8d980768d6dcfde8746f6b
parent205d731d7be974abcece5e02cc351bd26101168c (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.go108
-rw-r--r--pkg/gui/files_panel.go2
-rw-r--r--pkg/gui/gui.go5
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 {