diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 324e425fd..582d98f78 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -49,6 +49,14 @@ type RefresherConfig struct { FetchInterval int `yaml:"fetchInterval" jsonschema:"minimum=0"` } +func (c *RefresherConfig) RefreshIntervalDuration() time.Duration { + return time.Second * time.Duration(c.RefreshInterval) +} + +func (c *RefresherConfig) FetchIntervalDuration() time.Duration { + return time.Second * time.Duration(c.FetchInterval) +} + type GuiConfig struct { // See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color AuthorColors map[string]string `yaml:"authorColors"` diff --git a/pkg/gui/background.go b/pkg/gui/background.go index 6eaf041d4..73964838c 100644 --- a/pkg/gui/background.go +++ b/pkg/gui/background.go @@ -17,6 +17,9 @@ type BackgroundRoutineMgr struct { // we typically want to pause some things that are running like background // file refreshes pauseBackgroundRefreshes bool + + // a channel to trigger an immediate background fetch; we use this when switching repos + triggerFetch chan struct{} } func (self *BackgroundRoutineMgr) PauseBackgroundRefreshes(pause bool) { @@ -40,7 +43,7 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() { if userConfig.Git.AutoRefresh { refreshInterval := userConfig.Refresher.RefreshInterval if refreshInterval > 0 { - go utils.Safe(func() { self.startBackgroundFilesRefresh(refreshInterval) }) + go utils.Safe(self.startBackgroundFilesRefresh) } else { self.gui.c.Log.Errorf( "Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh", @@ -76,6 +79,16 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() { self.gui.waitForIntro.Wait() fetch := func() error { + // Do this on the UI thread so that we don't have to deal with synchronization around the + // access of the repo state. + self.gui.onUIThread(func() error { + // There's a race here, where we might be recording the time stamp for a different repo + // than where the fetch actually ran. It's not very likely though, and not harmful if it + // does happen; guarding against it would be more effort than it's worth. + self.gui.State.LastBackgroundFetchTime = time.Now() + return nil + }) + return self.gui.helpers.AppStatus.WithWaitingStatusImpl(self.gui.Tr.FetchingStatus, func(gocui.Task) error { return self.backgroundFetch() }, nil) @@ -86,41 +99,53 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() { _ = fetch() userConfig := self.gui.UserConfig() - self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, fetch) + self.triggerFetch = self.goEvery(userConfig.Refresher.FetchIntervalDuration(), self.gui.stopChan, fetch) } -func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval int) { +func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh() { self.gui.waitForIntro.Wait() - self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error { + userConfig := self.gui.UserConfig() + self.goEvery(userConfig.Refresher.RefreshIntervalDuration(), self.gui.stopChan, func() error { self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}) return nil }) } -func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) { +// returns a channel that can be used to trigger the callback immediately +func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) chan struct{} { done := make(chan struct{}) + retrigger := make(chan struct{}) go utils.Safe(func() { ticker := time.NewTicker(interval) defer ticker.Stop() + doit := func() { + if self.pauseBackgroundRefreshes { + return + } + self.gui.c.OnWorker(func(gocui.Task) error { + _ = function() + done <- struct{}{} + return nil + }) + // waiting so that we don't bunch up refreshes if the refresh takes longer than the + // interval, or if a retrigger comes in while we're still processing a timer-based one + // (or vice versa) + <-done + } for { select { case <-ticker.C: - if self.pauseBackgroundRefreshes { - continue - } - self.gui.c.OnWorker(func(gocui.Task) error { - _ = function() - done <- struct{}{} - return nil - }) - // waiting so that we don't bunch up refreshes if the refresh takes longer than the interval - <-done + doit() + case <-retrigger: + ticker.Reset(interval) + doit() case <-stop: return } } }) + return retrigger } func (self *BackgroundRoutineMgr) backgroundFetch() (err error) { @@ -134,3 +159,9 @@ func (self *BackgroundRoutineMgr) backgroundFetch() (err error) { return err } + +func (self *BackgroundRoutineMgr) triggerImmediateFetch() { + if self.triggerFetch != nil { + self.triggerFetch <- struct{}{} + } +} diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go index 2f729a7b5..702ed826d 100644 --- a/pkg/gui/controllers.go +++ b/pkg/gui/controllers.go @@ -26,7 +26,7 @@ func (gui *Gui) resetHelpersAndControllers() { helperCommon := gui.c recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon) - reposHelper := helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo) + reposHelper := helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onSwitchToNewRepo) rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon) refsHelper := helpers.NewRefsHelper(helperCommon, rebaseHelper) suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 2727df3c4..a7e17ef9f 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -12,6 +12,7 @@ import ( "sort" "strings" "sync" + "time" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazycore/pkg/boxlayout" @@ -233,7 +234,6 @@ type GuiRepoState struct { Modes *types.Modes SplitMainPanel bool - LimitCommits bool SearchState *types.SearchState StartupStage types.StartupStage // Allows us to not load everything at once @@ -254,6 +254,8 @@ type GuiRepoState struct { ScreenMode types.ScreenMode CurrentPopupOpts *types.CreatePopupPanelOpts + + LastBackgroundFetchTime time.Time } var _ types.IRepoStateAccessor = new(GuiRepoState) @@ -306,6 +308,16 @@ func (self *GuiRepoState) GetSplitMainPanel() bool { return self.SplitMainPanel } +func (gui *Gui) onSwitchToNewRepo(startArgs appTypes.StartArgs, contextKey types.ContextKey) error { + err := gui.onNewRepo(startArgs, contextKey) + if err == nil && gui.UserConfig().Git.AutoFetch && gui.UserConfig().Refresher.FetchInterval > 0 { + if time.Since(gui.State.LastBackgroundFetchTime) > gui.UserConfig().Refresher.FetchIntervalDuration() { + gui.BackgroundRoutineMgr.triggerImmediateFetch() + } + } + return err +} + func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.ContextKey) error { var err error gui.git, err = commands.NewGitCommand(