mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Run the repo switch on the UI thread
DispatchSwitchTo wrapped its whole body in WithWaitingStatus, so the switch ran on a worker: it chdirs, reassigns gui.git, and swaps gui.State (in resetState), all of which the UI thread also reads. The generation guard prevents the refresh-in-flight logical corruption but not this pointer data race on gui.State. Run the switch synchronously on the UI thread instead. Every caller is already a UI-thread handler except NewWorktreeCheckout, which must create the worktree (git work) on a worker first; it now dispatches only the switch via OnUIThread. The heavy data loading still happens asynchronously via the refresh that onNewRepo triggers, so the synchronous part is small (a couple of git rev-parse plus direnv). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3103fe97ea
commit
cf7c3d82e6
|
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/jesseduffield/lazygit/pkg/commands/direnv"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
|
|
@ -145,52 +144,53 @@ func (self *ReposHelper) DispatchSwitchToRepo(path string, contextKey types.Cont
|
|||
return self.DispatchSwitchTo(path, self.c.Tr.ErrRepositoryMovedOrDeleted, contextKey)
|
||||
}
|
||||
|
||||
// DispatchSwitchTo switches lazygit to the repository (or worktree) at the
|
||||
// given path. It runs synchronously on the UI thread: the switch swaps
|
||||
// gui.State (in resetState) and reassigns gui.git and the process cwd, all of
|
||||
// which the UI thread also reads, so doing it here rather than on a worker
|
||||
// avoids racing those reads. The heavy data loading is still dispatched
|
||||
// asynchronously by the refresh that onNewRepo kicks off.
|
||||
func (self *ReposHelper) DispatchSwitchTo(path string, errMsg string, contextKey types.ContextKey) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.Switching, func(gocui.Task) error {
|
||||
env.UnsetGitLocationEnvVars()
|
||||
originalPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil
|
||||
env.UnsetGitLocationEnvVars()
|
||||
originalPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := utils.ResolvePlaceholderString(self.c.Tr.ChangingDirectoryTo, map[string]string{"path": path})
|
||||
self.c.LogCommand(msg, false)
|
||||
|
||||
if err := os.Chdir(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
msg := utils.ResolvePlaceholderString(self.c.Tr.ChangingDirectoryTo, map[string]string{"path": path})
|
||||
self.c.LogCommand(msg, false)
|
||||
|
||||
if err := os.Chdir(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if err := commands.VerifyInGitRepo(self.c.OS()); err != nil {
|
||||
if err := os.Chdir(originalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := commands.VerifyInGitRepo(self.c.OS()); err != nil {
|
||||
if err := os.Chdir(originalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
direnvResult := self.logDirenvResult(direnv.Load(self.c.OS().Cmd))
|
||||
|
||||
direnvResult := self.logDirenvResult(direnv.Load(self.c.OS().Cmd))
|
||||
if err := self.recordDirectoryHelper.RecordCurrentDirectory(); err != nil {
|
||||
self.c.Log.Errorf("error recording current directory: %v", err)
|
||||
}
|
||||
|
||||
if err := self.recordDirectoryHelper.RecordCurrentDirectory(); err != nil {
|
||||
self.c.Log.Errorf("error recording current directory: %v", err)
|
||||
}
|
||||
if err := self.onNewRepo(appTypes.StartArgs{}, contextKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.onNewRepo(appTypes.StartArgs{}, contextKey); err != nil {
|
||||
return err
|
||||
}
|
||||
if direnvResult.Blocked {
|
||||
self.promptDirenvApproval(direnvResult.EnvrcPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
if direnvResult.Blocked {
|
||||
self.c.OnUIThread(func() error {
|
||||
self.promptDirenvApproval(direnvResult.EnvrcPath)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return direnvResult.Err
|
||||
})
|
||||
return direnvResult.Err
|
||||
}
|
||||
|
||||
// logDirenvResult writes whatever direnv emitted to the command log and the
|
||||
|
|
|
|||
|
|
@ -432,6 +432,12 @@ func (self *WorktreeHelper) createWorktree(opts git_commands.NewWorktreeOpts, co
|
|||
return err
|
||||
}
|
||||
|
||||
return self.reposHelper.DispatchSwitchTo(opts.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey)
|
||||
// The switch swaps gui.State and must run on the UI thread, but
|
||||
// we're on a worker here (creating the worktree is git work), so
|
||||
// dispatch it rather than calling it directly.
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.reposHelper.DispatchSwitchTo(opts.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey)
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue