From 1dd7307fde033dae5fececac15810a99e26c3d91 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 16 Jan 2022 14:46:53 +1100 Subject: start moving commit panel handlers into controller more and more move rebase commit refreshing into existing abstraction and more and more WIP and more handling clicks properly fix merge conflicts update cheatsheet lots more preparation to start moving things into controllers WIP better typing expand on remotes controller moving more code into controllers --- docs/keybindings/Keybindings_en.md | 26 +- docs/keybindings/Keybindings_nl.md | 26 +- docs/keybindings/Keybindings_pl.md | 26 +- docs/keybindings/Keybindings_zh.md | 26 +- pkg/cheatsheet/generate.go | 17 +- pkg/commands/git.go | 6 +- pkg/commands/git_commands/common.go | 6 + pkg/commands/git_commands/remote.go | 2 +- pkg/commands/git_commands/sync.go | 10 +- pkg/commands/git_commands/tag.go | 2 +- pkg/commands/git_test.go | 8 +- pkg/commands/oscommands/cmd_obj.go | 17 + pkg/commands/oscommands/cmd_obj_runner.go | 29 +- pkg/gui/app_status_manager.go | 4 +- pkg/gui/arrangement.go | 11 +- pkg/gui/basic_context.go | 26 +- pkg/gui/bisect.go | 219 ------ pkg/gui/branches_panel.go | 265 +++----- pkg/gui/cherry_picking.go | 43 +- pkg/gui/command_log_panel.go | 14 +- pkg/gui/commit_files_panel.go | 110 ++- pkg/gui/commit_message_panel.go | 20 +- pkg/gui/commits_panel.go | 701 +------------------ pkg/gui/confirmation_panel.go | 21 +- pkg/gui/context.go | 104 +-- pkg/gui/context/context.go | 98 +++ pkg/gui/context_config.go | 179 ++--- pkg/gui/controllers/bisect_controller.go | 273 ++++++++ pkg/gui/controllers/controller_common.go | 10 + pkg/gui/controllers/files_controller.go | 737 ++++++++++++++++++++ pkg/gui/controllers/local_commits_controller.go | 783 ++++++++++++++++++++++ pkg/gui/controllers/menu_controller.go | 70 ++ pkg/gui/controllers/remotes_controller.go | 204 ++++++ pkg/gui/controllers/submodules_controller.go | 60 +- pkg/gui/controllers/sync_controller.go | 253 +++++++ pkg/gui/controllers/tags_controller.go | 229 +++++++ pkg/gui/controllers/types.go | 53 +- pkg/gui/controllers/undo_controller.go | 266 ++++++++ pkg/gui/credentials_panel.go | 15 +- pkg/gui/custom_commands.go | 66 +- pkg/gui/diff_context_size.go | 18 +- pkg/gui/diff_context_size_test.go | 50 +- pkg/gui/diffing.go | 26 +- pkg/gui/discard_changes_menu_panel.go | 46 +- pkg/gui/editors.go | 4 +- pkg/gui/extras_panel.go | 17 +- pkg/gui/file_helper.go | 51 ++ pkg/gui/file_watching.go | 4 +- pkg/gui/files_panel.go | 815 +---------------------- pkg/gui/filetree/commit_file_node.go | 4 +- pkg/gui/filetree/file_node.go | 4 +- pkg/gui/filtering.go | 28 +- pkg/gui/filtering_menu_panel.go | 14 +- pkg/gui/find_suggestions.go | 190 ------ pkg/gui/git_flow.go | 20 +- pkg/gui/global_handlers.go | 38 +- pkg/gui/gpg.go | 18 +- pkg/gui/gui.go | 241 +++++-- pkg/gui/gui_common.go | 53 ++ pkg/gui/information_panel.go | 10 +- pkg/gui/keybindings.go | 751 ++++++--------------- pkg/gui/layout.go | 37 +- pkg/gui/line_by_line_panel.go | 8 +- pkg/gui/list_context.go | 116 ++-- pkg/gui/list_context_config.go | 204 ++---- pkg/gui/main_panels.go | 2 +- pkg/gui/menu_panel.go | 25 +- pkg/gui/merge_panel.go | 47 +- pkg/gui/misc.go | 19 + pkg/gui/modes.go | 16 +- pkg/gui/options_menu_panel.go | 4 +- pkg/gui/patch_building_panel.go | 24 +- pkg/gui/patch_options_panel.go | 72 +- pkg/gui/popup/popup_handler.go | 26 + pkg/gui/pty.go | 4 +- pkg/gui/pull_request_menu_panel.go | 20 +- pkg/gui/quitting.go | 10 +- pkg/gui/rebase_options_panel.go | 46 +- pkg/gui/recent_repos_panel.go | 27 +- pkg/gui/ref_helper.go | 137 ++++ pkg/gui/reflog_panel.go | 31 +- pkg/gui/remote_branches_panel.go | 36 +- pkg/gui/remotes_panel.go | 136 +--- pkg/gui/reset_menu_panel.go | 53 -- pkg/gui/searching.go | 4 +- pkg/gui/staging_panel.go | 30 +- pkg/gui/stash_panel.go | 76 +-- pkg/gui/status_panel.go | 22 +- pkg/gui/style/style_test.go | 15 +- pkg/gui/sub_commits_panel.go | 31 +- pkg/gui/submodules_panel.go | 6 +- pkg/gui/suggestions_helper.go | 206 ++++++ pkg/gui/tags_panel.go | 106 +-- pkg/gui/tasks_adapter.go | 8 +- pkg/gui/types/common_commands.go | 7 + pkg/gui/types/context.go | 87 +++ pkg/gui/types/keybindings.go | 9 + pkg/gui/types/refresh.go | 1 + pkg/gui/undoing.go | 208 ------ pkg/gui/updates.go | 14 +- pkg/gui/view_helpers.go | 96 ++- pkg/gui/whitespace-toggle.go | 6 +- pkg/gui/working_tree_helper.go | 50 ++ pkg/gui/workspace_reset_options_panel.go | 62 +- pkg/i18n/english.go | 2 + test/integration/commit/expected/.git_keep/index | Bin 425 -> 425 bytes 106 files changed, 5226 insertions(+), 4357 deletions(-) delete mode 100644 pkg/gui/bisect.go create mode 100644 pkg/gui/context/context.go create mode 100644 pkg/gui/controllers/bisect_controller.go create mode 100644 pkg/gui/controllers/controller_common.go create mode 100644 pkg/gui/controllers/files_controller.go create mode 100644 pkg/gui/controllers/local_commits_controller.go create mode 100644 pkg/gui/controllers/menu_controller.go create mode 100644 pkg/gui/controllers/remotes_controller.go create mode 100644 pkg/gui/controllers/sync_controller.go create mode 100644 pkg/gui/controllers/tags_controller.go create mode 100644 pkg/gui/controllers/undo_controller.go create mode 100644 pkg/gui/file_helper.go delete mode 100644 pkg/gui/find_suggestions.go create mode 100644 pkg/gui/gui_common.go create mode 100644 pkg/gui/misc.go create mode 100644 pkg/gui/ref_helper.go delete mode 100644 pkg/gui/reset_menu_panel.go create mode 100644 pkg/gui/suggestions_helper.go create mode 100644 pkg/gui/types/common_commands.go create mode 100644 pkg/gui/types/context.go delete mode 100644 pkg/gui/undoing.go create mode 100644 pkg/gui/working_tree_helper.go diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index 43e586897..09a933f81 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct ## Commits Panel (Commits)
-  ctrl+l: open log menu
+  c: copy commit (cherry-pick)
+  ctrl+o: copy commit SHA to clipboard
+  C: copy commit range (cherry-pick)
+  v: paste commits (cherry-pick)
+  n: create new branch off of commit
+  ctrl+r: reset cherry-picked (copied) commits selection
   s: squash down
+  f: fixup commit
   r: reword commit
   R: reword commit with editor
-  g: reset to this commit
-  f: fixup commit
+  d: delete commit
+  e: edit commit
+  p: pick commit (when mid-rebase)
   F: create fixup commit for this commit
   S: squash all 'fixup!' commits above selected commit (autosquash)
-  d: delete commit
   ctrl+j: move commit down one
   ctrl+k: move commit up one
-  e: edit commit
   A: amend commit with staged changes
-  p: pick commit (when mid-rebase)
   t: revert commit
-  c: copy commit (cherry-pick)
-  ctrl+o: copy commit SHA to clipboard
-  C: copy commit range (cherry-pick)
-  v: paste commits (cherry-pick)
+  ctrl+l: open log menu
+  g: reset to this commit
   enter: view commit's files
   space: checkout commit
-  n: create new branch off of commit
   T: tag commit
-  ctrl+r: reset cherry-picked (copied) commits selection
   ctrl+y: copy commit message to clipboard
   o: open commit in browser
   b: view bisect options
@@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   w: commit changes without pre-commit hook
   A: amend last commit
   C: commit changes using git editor
-  space: toggle staged
   d: view 'discard changes' options
   e: edit file
   o: open file
@@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   `: toggle file tree view
   M: open external merge tool (git mergetool)
   ctrl+w: Toggle whether or not whitespace changes are shown in the diff view
+  space: toggle staged
 
## Files Panel (Submodules) diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index 7d5b8f78c..5e0d62fac 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct ## Commits Paneel (Commits)
-  ctrl+l: open log menu
+  c: kopieer commit (cherry-pick)
+  ctrl+o: kopieer commit SHA naar klembord
+  C: kopieer commit reeks (cherry-pick)
+  v: plak commits (cherry-pick)
+  n: creëer nieuwe branch van commit
+  ctrl+r: reset cherry-picked (gekopieerde) commits selectie
   s: squash beneden
+  f: Fixup commit
   r: hernoem commit
   R: hernoem commit met editor
-  g: reset naar deze commit
-  f: Fixup commit
+  d: verwijder commit
+  e: wijzig commit
+  p: kies commit (wanneer midden in rebase)
   F: creëer fixup commit voor deze commit
   S: squash bovenstaande commits
-  d: verwijder commit
   ctrl+j: verplaats commit 1 naar beneden
   ctrl+k: verplaats commit 1 naar boven
-  e: wijzig commit
   A: wijzig commit met staged veranderingen
-  p: kies commit (wanneer midden in rebase)
   t: commit ongedaan maken
-  c: kopieer commit (cherry-pick)
-  ctrl+o: kopieer commit SHA naar klembord
-  C: kopieer commit reeks (cherry-pick)
-  v: plak commits (cherry-pick)
+  ctrl+l: open log menu
+  g: reset naar deze commit
   enter: bekijk gecommite bestanden
   space: checkout commit
-  n: creëer nieuwe branch van commit
   T: tag commit
-  ctrl+r: reset cherry-picked (gekopieerde) commits selectie
   ctrl+y: kopieer commit bericht naar klembord
   o: open commit in browser
   b: view bisect options
@@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   w: commit veranderingen zonder pre-commit hook
   A: wijzig laatste commit
   C: commit veranderingen met de git editor
-  space: toggle staged
   d: bekijk 'veranderingen ongedaan maken' opties
   e: verander bestand
   o: open bestand
@@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   `: toggle bestandsboom weergave
   M: open external merge tool (git mergetool)
   ctrl+w: Toggle whether or not whitespace changes are shown in the diff view
+  space: toggle staged
 
## Bestanden Paneel (Submodules) diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index 95e713826..afdcdebcd 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct ## Commity Panel (Commity)
-  ctrl+l: open log menu
+  c: kopiuj commit (przebieranie)
+  ctrl+o: copy commit SHA to clipboard
+  C: kopiuj zakres commitów (przebieranie)
+  v: wklej commity (przebieranie)
+  n: create new branch off of commit
+  ctrl+r: reset cherry-picked (copied) commits selection
   s: ściśnij
+  f: napraw commit
   r: zmień nazwę commita
   R: zmień nazwę commita w edytorze
-  g: zresetuj do tego commita
-  f: napraw commit
+  d: usuń commit
+  e: edytuj commit
+  p: wybierz commit (podczas zmiany bazy)
   F: utwórz commit naprawczy dla tego commita
   S: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
-  d: usuń commit
   ctrl+j: przenieś commit 1 w dół
   ctrl+k: przenieś commit 1 w górę
-  e: edytuj commit
   A: popraw commit zmianami z poczekalni
-  p: wybierz commit (podczas zmiany bazy)
   t: odwróć commit
-  c: kopiuj commit (przebieranie)
-  ctrl+o: copy commit SHA to clipboard
-  C: kopiuj zakres commitów (przebieranie)
-  v: wklej commity (przebieranie)
+  ctrl+l: open log menu
+  g: zresetuj do tego commita
   enter: przeglądaj pliki commita
   space: checkout commit
-  n: create new branch off of commit
   T: tag commit
-  ctrl+r: reset cherry-picked (copied) commits selection
   ctrl+y: copy commit message to clipboard
   o: open commit in browser
   b: view bisect options
@@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   w: zatwierdź zmiany bez skryptu pre-commit
   A: Zmień ostatni commit
   C: Zatwierdź zmiany używając edytora
-  space: przełącz stan poczekalni
   d: pokaż opcje porzucania zmian
   e: edytuj plik
   o: otwórz plik
@@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   `: toggle file tree view
   M: open external merge tool (git mergetool)
   ctrl+w: Toggle whether or not whitespace changes are shown in the diff view
+  space: przełącz stan poczekalni
 
## Pliki Panel (Submodules) diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md index 42a4fd1c7..61b2c287f 100644 --- a/docs/keybindings/Keybindings_zh.md +++ b/docs/keybindings/Keybindings_zh.md @@ -123,30 +123,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct ## 提交 面板 (提交)
-  ctrl+l: open log menu
+  c: 复制提交(拣选)
+  ctrl+o: 将提交的 SHA 复制到剪贴板
+  C: 复制提交范围(拣选)
+  v: 粘贴提交(拣选)
+  n: 从提交创建新分支
+  ctrl+r: 重置已拣选(复制)的提交
   s: 向下压缩
+  f: 修正提交(fixup)
   r: 改写提交
   R: 使用编辑器重命名提交
-  g: 重置为此提交
-  f: 修正提交(fixup)
+  d: 删除提交
+  e: 编辑提交
+  p: 选择提交(变基过程中)
   F: 为此提交创建修正
   S: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
-  d: 删除提交
   ctrl+j: 下移提交
   ctrl+k: 上移提交
-  e: 编辑提交
   A: 用已暂存的更改来修补提交
-  p: 选择提交(变基过程中)
   t: 还原提交
-  c: 复制提交(拣选)
-  ctrl+o: 将提交的 SHA 复制到剪贴板
-  C: 复制提交范围(拣选)
-  v: 粘贴提交(拣选)
+  ctrl+l: open log menu
+  g: 重置为此提交
   enter: 查看提交的文件
   space: 检出提交
-  n: 从提交创建新分支
   T: 标签提交
-  ctrl+r: 重置已拣选(复制)的提交
   ctrl+y: 将提交消息复制到剪贴板
   o: open commit in browser
   b: view bisect options
@@ -183,7 +183,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   w: 提交更改而无需预先提交钩子
   A: 修补最后一次提交
   C: 提交更改(使用编辑器编辑提交信息)
-  space: 切换暂存状态
   d: 查看'放弃更改‘选项
   e: 编辑文件
   o: 打开文件
@@ -200,6 +199,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
   `: 切换文件树视图
   M: 打开合并工具
   ctrl+w: 切换是否在差异视图中显示空白更改
+  space: 切换暂存状态
 
## 文件 面板 (子模块) diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go index 546239df5..3d1b5efcf 100644 --- a/pkg/cheatsheet/generate.go +++ b/pkg/cheatsheet/generate.go @@ -17,13 +17,14 @@ import ( "github.com/jesseduffield/lazygit/pkg/app" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui" + "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/integration" ) type bindingSection struct { title string - bindings []*gui.Binding + bindings []*types.Binding } func CommandToRun() string { @@ -113,7 +114,7 @@ func formatTitle(title string) string { return fmt.Sprintf("\n## %s\n\n", title) } -func formatBinding(binding *gui.Binding) string { +func formatBinding(binding *types.Binding) string { if binding.Alternative != "" { return fmt.Sprintf(" %s: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, binding.Alternative) } @@ -130,7 +131,7 @@ func getBindingSections(mApp *app.App) []*bindingSection { title string } - contextAndViewBindingMap := map[contextAndViewType][]*gui.Binding{} + contextAndViewBindingMap := map[contextAndViewType][]*types.Binding{} outer: for _, binding := range bindings { @@ -138,7 +139,7 @@ outer: key := contextAndViewType{subtitle: "", title: "navigation"} existing := contextAndViewBindingMap[key] if existing == nil { - contextAndViewBindingMap[key] = []*gui.Binding{binding} + contextAndViewBindingMap[key] = []*types.Binding{binding} } else { for _, navBinding := range contextAndViewBindingMap[key] { if navBinding.Description == binding.Description { @@ -162,7 +163,7 @@ outer: key := contextAndViewType{subtitle: context, title: binding.ViewName} existing := contextAndViewBindingMap[key] if existing == nil { - contextAndViewBindingMap[key] = []*gui.Binding{binding} + contextAndViewBindingMap[key] = []*types.Binding{binding} } else { contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding) } @@ -171,7 +172,7 @@ outer: type groupedBindingsType struct { contextAndView contextAndViewType - bindings []*gui.Binding + bindings []*types.Binding } groupedBindings := make([]groupedBindingsType, len(contextAndViewBindingMap)) @@ -227,7 +228,7 @@ outer: return bindingSections } -func addBinding(title string, bindingSections []*bindingSection, binding *gui.Binding) []*bindingSection { +func addBinding(title string, bindingSections []*bindingSection, binding *types.Binding) []*bindingSection { if binding.Description == "" && binding.Alternative == "" { return bindingSections } @@ -241,7 +242,7 @@ func addBinding(title string, bindingSections []*bindingSection, binding *gui.Bi section := &bindingSection{ title: title, - bindings: []*gui.Binding{binding}, + bindings: []*types.Binding{binding}, } return append(bindingSections, section) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index f6812e254..3880e0dfc 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strings" + "sync" "github.com/go-errors/errors" @@ -56,6 +57,7 @@ func NewGitCommand( cmn *common.Common, osCommand *oscommands.OSCommand, gitConfig git_config.IGitConfig, + syncMutex *sync.Mutex, ) (*GitCommand, error) { if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil { return nil, err @@ -77,6 +79,7 @@ func NewGitCommand( gitConfig, dotGitDir, repo, + syncMutex, ), nil } @@ -86,6 +89,7 @@ func NewGitCommandAux( gitConfig git_config.IGitConfig, dotGitDir string, repo *gogit.Repository, + syncMutex *sync.Mutex, ) *GitCommand { cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd) @@ -95,7 +99,7 @@ func NewGitCommandAux( // on the one struct. // common ones are: cmn, osCommand, dotGitDir, configCommands configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo) - gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands) + gitCommon := git_commands.NewGitCommon(cmn, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex) statusCommands := git_commands.NewStatusCommands(gitCommon) fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands) diff --git a/pkg/commands/git_commands/common.go b/pkg/commands/git_commands/common.go index a045be75a..85f0d2118 100644 --- a/pkg/commands/git_commands/common.go +++ b/pkg/commands/git_commands/common.go @@ -1,6 +1,8 @@ package git_commands import ( + "sync" + gogit "github.com/jesseduffield/go-git/v5" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/common" @@ -13,6 +15,8 @@ type GitCommon struct { dotGitDir string repo *gogit.Repository config *ConfigCommands + // mutex for doing things like push/pull/fetch + syncMutex *sync.Mutex } func NewGitCommon( @@ -22,6 +26,7 @@ func NewGitCommon( dotGitDir string, repo *gogit.Repository, config *ConfigCommands, + syncMutex *sync.Mutex, ) *GitCommon { return &GitCommon{ Common: cmn, @@ -30,5 +35,6 @@ func NewGitCommon( dotGitDir: dotGitDir, repo: repo, config: config, + syncMutex: syncMutex, } } diff --git a/pkg/commands/git_commands/remote.go b/pkg/commands/git_commands/remote.go index 3116c764a..1245a8cf0 100644 --- a/pkg/commands/git_commands/remote.go +++ b/pkg/commands/git_commands/remote.go @@ -40,7 +40,7 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error { command := fmt.Sprintf("git push %s --delete %s", self.cmd.Quote(remoteName), self.cmd.Quote(branchName)) - return self.cmd.New(command).PromptOnCredentialRequest().Run() + return self.cmd.New(command).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } // CheckRemoteBranchExists Returns remote branch diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go index 8a6933522..fb1aa9648 100644 --- a/pkg/commands/git_commands/sync.go +++ b/pkg/commands/git_commands/sync.go @@ -47,7 +47,7 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch) } - cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest() + cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex) return cmdObj, nil } @@ -83,7 +83,7 @@ func (self *SyncCommands) Fetch(opts FetchOptions) error { } else { cmdObj.PromptOnCredentialRequest() } - return cmdObj.Run() + return cmdObj.WithMutex(self.syncMutex).Run() } type PullOptions struct { @@ -108,15 +108,15 @@ func (self *SyncCommands) Pull(opts PullOptions) error { // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user // has 'pull.rebase = interactive' configured. - return self.cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().Run() + return self.cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error { cmdStr := fmt.Sprintf("git fetch %s %s:%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName)) - return self.cmd.New(cmdStr).PromptOnCredentialRequest().Run() + return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } func (self *SyncCommands) FetchRemote(remoteName string) error { cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName)) - return self.cmd.New(cmdStr).PromptOnCredentialRequest().Run() + return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go index 94b0d8ac1..5abad0dc5 100644 --- a/pkg/commands/git_commands/tag.go +++ b/pkg/commands/git_commands/tag.go @@ -27,5 +27,5 @@ func (self *TagCommands) Delete(tagName string) error { } func (self *TagCommands) Push(remoteName string, tagName string) error { - return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().Run() + return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 684696a8c..77436130d 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -3,6 +3,7 @@ package commands import ( "fmt" "os" + "sync" "testing" "time" @@ -211,7 +212,12 @@ func TestNewGitCommand(t *testing.T) { s := s t.Run(s.testName, func(t *testing.T) { s.setup() - s.test(NewGitCommand(utils.NewDummyCommon(), oscommands.NewDummyOSCommand(), git_config.NewFakeGitConfig(nil))) + s.test( + NewGitCommand(utils.NewDummyCommon(), + oscommands.NewDummyOSCommand(), + git_config.NewFakeGitConfig(nil), + &sync.Mutex{}, + )) }) } } diff --git a/pkg/commands/oscommands/cmd_obj.go b/pkg/commands/oscommands/cmd_obj.go index 3e55359de..7960bfa99 100644 --- a/pkg/commands/oscommands/cmd_obj.go +++ b/pkg/commands/oscommands/cmd_obj.go @@ -2,6 +2,7 @@ package oscommands import ( "os/exec" + "sync" ) // A command object is a general way to represent a command to be run on the @@ -50,6 +51,9 @@ type ICmdObj interface { PromptOnCredentialRequest() ICmdObj FailOnCredentialRequest() ICmdObj + WithMutex(mutex *sync.Mutex) ICmdObj + Mutex() *sync.Mutex + GetCredentialStrategy() CredentialStrategy } @@ -70,6 +74,9 @@ type CmdObj struct { // if set to true, it means we might be asked to enter a username/password by this command. credentialStrategy CredentialStrategy + + // can be set so that we don't run certain commands simultaneously + mutex *sync.Mutex } type CredentialStrategy int @@ -132,6 +139,16 @@ func (self *CmdObj) IgnoreEmptyError() ICmdObj { return self } +func (self *CmdObj) Mutex() *sync.Mutex { + return self.mutex +} + +func (self *CmdObj) WithMutex(mutex *sync.Mutex) ICmdObj { + self.mutex = mutex + + return self +} + func (self *CmdObj) ShouldIgnoreEmptyError() bool { return self.ignoreEmptyError } diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go index e1a38d80f..9522bc627 100644 --- a/pkg/commands/oscommands/cmd_obj_runner.go +++ b/pkg/commands/oscommands/cmd_obj_runner.go @@ -34,6 +34,11 @@ type cmdObjRunner struct { var _ ICmdObjRunner = &cmdObjRunner{} func (self *cmdObjRunner) Run(cmdObj ICmdObj) error { + if cmdObj.Mutex() != nil { + cmdObj.Mutex().Lock() + defer cmdObj.Mutex().Unlock() + } + if cmdObj.GetCredentialStrategy() != NONE { return self.runWithCredentialHandling(cmdObj) } @@ -42,27 +47,36 @@ func (self *cmdObjRunner) Run(cmdObj ICmdObj) error { return self.runAndStream(cmdObj) } - _, err := self.RunWithOutput(cmdObj) + _, err := self.RunWithOutputAux(cmdObj) return err } func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) { - if cmdObj.ShouldStreamOutput() { - err := self.runAndStream(cmdObj) + if cmdObj.Mutex() != nil { + cmdObj.Mutex().Lock() + defer cmdObj.Mutex().Unlock() + } + + if cmdObj.GetCredentialStrategy() != NONE { + err := self.runWithCredentialHandling(cmdObj) // for now we're not capturing output, just because it would take a little more // effort and there's currently no use case for it. Some commands call RunWithOutput // but ignore the output, hence why we've got this check here. return "", err } - if cmdObj.GetCredentialStrategy() != NONE { - err := self.runWithCredentialHandling(cmdObj) + if cmdObj.ShouldStreamOutput() { + err := self.runAndStream(cmdObj) // for now we're not capturing output, just because it would take a little more // effort and there's currently no use case for it. Some commands call RunWithOutput // but ignore the output, hence why we've got this check here. return "", err } + return self.RunWithOutputAux(cmdObj) +} + +func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) { self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand") if cmdObj.ShouldLog() { @@ -77,6 +91,11 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) { } func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error { + if cmdObj.Mutex() != nil { + cmdObj.Mutex().Lock() + defer cmdObj.Mutex().Unlock() + } + if cmdObj.GetCredentialStrategy() != NONE { return errors.New("cannot call RunAndProcessLines with credential strategy. If you're seeing this then a contributor to Lazygit has accidentally called this method! Please raise an issue") } diff --git a/pkg/gui/app_status_manager.go b/pkg/gui/app_status_manager.go index 825bb8801..4c32f79b5 100644 --- a/pkg/gui/app_status_manager.go +++ b/pkg/gui/app_status_manager.go @@ -83,7 +83,7 @@ func (m *statusManager) getStatusString() string { return topStatus.message } -func (gui *Gui) raiseToast(message string) { +func (gui *Gui) toast(message string) { gui.statusManager.addToastStatus(message) gui.renderAppStatus() @@ -119,7 +119,7 @@ func (gui *Gui) withWaitingStatus(message string, f func() error) error { if err := f(); err != nil { gui.OnUIThread(func() error { - return gui.PopupHandler.Error(err) + return gui.c.Error(err) }) } }) diff --git a/pkg/gui/arrangement.go b/pkg/gui/arrangement.go index fa2e7f29d..944391d75 100644 --- a/pkg/gui/arrangement.go +++ b/pkg/gui/arrangement.go @@ -2,6 +2,7 @@ package gui import ( "github.com/jesseduffield/lazygit/pkg/gui/boxlayout" + "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -44,7 +45,7 @@ func (gui *Gui) getMidSectionWeights() (int, int) { currentWindow := gui.currentWindow() // we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4 - sidePanelWidthRatio := gui.UserConfig.Gui.SidePanelWidth + sidePanelWidthRatio := gui.c.UserConfig.Gui.SidePanelWidth // we could make this better by creating ratios like 2:3 rather than always 1:something mainSectionWeight := int(1/sidePanelWidthRatio) - 1 sideSectionWeight := 1 @@ -115,7 +116,7 @@ func (gui *Gui) splitMainPanelSideBySide() bool { return false } - mainPanelSplitMode := gui.UserConfig.Gui.MainPanelSplitMode + mainPanelSplitMode := gui.c.UserConfig.Gui.MainPanelSplitMode width, height := gui.g.Size() switch mainPanelSplitMode { @@ -143,7 +144,7 @@ func (gui *Gui) getExtrasWindowSize(screenHeight int) int { } else if screenHeight < 40 { baseSize = 1 } else { - baseSize = gui.UserConfig.Gui.CommandLogSize + baseSize = gui.c.UserConfig.Gui.CommandLogSize } frameSize := 2 @@ -259,7 +260,7 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box { fullHeightBox("stash"), } } else if height >= 28 { - accordionMode := gui.UserConfig.Gui.ExpandFocusedSidePanel + accordionMode := gui.c.UserConfig.Gui.ExpandFocusedSidePanel accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box { if accordionMode && defaultBox.Window == currentWindow { return &boxlayout.Box{ @@ -320,7 +321,7 @@ func (gui *Gui) currentSideWindowName() string { reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx context := gui.State.ContextManager.ContextStack[reversedIdx] - if context.GetKind() == SIDE_CONTEXT { + if context.GetKind() == types.SIDE_CONTEXT { return context.GetWindowName() } } diff --git a/pkg/gui/basic_context.go b/pkg/gui/basic_context.go index 1db80ee4a..1043cca89 100644 --- a/pkg/gui/basic_context.go +++ b/pkg/gui/basic_context.go @@ -1,22 +1,28 @@ package gui +import ( + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + type BasicContext struct { - OnFocus func(opts ...OnFocusOpts) error + OnFocus func(opts ...types.OnFocusOpts) error OnFocusLost func() error OnRender func() error // this is for pushing some content to the main view - OnRenderToMain func(opts ...OnFocusOpts) error - Kind ContextKind - Key ContextKey + OnRenderToMain func(opts ...types.OnFocusOpts) error + Kind types.ContextKind + Key types.ContextKey ViewName string WindowName string OnGetOptionsMap func() map[string]string - ParentContext Context + ParentContext types.Context // we can't know on the calling end whether a Context is actually a nil value without reflection, so we're storing this flag here to tell us. There has got to be a better way around this hasParent bool } +var _ types.Context = &BasicContext{} + func (self *BasicContext) GetOptionsMap() map[string]string { if self.OnGetOptionsMap != nil { return self.OnGetOptionsMap() @@ -24,12 +30,12 @@ func (self *BasicContext) GetOptionsMap() map[string]string { return nil } -func (self *BasicContext) SetParentContext(context Context) { +func (self *BasicContext) SetParentContext(context types.Context) { self.ParentContext = context self.hasParent = true } -func (self *BasicContext) GetParentContext() (Context, bool) { +func (self *BasicContext) GetParentContext() (types.Context, bool) { return self.ParentContext, self.hasParent } @@ -59,7 +65,7 @@ func (self *BasicContext) GetViewName() string { return self.ViewName } -func (self *BasicContext) HandleFocus(opts ...OnFocusOpts) error { +func (self *BasicContext) HandleFocus(opts ...types.OnFocusOpts) error { if self.OnFocus != nil { if err := self.OnFocus(opts...); err != nil { return err @@ -90,10 +96,10 @@ func (self *BasicContext) HandleRenderToMain() error { return nil } -func (self *BasicContext) GetKind() ContextKind { +func (self *BasicContext) GetKind() types.ContextKind { return self.Kind } -func (self *BasicContext) GetKey() ContextKey { +func (self *BasicContext) GetKey() types.ContextKey { return self.Key } diff --git a/pkg/gui/bisect.go b/pkg/gui/bisect.go deleted file mode 100644 index 5c46460ac..000000000 --- a/pkg/gui/bisect.go +++ /dev/null @@ -1,219 +0,0 @@ -package gui - -import ( - "fmt" - "strings" - - "github.com/jesseduffield/lazygit/pkg/commands/git_commands" - "github.com/jesseduffield/lazygit/pkg/commands/models" -) - -func (gui *Gui) handleOpenBisectMenu() error { - if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { - return err - } - - // no shame in getting this directly rather than using the cached value - // given how cheap it is to obtain - info := gui.Git.Bisect.GetInfo() - commit := gui.getSelectedLocalCommit() - if info.Started() { - return gui.openMidBisectMenu(info, commit) - } else { - return gui.openStartBisectMenu(info, commit) - } -} - -func (gui *Gui) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error { - // if there is not yet a 'current' bisect commit, or if we have - // selected the current commit, we need to jump to the next 'current' commit - // after we perform a bisect action. The reason we don't unconditionally jump - // is that sometimes the user will want to go and mark a few commits as skipped - // in a row and they wouldn't want to be jumped back to the current bisect - // commit each time. - // Originally we were allowing the user to, from the bisect menu, select whether - // they were talking about the selected commit or the current bisect commit, - // and that was a bit confusing (and required extra keypresses). - selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha - // we need to wait to reselect if our bisect commits aren't ancestors of our 'start' - // ref, because we'll be reloading our commits in that case. - waitToReselect := selectCurrentAfter && !gui.Git.Bisect.ReachableFromStart(info) - - menuItems := []*menuItem{ - { - displayString: fmt.Sprintf(gui.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()), - onPress: func() error { - gui.logAction(gui.Tr.Actions.BisectMark) - if err := gui.Git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil { - return gui.surfaceError(err) - } - - return gui.afterMark(selectCurrentAfter, waitToReselect) - }, - }, - { - displayString: fmt.Sprintf(gui.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()), - onPress: func() error { - gui.logAction(gui.Tr.Actions.BisectMark) - if err := gui.Git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil { - return gui.surfaceError(err) - } - - return gui.afterMark(selectCurrentAfter, waitToReselect) - }, - }, - { - displayString: fmt.Sprintf(gui.Tr.Bisect.Skip, commit.ShortSha()), - onPress: func() error { - gui.logAction(gui.Tr.Actions.BisectSkip) - if err := gui.Git.Bisect.Skip(commit.Sha); err != nil { - return gui.surfaceError(err) - } - - return gui.afterMark(selectCurrentAfter, waitToReselect) - }, - }, - { - displayString: gui.Tr.Bisect.ResetOption, - onPress: func() error { - return gui.resetBisect() - }, - }, - } - - return gui.createMenu( - gui.Tr.Bisect.BisectMenuTitle, - menuItems, - createMenuOptions{showCancel: true}, - ) -} - -func (gui *Gui) openStartBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error { - return gui.createMenu( - gui.Tr.Bisect.BisectMenuTitle, - []*menuItem{ - { - displayString: fmt.Sprintf(gui.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()), - onPress: func() error { - gui.logAction(gui.Tr.Actions.StartBisect) - if err := gui.Git.Bisect.Start(); err != nil { - return gui.surfaceError(err) - } - - if err := gui.Git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil { - return gui.surfaceError(err) - } - - return gui.postBisectCommandRefresh() - }, - }, - { - displayString: fmt.Sprintf(gui.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()), - onPress: func() error { - gui.logAction(gui.Tr.Actions.StartBisect) - if err := gui.Git.Bisect.Start(); err != nil { - return gui.surfaceError(err) - } - - if err := gui.Git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil { - return gui.surfaceError(err) - } - - return gui.postBisectCommandRefresh() - }, - }, - }, - createMenuOptions{showCancel: true}, - ) -} - -func (gui *Gui) resetBisect() error { - return gui.ask(askOpts{ - title: gui.Tr.Bisect.ResetTitle, - prompt: gui.Tr.Bisect.ResetPrompt, - handleConfirm: func() error { - gui.logAction(gui.Tr.Actions.ResetBisect) - if err := gui.Git.Bisect.Reset(); err != nil { - return gui.surfaceError(err) - } - - return gui.postBisectCommandRefresh() - }, - }) -} - -func (gui *Gui) showBisectCompleteMessage(candidateShas []string) error { - prompt := gui.Tr.Bisect.CompletePrompt - if len(candidateShas) > 1 { - prompt = gui.Tr.Bisect.CompletePromptIndeterminate - } - - formattedCommits, err := gui.Git.Commit.GetCommitsOneline(candidateShas) - if err != nil { - return gui.surfaceError(err) - } - - return gui.ask(askOpts{ - title: gui.Tr.Bisect.CompleteTitle, - prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)), - handleConfirm: func() error { - gui.logAction(gui.Tr.Actions.ResetBisect) - if err := gui.Git.Bisect.Reset(); err != nil { - return gui.surfaceError(err) - } - - return gui.postBisectCommandRefresh() - }, - }) -} - -func (gui *Gui) afterMark(selectCurrent bool, waitToReselect bool) error { - done, candidateShas, err := gui.Git.Bisect.IsDone() - if err != nil { - return gui.surfaceError(err) - } - - if err := gui.afterBisectMarkRefresh(selectCurrent, waitToReselect); err != nil { - return gui.surfaceError(err) - } - - if done { - return gui.showBisectCompleteMessage(candidateShas) - } - - return nil -} - -func (gui *Gui) postBisectCommandRefresh() error { - return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{}}) -} - -func (gui *Gui) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error { - selectFn := func() { - if selectCurrent { - gui.selectCurrentBisectCommit() - } - } - - if waitToReselect { - return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{}, then: selectFn}) - } else { - selectFn() - - return gui.postBisectCommandRefresh() - } -} - -func (gui *Gui) selectCurrentBisectCommit() { - info := gui.Git.Bisect.GetInfo() - if info.GetCurrentSha() != "" { - // find index of commit with that sha, move cursor to that. - for i, commit := range gui.State.Commits { - if commit.Sha == info.GetCurrentSha() { - gui.State.Contexts.BranchCommits.GetPanelState().SetSelectedLineIdx(i) - _ = gui.State.Contexts.BranchCommits.HandleFocus() - break - } - } - } -} diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index dca0dc8f0..18094b297 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -7,6 +7,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/controllers" "github.com/jesseduffield/lazygit/pkg/gui/popup" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" @@ -31,9 +32,9 @@ func (gui *Gui) branchesRenderToMain() error { var task updateTask branch := gui.getSelectedBranch() if branch == nil { - task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo) + task = NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo) } else { - cmdObj := gui.Git.Branch.GetGraphCmdObj(branch.Name) + cmdObj := gui.git.Branch.GetGraphCmdObj(branch.Name) task = NewRunPtyTask(cmdObj.GetCmd()) } @@ -56,21 +57,21 @@ func (gui *Gui) refreshBranches() { // which allows us to order them correctly. So if we're filtering we'll just // manually load all the reflog commits here var err error - reflogCommits, _, err = gui.Git.Loaders.ReflogCommits.GetReflogCommits(nil, "") + reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "") if err != nil { - gui.Log.Error(err) + gui.c.Log.Error(err) } } - branches, err := gui.Git.Loaders.Branches.Load(reflogCommits) + branches, err := gui.git.Loaders.Branches.Load(reflogCommits) if err != nil { - _ = gui.PopupHandler.Error(err) + _ = gui.c.Error(err) } gui.State.Branches = branches - if err := gui.postRefreshUpdate(gui.State.Contexts.Branches); err != nil { - gui.Log.Error(err) + if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Branches); err != nil { + gui.c.Log.Error(err) } gui.refreshStatus() @@ -83,11 +84,11 @@ func (gui *Gui) handleBranchPress() error { return nil } if gui.State.Panels.Branches.SelectedLineIdx == 0 { - return gui.PopupHandler.ErrorMsg(gui.Tr.AlreadyCheckedOutBranch) + return gui.c.ErrorMsg(gui.c.Tr.AlreadyCheckedOutBranch) } branch := gui.getSelectedBranch() - gui.logAction(gui.Tr.Actions.CheckoutBranch) - return gui.handleCheckoutRef(branch.Name, handleCheckoutRefOptions{}) + gui.c.LogAction(gui.c.Tr.Actions.CheckoutBranch) + return gui.refHelper.CheckoutRef(branch.Name, types.CheckoutRefOptions{}) } func (gui *Gui) handleCreatePullRequestPress() error { @@ -110,129 +111,64 @@ func (gui *Gui) handleCopyPullRequestURLPress() error { branch := gui.getSelectedBranch() - branchExistsOnRemote := gui.Git.Remote.CheckRemoteBranchExists(branch.Name) + branchExistsOnRemote := gui.git.Remote.CheckRemoteBranchExists(branch.Name) if !branchExistsOnRemote { - return gui.PopupHandler.Error(errors.New(gui.Tr.NoBranchOnRemote)) + return gui.c.Error(errors.New(gui.c.Tr.NoBranchOnRemote)) } url, err := hostingServiceMgr.GetPullRequestURL(branch.Name, "") if err != nil { - return gui.PopupHandler.Error(err) + return gui.c.Error(err) } - gui.logAction(gui.Tr.Actions.CopyPullRequestURL) + gui.c.LogAction(gui.c.Tr.Actions.CopyPullRequestURL) if err := gui.OSCommand.CopyToClipboard(url); err != nil { - return gui.PopupHandler.Error(err) + return gui.c.Error(err) } - gui.raiseToast(gui.Tr.PullRequestURLCopiedToClipboard) + gui.c.Toast(gui.c.Tr.PullRequestURLCopiedToClipboard) return nil } func (gui *Gui) handleGitFetch() error { - return gui.PopupHandler.WithLoaderPanel(gui.Tr.FetchWait, func() error { + return gui.c.WithLoaderPanel(gui.c.Tr.FetchWait, func() error { if err := gui.fetch(); err != nil { - _ = gui.PopupHandler.Error(err) + _ = gui.c.Error(err) } - return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}) + return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) }) } func (gui *Gui) handleForceCheckout() error { branch := gui.getSelectedBranch() - message := gui.Tr.SureForceCheckout - title := gui.Tr.ForceCheckoutBranch + message := gui.c.Tr.SureForceCheckout + title := gui.c.Tr.ForceCheckoutBranch - return gui.PopupHandler.Ask(popup.AskOpts{ + return gui.c.Ask(popup.AskOpts{ Title: title, Prompt: message, HandleConfirm: func() error { - gui.logAction(gui.Tr.Actions.ForceCheckoutBranch) - if err := gui.Git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil { - _ = gui.PopupHandler.Error(err) + gui.c.LogAction(gui.c.Tr.Actions.ForceCheckoutBranch) + if err := gui.git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil { + _ = gui.c.Error(err) } - return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}) + return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) }, }) } -type handleCheckoutRefOptions struct { - WaitingStatus string - EnvVars []string - onRefNotFound func(ref string) error -} - -func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions) error { - waitingStatus := options.WaitingStatus - if waitingStatus == "" { - waitingStatus = gui.Tr.CheckingOutStatus - } - - cmdOptions := git_commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars} - - onSuccess := func() { - gui.State.Panels.Branches.SelectedLineIdx = 0 - gui.State.Panels.Commits.SelectedLineIdx = 0 - // loading a heap of commits is slow so we limit them whenever doing a reset - gui.State.Panels.Commits.LimitCommits = true - } - - return gui.PopupHandler.WithWaitingStatus(waitingStatus, func() error { - if err := gui.Git.Branch.Checkout(ref, cmdOptions); err != nil { - // note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option - - if options.onRefNotFound != nil && strings.Contains(err.Error(), "did not match any file(s) known to git") { - return options.onRefNotFound(ref) - } - - if strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") { - // offer to autostash changes - return gui.PopupHandler.Ask(popup.AskOpts{ - - Title: gui.Tr.AutoStashTitle, - Prompt: gui.Tr.AutoStashPrompt, - HandleConfirm: func() error { - if err := gui.Git.Stash.Save(gui.Tr.StashPrefix + ref); err != nil { - return gui.PopupHandler.Error(err) - } - if err := gui.Git.Branch.Checkout(ref, cmdOptions); err != nil { - return gui.PopupHandler.Error(err) - } - - onSuccess() - if err := gui.Git.Stash.Pop(0); err != nil { - if err := gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI}); err != nil { - return err - } - return gui.PopupHandler.Error(err) - } - return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI}) - }, - }) - } - - if err := gui.PopupHandler.Error(err); err != nil { - return err - } - } - onSuccess() - - return gui.refreshSidePanels(types.RefreshOptions{Mode: types.BLOCK_UI}) - }) -} - func (gui *Gui) handleCheckoutByName() error { - return gui.PopupHandler.Prompt(popup.PromptOpts{ - Title: gui.Tr.BranchName + ":", - FindSuggestionsFunc: gui.getRefsSuggestionsFunc(), + return gui.c.Prompt(popup.PromptOpts{ + Title: gui.c.Tr.BranchName + ":", + FindSuggestionsFunc: gui.suggestionsHelper.GetRefsSuggestionsFunc(), HandleConfirm: func(response string) error { - gui.logAction("Checkout branch") - return gui.handleCheckoutRef(response, handleCheckoutRefOptions{ - onRefNotFound: func(ref string) error { - return gui.PopupHandler.Ask(popup.AskOpts{ - Title: gui.Tr.BranchNotFoundTitle, - Prompt: fmt.Sprintf("%s %s%s", gui.Tr.BranchNotFoundPrompt, ref, "?"), + gui.c.LogAction("Checkout branch") + return gui.refHelper.CheckoutRef(response, types.CheckoutRefOptions{ + OnRefNotFound: func(ref string) error { + return gui.c.Ask(popup.AskOpts{ + Title: gui.c.Tr.BranchNotFoundTitle, + Prompt: fmt.Sprintf("%s %s%s", gui.c.Tr.BranchNotFoundPrompt, ref, "?"), HandleConfirm: func() error { return gui.createNewBranchWithName(ref) }, @@ -257,12 +193,12 @@ func (gui *Gui) createNewBranchWithName(newBranchName string) error { return nil } - if err := gui.Git.Branch.New(newBranchName, branch.Name); err != nil { - return gui.PopupHandler.Error(err) + if err := gui.git.Branch.New(newBranchName, branch.Name); err != nil { + return gui.c.Error(err) } gui.State.Panels.Branches.SelectedLineIdx = 0 - return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}) + return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) } func (gui *Gui) handleDeleteBranch() error { @@ -276,18 +212,18 @@ func (gui *Gui) deleteBranch(force bool) error { } checkedOutBranch := gui.getCheckedOutBranch() if checkedOutBranch.Name == selectedBranch.Name { - return gui.PopupHandler.ErrorMsg(gui.Tr.CantDeleteCheckOutBranch) + return gui.c.ErrorMsg(gui.c.Tr.CantDeleteCheckOutBranch) } return gui.deleteNamedBranch(selectedBranch, force) } func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) error { - title := gui.Tr.DeleteBranch + title := gui.c.Tr.DeleteBranch var templateStr string if force { - templateStr = gui.Tr.ForceDeleteBranchMessage + templateStr = gui.c.Tr.ForceDeleteBranchMessage } else { - templateStr = gui.Tr.DeleteBranchMessage + templateStr = gui.c.Tr.DeleteBranchMessage } message := utils.ResolvePlaceholderString( templateStr, @@ -296,59 +232,51 @@ func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) err }, ) - return gui.PopupHandler.Ask(popup.AskOpts{ + return gui.c.Ask(popup.AskOpts{ Title: title, Prompt: message, HandleConfirm: func() error { - gui.logAction(gui.Tr.Actions.DeleteBranch) - if err := gui.Git.Branch.Delete(selectedBranch.Name, force); err != nil { + gui.c.LogAction(gui.c.Tr.Actions.DeleteBranch) + if err := gui.git.Branch.Delete(selectedBranch.Name, force); err != nil { errMessage := err.Error() if !force && strings.Contains(errMessage, "git branch -D ") { return gui.deleteNamedBranch(selectedBranch, true) } - return gui.PopupHandler.ErrorMsg(errMessage) + return gui.c.ErrorMsg(errMessage) } - return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) + return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) }, }) } func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error { - if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { - return err - } - - if gui.Git.Branch.IsHeadDetached() { - return gui.PopupHandler.ErrorMsg("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on") + if gui.git.Branch.IsHeadDetached() { + return gui.c.ErrorMsg("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on") } checkedOutBranchName := gui.getCheckedOutBranch().Name if checkedOutBranchName == branchName { - return gui.PopupHandler.ErrorMsg(gui.Tr.CantMergeBranchIntoItself) + return gui.c.ErrorMsg(gui.c.Tr.CantMergeBranchIntoItself) } prompt := utils.ResolvePlaceholderString( - gui.Tr.ConfirmMerge, + gui.c.Tr.ConfirmMerge, map[string]string{ "checkedOutBranch": checkedOutBranchName, "selectedBranch": branchName, }, ) - return gui.PopupHandler.Ask(popup.AskOpts{ - Title: gui.Tr.MergingTitle, + return gui.c.Ask(popup.AskOpts{ + Title: gui.c.Tr.MergingTitle, Prompt: prompt, HandleConfirm: func() error { - gui.logAction(gui.Tr.Actions.Merge) - err := gui.Git.Branch.Merge(branchName, git_commands.MergeOpts{}) - return gui.handleGenericMergeCommandResult(err) + gui.c.LogAction(gui.c.Tr.Actions.Merge) + err := gui.git.Branch.Merge(branchName, git_commands.MergeOpts{}) + return gui.checkMergeOrRebase(err) }, }) } func (gui *Gui) handleMerge() error { - if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { - return err - } - selectedBranchName := gui.getSelectedBranch().Name return gui.mergeBranchIntoCheckedOutBranch(selectedBranchName) } @@ -359,29 +287,25 @@ func (gui *Gui) handleRebaseOntoLocalBranch() error { } func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error { - if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { - return err - } - checkedOutBranch := gui.getCheckedOutBranch().Name if selectedBranchName == checkedOutBranch { - return gui.PopupHandler.ErrorMsg(gui.Tr.CantRebaseOntoSelf) + return gui.c.ErrorMsg(gui.c.Tr.CantRebaseOntoSelf) } prompt := utils.ResolvePlaceholderString( - gui.Tr.ConfirmRebase, + gui.c.Tr.ConfirmRebase, map[string]string{ "checkedOutBranch": checkedOutBranch, "selectedBranch": selectedBranchName, }, ) - return gui.PopupHandler.Ask(popup.AskOpts{ - Title: gui.Tr.RebasingTitle, + return gui.c.Ask(popup.AskOpts{ + Title: gui.c.Tr.RebasingTitle, Prompt: prompt, HandleConfirm: func() error { - gui.logAction(gui.Tr.Actions.RebaseBranch) - err := gui.Git.Rebase.RebaseBranch(selectedBranchName) - return gui.handleGenericMergeCommandResult(err) + gui.c.LogAction(gui.c.Tr.Actions.RebaseBranch) + err := gui.git.Rebase.RebaseBranch(selectedBranchName) + return gui.checkMergeOrRebase(err) }, }) } @@ -393,35 +317,35 @@ func (gui *Gui) handleFastForward() error { } if !branch.IsTrackingRemote() { - return gui.PopupHandler.ErrorMsg(gui.Tr.FwdNoUpstream) + return gui.c.ErrorMsg(gui.c.Tr.FwdNoUpstream) } if !branch.RemoteBranchStoredLocally() { - return gui.PopupHandler.ErrorMsg(gui.Tr.FwdNoLocalUpstream) + return gui.c.ErrorMsg(gui.c.Tr.FwdNoLocalUpstream) } if branch.HasCommitsToPush() { - return gui.PopupHandler.ErrorMsg(gui.Tr.FwdCommitsToPush) + return gui.c.ErrorMsg(gui.c.Tr.FwdCommitsToPush) } - action := gui.Tr.Actions.FastForwardBranch + action := gui.c.Tr.Actions.FastForwardBranch message := utils.ResolvePlaceholderString( - gui.Tr.Fetching, + gui.c.Tr.Fetching, map[string]string{ "from": fmt.Sprintf("%s/%s", branch.UpstreamRemote, branch.UpstreamBranch), "to": branch.Name, }, ) - return gui.PopupHandler.WithLoaderPanel(message, func() error { + return gui.c.WithLoaderPanel(message, func() error { if gui.State.Panels.Branches.SelectedLineIdx == 0 { - _ = gui.pullWithLock(PullFilesOptions{action: action, FastForwardOnly: true}) + _ = gui.Controllers.Sync.PullAux(controllers.PullFilesOptions{Action: action, FastForwardOnly: true}) } else { - gui.logAction(action) - err := gui.Git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch) + gui.c.LogAction(action) + err := gui.git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch) if err != nil { - _ = gui.PopupHandler.Error(err) + _ = gui.c.Error(err) } - _ = gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) + _ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}}) } return nil @@ -434,7 +358,7 @@ func (gui *Gui) handleCreateResetToBranchMenu() error { return nil } - return gui.createResetMenu(branch.Name) + return gui.refHelper.CreateGitResetMenu(branch.Name) } func (gui *Gui) handleRenameBranch() error { @@ -444,13 +368,13 @@ func (gui *Gui) handleRenameBranch() error { } promptForNewName := func() error { - return gui.PopupHandler.Prompt(popup.PromptOpts{ - Title: gui.Tr.NewBranchNamePrompt + " " + branch.Name + ":", + return gui.c.Prompt(popup.PromptOpts{ + Title: gui.c.Tr.NewBranchNamePrompt + " " + branch.Name + ":", InitialContent: branch.Name, HandleConfirm: func(newBranchName string) error { - gui.logAction(gui.Tr.Actions.RenameBranch) - if err := gui.Git.Branch.Rename(branch.Name, newBranchName); err != nil { - return gui.PopupHandler.Error(err) + gui.c.LogAction(gui.c.Tr.Actions.RenameBranch) + if err := gui.git.Branch.Rename(branch.Name, newBranchName); err != nil { + return gui.c.Error(err) } // need to find where the branch is now so that we can re-select it. That means we need to refetch the branches synchronously and then find our branch @@ -478,20 +402,13 @@ func (gui *Gui) handleRenameBranch() error { return promptForNewName() } - return gui.PopupHandler.Ask(popup.AskOpts{ - Title: gui.Tr.LcRenameBranch, - Prompt: gui.Tr.RenameBranchWarning, + return gui.c.Ask(popup.AskOpts{ + Title: gui.c.Tr.LcRenameBranch, + Prompt: gui.c.Tr.RenameBranchWarning, HandleConfirm: promptForNewName, }) } -func (gui *Gui) currentBranch() *models.Branch { - if len(gui.State.Branches) == 0 { - return nil - } - return gui.State.Branches[0] -} - func (gui *Gui) handleNewBranchOffCurrentItem() error { context := gui.currentSideListContext() @@ -501,7 +418,7 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error { } message := utils.ResolvePlaceholderString( - gui.Tr.NewBranchNameBranchOff, + gui.c.Tr.NewBranchNameBranchOff, map[string]string{ "branchName": item.Description(), }, @@ -513,12 +430,12 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error { prefilledName = strings.SplitAfterN(item.ID(), "/", 2)[1] } - return gui.PopupHandler.Prompt(popup.PromptOpts{ + return gui.c.Prompt(popup.PromptOpts{ Title: message, InitialContent: prefilledName, HandleConfirm: func(response string) error { - gui.logAction(gui.Tr.Actions.CreateBranch) - if err := gui.Git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil { + gui.c.LogAction(gui.c.Tr.Actions.CreateBranch) + if err := gui.git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil { return err } @@ -529,14 +446,14 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error { } if context.GetKey() != gui.State.Contexts.Branches.GetKey() { - if err := gui.pushContext(gui.State.Contexts.Branches); err != nil { + if err := gui.c.PushContext(gui.State.Contexts.Branches); err != nil { return err } } gui.State.Panels.Branches.SelectedLineIdx = 0 - return gui.refreshSidePanels(types.RefreshOptions{Mode: types.ASYNC}) + return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) }, }) } diff --git a/pkg/gui/cherry_picking.go b/pkg/gui/cherry_picking.go index 225fc3811..28554edce 100644 --- a/pkg/gui/cherry_picking.go +++ b/pkg/gui/cherry_picking.go @@ -3,12 +3,13 @@ package gui import ( "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/popup" + "github.com/jesseduffield/lazygit/pkg/gui/types" ) // you can only copy from one context at a time, because the order and position of commits matter -func (gui *Gui) resetCherryPickingIfNecessary(context Context) error { - oldContextKey := ContextKey(gui.State.Modes.CherryPicking.ContextKey) +func (gui *Gui) resetCherryPickingIfNecessary(context types.Context) error { + oldContextKey := types.ContextKey(gui.State.Modes.CherryPicking.ContextKey) if oldContextKey != context.GetKey() { // need to reset the cherry picking mode @@ -22,10 +23,6 @@ func (gui *Gui) resetCherryPickingIfNecessary(context Context) error { } func (gui *Gui) handleCopyCommit() error { - if ok, err := gui.validateNotInFilterMode(); err != nil || !ok { - return err - } - // get currently selected commit, add the sha to state. context := gui.currentSideListContext() if context == nil { @@ -80,7 +77,7 @@ func (gui *Gui) commitsListForContext() []*models.Commit { case SUB_COMMITS_CONTEXT_KEY: return gui.State.SubCommits default: - gui.Log.Errorf("no commit list for context %s", context.GetKey()) + gui.c.Log.Errorf("no commit list for context %s", context.GetKey()) return nil } } @@ -102,10 +99,6 @@ func (gui *Gui) addCommitToCherryPickedCommits(index int) { } func (gui *Gui) handleCopyCommitRange() error { - if ok, err := g