mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Switching repos reassigns gui.git and the process cwd; doing it while a foreground git operation (rebase/commit/push/…) is mid-flight would run that operation's remaining commands against the wrong repo. The same applies while the refresh an operation triggers is still settling: its model writes are generation-guarded, but the client-side Then/OnUIThread callbacks that run after it aren't, and shouldn't run against a repo that changed underneath them. Refuse the switch (with a toast) whenever gocui reports a busy foreground task. DispatchSwitchTo carries the guard for the simple callers. The callers that do work before the switch check up front instead, so a refused switch doesn't leave that work half-done: worktree creation checks before creating (its own waiting-status spinner would otherwise make the query busy and refuse its own switch); submodule-enter and the recent-repos menu check before mutating the repo-path stack (pushing / clearing it); and escape-to-parent (SwitchToParentRepo) checks before popping it, so a refusal doesn't consume the entry and strand the user with nowhere to escape back to. All then call the unguarded switchTo, which is safe because their own operation is complete by then.
169 lines
3.7 KiB
Go
169 lines
3.7 KiB
Go
package controllers
|
|
|
|
import (
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
)
|
|
|
|
type QuitActions struct {
|
|
c *ControllerCommon
|
|
}
|
|
|
|
func (self *QuitActions) Quit() error {
|
|
self.c.State().SetRetainOriginalDir(false)
|
|
return self.quitAux()
|
|
}
|
|
|
|
func (self *QuitActions) QuitWithoutChangingDirectory() error {
|
|
self.c.State().SetRetainOriginalDir(true)
|
|
return self.quitAux()
|
|
}
|
|
|
|
func (self *QuitActions) quitAux() error {
|
|
if self.c.State().GetUpdating() {
|
|
return self.confirmQuitDuringUpdate()
|
|
}
|
|
|
|
return self.c.ConfirmIf(self.c.UserConfig().ConfirmOnQuit,
|
|
types.ConfirmOpts{
|
|
Title: "",
|
|
Prompt: self.c.Tr.ConfirmQuit,
|
|
HandleConfirm: func() error {
|
|
return gocui.ErrQuit
|
|
},
|
|
})
|
|
}
|
|
|
|
func (self *QuitActions) confirmQuitDuringUpdate() error {
|
|
self.c.Confirm(types.ConfirmOpts{
|
|
Title: self.c.Tr.ConfirmQuitDuringUpdateTitle,
|
|
Prompt: self.c.Tr.ConfirmQuitDuringUpdate,
|
|
HandleConfirm: func() error {
|
|
return gocui.ErrQuit
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *QuitActions) Escape() error {
|
|
// If you make changes to this function, be sure to update EscapeEnabled and EscapeDescription accordingly.
|
|
|
|
currentContext := self.c.Context().Current()
|
|
|
|
if listContext, ok := currentContext.(types.IListContext); ok {
|
|
if listContext.GetList().IsSelectingRange() {
|
|
listContext.GetList().CancelRangeSelect()
|
|
self.c.PostRefreshUpdate(listContext)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Cancelling searching (as opposed to filtering) is handled by gocui
|
|
if ctx, ok := currentContext.(types.IFilterableContext); ok {
|
|
if ctx.IsFiltering() {
|
|
self.c.Helpers().Search.Cancel()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
parentContext := currentContext.GetParentContext()
|
|
if parentContext != nil {
|
|
// TODO: think about whether this should be marked as a return rather than adding to the stack
|
|
self.c.Context().Push(parentContext, types.OnFocusOpts{})
|
|
return nil
|
|
}
|
|
|
|
for _, mode := range self.c.Helpers().Mode.Statuses() {
|
|
if mode.IsActive() {
|
|
return mode.Reset()
|
|
}
|
|
}
|
|
|
|
if !self.c.State().GetRepoPathStack().IsEmpty() {
|
|
return self.c.Helpers().Repos.SwitchToParentRepo()
|
|
}
|
|
|
|
if self.c.UserConfig().QuitOnTopLevelReturn {
|
|
return self.Quit()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *QuitActions) EscapeEnabled() bool {
|
|
currentContext := self.c.Context().Current()
|
|
|
|
if listContext, ok := currentContext.(types.IListContext); ok {
|
|
if listContext.GetList().IsSelectingRange() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
if ctx, ok := currentContext.(types.IFilterableContext); ok {
|
|
if ctx.IsFiltering() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
parentContext := currentContext.GetParentContext()
|
|
if parentContext != nil {
|
|
return true
|
|
}
|
|
|
|
for _, mode := range self.c.Helpers().Mode.Statuses() {
|
|
if mode.IsActive() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
repoPathStack := self.c.State().GetRepoPathStack()
|
|
if !repoPathStack.IsEmpty() {
|
|
return true
|
|
}
|
|
|
|
if self.c.UserConfig().QuitOnTopLevelReturn {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (self *QuitActions) EscapeDescription() string {
|
|
currentContext := self.c.Context().Current()
|
|
|
|
if listContext, ok := currentContext.(types.IListContext); ok {
|
|
if listContext.GetList().IsSelectingRange() {
|
|
return self.c.Tr.DismissRangeSelect
|
|
}
|
|
}
|
|
|
|
if ctx, ok := currentContext.(types.IFilterableContext); ok {
|
|
if ctx.IsFiltering() {
|
|
return self.c.Tr.ExitFilterMode
|
|
}
|
|
}
|
|
|
|
parentContext := currentContext.GetParentContext()
|
|
if parentContext != nil {
|
|
return self.c.Tr.ExitSubview
|
|
}
|
|
|
|
for _, mode := range self.c.Helpers().Mode.Statuses() {
|
|
if mode.IsActive() {
|
|
return mode.CancelLabel()
|
|
}
|
|
}
|
|
|
|
repoPathStack := self.c.State().GetRepoPathStack()
|
|
if !repoPathStack.IsEmpty() {
|
|
return self.c.Tr.BackToParentRepo
|
|
}
|
|
|
|
if self.c.UserConfig().QuitOnTopLevelReturn {
|
|
return self.c.Tr.Quit
|
|
}
|
|
|
|
return self.c.Tr.Cancel
|
|
}
|