mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
Don't refuse a repo switch during a pure refresh
The refreshes on focus-in, right after a repo switch, and after returning from a subprocess are full foreground refreshes, so their tasks kept Busy() true for as long as the slowest scope took — and any switch attempt in that window was refused with the "can't switch" toast. The focus-in one is particularly annoying: focusing lazygit is often precisely what the user does in order to switch repos, and right after regaining focus is when a refresh takes longest. Blocking the switch bought nothing there. The refusal exists for user operations, whose follow-up work (e.g. a Then callback reading the model) isn't covered by the switch-safety guards; but these refreshes merely reload state, and a refresh by itself is now switch-safe: its git commands run against the repo it was started for, and the generation guard drops its updates when the repo changed. We can't just mark them Background, because that flag also decides whether the files refresh lets git take optional locks to persist its refreshed stat cache — worth doing for an attended refresh, and the focus-in refresh (typically running right after external changes) is the case that profits most. So split the two meanings: a new DontBlockRepoSwitch option dispatches the refresh's tasks as background tasks (excluded from Busy()) while keeping the attended optional-locks behavior. Combining it with Then panics, since Then is not generation-guarded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8e045653be
commit
ae095f276b
|
|
@ -91,10 +91,19 @@ func (self *RefreshHelper) RefreshFromWorker(options types.RefreshOptions) {
|
|||
}
|
||||
|
||||
type refreshEnv struct {
|
||||
// whether this is a background refresh (which selects the dispatch variant that
|
||||
// doesn't count towards lazygit being busy)
|
||||
// Whether everything this refresh dispatches uses the background task
|
||||
// variants, which don't count towards lazygit being busy — so the refresh
|
||||
// doesn't block switching repos. Set for refreshes initiated by a
|
||||
// background routine, and for foreground ones that opted in via
|
||||
// RefreshOptions.DontBlockRepoSwitch.
|
||||
background bool
|
||||
|
||||
// Whether the refresh was initiated by an unattended background routine
|
||||
// (RefreshOptions.Background) rather than by user activity. The files
|
||||
// refresh uses this to decide whether git may take optional locks and
|
||||
// persist its refreshed stat cache.
|
||||
backgroundRoutine bool
|
||||
|
||||
// the repo generation captured when the refresh started
|
||||
generation int
|
||||
|
||||
|
|
@ -175,6 +184,13 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr
|
|||
panic("Refresh called from a worker, or RefreshFromWorker called from the UI thread")
|
||||
}
|
||||
|
||||
if options.Then != nil && options.DontBlockRepoSwitch {
|
||||
// Then is not generation-guarded, so if a switch crossed the refresh it
|
||||
// would run against the newly switched-to repo. A refresh carrying a
|
||||
// Then must keep blocking switches.
|
||||
panic("a refresh with a Then callback must not set DontBlockRepoSwitch")
|
||||
}
|
||||
|
||||
// Capture the refresh's baseline once, here at the start: the repo
|
||||
// generation that every scope's bounce is guarded against, and the git
|
||||
// command instance the scopes run their commands through. The two are
|
||||
|
|
@ -186,9 +202,10 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr
|
|||
// against the repo it started in, and the generation guard drops its
|
||||
// writes.
|
||||
env := refreshEnv{
|
||||
background: options.Background,
|
||||
background: options.Background || options.DontBlockRepoSwitch,
|
||||
backgroundRoutine: options.Background,
|
||||
}
|
||||
self.captureOnUIThread(calledFromWorker, options.Background, func() {
|
||||
self.captureOnUIThread(calledFromWorker, env.background, func() {
|
||||
env.generation = self.c.State().GetRepoGeneration()
|
||||
env.git = self.c.Git()
|
||||
})
|
||||
|
|
@ -1276,7 +1293,7 @@ func (self *RefreshHelper) refreshStateFiles(captured capturedFilesState, env re
|
|||
files := env.git.Loaders.FileLoader.
|
||||
GetStatusFiles(git_commands.GetStatusFileOptions{
|
||||
ForceShowUntracked: captured.forceShowUntracked,
|
||||
Background: env.background,
|
||||
Background: env.backgroundRoutine,
|
||||
})
|
||||
|
||||
conflictFileCount := 0
|
||||
|
|
|
|||
|
|
@ -390,7 +390,7 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
|
|||
}
|
||||
|
||||
gui.c.Log.Info("Receiving focus - refreshing")
|
||||
gui.helpers.Refresh.Refresh(types.RefreshOptions{})
|
||||
gui.helpers.Refresh.Refresh(types.RefreshOptions{DontBlockRepoSwitch: true})
|
||||
return reloadErr
|
||||
}
|
||||
|
||||
|
|
@ -1031,7 +1031,7 @@ func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess *oscommands.CmdOb
|
|||
return err
|
||||
}
|
||||
|
||||
gui.c.Refresh(types.RefreshOptions{})
|
||||
gui.c.Refresh(types.RefreshOptions{DontBlockRepoSwitch: true})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1108,7 +1108,7 @@ func (gui *Gui) loadNewRepo() error {
|
|||
return err
|
||||
}
|
||||
|
||||
gui.c.Refresh(types.RefreshOptions{})
|
||||
gui.c.Refresh(types.RefreshOptions{DontBlockRepoSwitch: true})
|
||||
|
||||
if err := gui.os.UpdateWindowTitle(); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -94,4 +94,19 @@ type RefreshOptions struct {
|
|||
// fast. Background refreshes leave the suppression in place: not persisting
|
||||
// the stat-cache is the right trade-off for unattended work.
|
||||
Background bool
|
||||
|
||||
// When true, this foreground refresh does not block switching repos while
|
||||
// it is in flight. A refresh is switch-safe by construction — its git
|
||||
// commands run against the repo it was started for, and the generation
|
||||
// guard drops its model/view updates if the repo changed — but a refresh
|
||||
// triggered by a user operation still blocks switching (its tasks count
|
||||
// towards Busy()), because the operation's follow-up work isn't covered
|
||||
// by those guards. A refresh that merely reloads state (on focus, after a
|
||||
// repo switch, after returning from a subprocess) has no such follow-up,
|
||||
// so it opts in here and a repo switch during it is allowed rather than
|
||||
// refused with a toast.
|
||||
//
|
||||
// Must not be combined with Then: Then is not generation-guarded, so it
|
||||
// would run against the newly switched-to repo.
|
||||
DontBlockRepoSwitch bool
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue