From c679fd1924e9669a30d038fbdd164d948393c301 Mon Sep 17 00:00:00 2001 From: Joel Baranick Date: Fri, 2 Sep 2022 09:35:08 -0700 Subject: [PATCH] Style missing worktree as red and display better error when trying to switch to them Use a broken link icon for missing worktrees --- pkg/commands/git_commands/worktree_loader.go | 29 +----------- pkg/commands/models/worktree.go | 44 ++++++++++++++++--- pkg/gui/context/worktrees_context.go | 2 +- pkg/gui/controllers/helpers/refresh_helper.go | 13 +++--- pkg/gui/controllers/helpers/repos_helper.go | 6 ++- .../controllers/helpers/worktree_helper.go | 32 ++++++-------- pkg/gui/controllers/worktrees_controller.go | 24 +++++----- pkg/gui/presentation/icons/git_icons.go | 24 +++++----- pkg/gui/presentation/worktrees.go | 12 +++-- pkg/i18n/english.go | 2 + 10 files changed, 103 insertions(+), 85 deletions(-) diff --git a/pkg/commands/git_commands/worktree_loader.go b/pkg/commands/git_commands/worktree_loader.go index 81df84398..8dc7dcbe5 100644 --- a/pkg/commands/git_commands/worktree_loader.go +++ b/pkg/commands/git_commands/worktree_loader.go @@ -1,10 +1,6 @@ package git_commands import ( - "errors" - "io/fs" - "os" - "path/filepath" "strings" "github.com/jesseduffield/lazygit/pkg/commands/models" @@ -28,11 +24,6 @@ func NewWorktreeLoader( } func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) { - currentDir, err := os.Getwd() - if err != nil { - return nil, err - } - cmdArgs := NewGitCmd("worktree").Arg("list", "--porcelain", "-z").ToArgv() worktreesOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() if err != nil { @@ -51,25 +42,9 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) { } if strings.HasPrefix(splitLine, "worktree ") { path := strings.SplitN(splitLine, " ", 2)[1] - - if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { - // Ignore because the worktree is points to a non-existing filesystem location - continue - } - - main := false - name := "main" - if len(worktrees) == 0 { - main = true - } else { - name = filepath.Base(path) - } - currentWorktree = &models.Worktree{ - Name: name, - Path: path, - Main: main, - Current: path == currentDir, + Id: len(worktrees), + Path: path, } } } diff --git a/pkg/commands/models/worktree.go b/pkg/commands/models/worktree.go index f38c67e07..c4e31a9d3 100644 --- a/pkg/commands/models/worktree.go +++ b/pkg/commands/models/worktree.go @@ -1,15 +1,22 @@ package models +import ( + "fmt" + "github.com/go-errors/errors" + "io/fs" + "log" + "os" + "path/filepath" +) + // Worktree : A git worktree type Worktree struct { - Name string - Main bool - Current bool - Path string + Id int + Path string } func (w *Worktree) RefName() string { - return w.Name + return w.Name() } func (w *Worktree) ID() string { @@ -19,3 +26,30 @@ func (w *Worktree) ID() string { func (w *Worktree) Description() string { return w.RefName() } + +func (w *Worktree) Name() string { + return filepath.Base(w.Path) +} + +func (w *Worktree) Main() bool { + return w.Id == 0 +} + +func (w *Worktree) Current() bool { + pwd, err := os.Getwd() + if err != nil { + log.Fatalln(err.Error()) + } + + return pwd == w.Path +} + +func (w *Worktree) Missing() bool { + if _, err := os.Stat(w.Path); err != nil { + if errors.Is(err, fs.ErrNotExist) { + return true + } + log.Fatalln(fmt.Errorf("failed to check if worktree path `%s` exists\n%w", w.Path, err).Error()) + } + return false +} diff --git a/pkg/gui/context/worktrees_context.go b/pkg/gui/context/worktrees_context.go index 6ce8f8a80..5db816b60 100644 --- a/pkg/gui/context/worktrees_context.go +++ b/pkg/gui/context/worktrees_context.go @@ -17,7 +17,7 @@ func NewWorktreesContext(c *ContextCommon) *WorktreesContext { viewModel := NewFilteredListViewModel( func() []*models.Worktree { return c.Model().Worktrees }, func(Worktree *models.Worktree) []string { - return []string{Worktree.Name} + return []string{Worktree.Name()} }, ) diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index 9745cf58e..6214416db 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -638,16 +638,13 @@ func (self *RefreshHelper) refreshStatus() { } name := presentation.GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name) - var repoName string - worktreeName := self.worktreeHelper.GetCurrentWorktreeName() - if len(worktreeName) > 0 { - worktreeName = fmt.Sprintf("[%s]", worktreeName) - repoName = self.worktreeHelper.GetMainWorktreeName() - } else { - repoName = utils.GetCurrentRepoName() + repoName = utils.GetCurrentRepoName() + mainWorktreeName := self.worktreeHelper.GetMainWorktreeName() + if repoName != mainWorktreeName { + repoName = fmt.Sprintf("%s(%s)", mainWorktreeName, style.FgBlue.Sprint(repoName)) } - status += fmt.Sprintf("%s%s → %s ", repoName, worktreeName, name) + status += fmt.Sprintf("%s → %s ", repoName, name) self.c.SetViewContent(self.c.Views().Status, status) } diff --git a/pkg/gui/controllers/helpers/repos_helper.go b/pkg/gui/controllers/helpers/repos_helper.go index f4e700d7b..013580ff2 100644 --- a/pkg/gui/controllers/helpers/repos_helper.go +++ b/pkg/gui/controllers/helpers/repos_helper.go @@ -138,6 +138,10 @@ func (self *ReposHelper) CreateRecentReposMenu() error { } func (self *ReposHelper) DispatchSwitchToRepo(path string, reuse bool) error { + return self.DispatchSwitchTo(path, reuse, self.c.Tr.ErrRepositoryMovedOrDeleted) +} + +func (self *ReposHelper) DispatchSwitchTo(path string, reuse bool, errMsg string) error { env.UnsetGitDirEnvs() originalPath, err := os.Getwd() if err != nil { @@ -146,7 +150,7 @@ func (self *ReposHelper) DispatchSwitchToRepo(path string, reuse bool) error { if err := os.Chdir(path); err != nil { if os.IsNotExist(err) { - return self.c.ErrorMsg(self.c.Tr.ErrRepositoryMovedOrDeleted) + return self.c.ErrorMsg(errMsg) } return err } diff --git a/pkg/gui/controllers/helpers/worktree_helper.go b/pkg/gui/controllers/helpers/worktree_helper.go index 9d22f0db1..a1adccd67 100644 --- a/pkg/gui/controllers/helpers/worktree_helper.go +++ b/pkg/gui/controllers/helpers/worktree_helper.go @@ -1,9 +1,5 @@ package helpers -import ( - "path/filepath" -) - type IWorktreeHelper interface { GetMainWorktreeName() string GetCurrentWorktreeName() string @@ -21,23 +17,23 @@ func NewWorktreeHelper(c *HelperCommon) *WorktreeHelper { func (self *WorktreeHelper) GetMainWorktreeName() string { for _, worktree := range self.c.Model().Worktrees { - if worktree.Main { - return filepath.Base(worktree.Path) + if worktree.Main() { + return worktree.Name() } } return "" } -func (self *WorktreeHelper) GetCurrentWorktreeName() string { - for _, worktree := range self.c.Model().Worktrees { - if worktree.Current { - if worktree.Main { - return "" - } - return worktree.Name - } - } - - return "" -} +//func (self *WorktreeHelper) GetCurrentWorktreeName() string { +// for _, worktree := range self.c.Model().Worktrees { +// if worktree.Current() { +// if worktree.Main() { +// return "" +// } +// return worktree.Name() +// } +// } +// +// return "" +//} diff --git a/pkg/gui/controllers/worktrees_controller.go b/pkg/gui/controllers/worktrees_controller.go index 9cbbb6faa..c54ab9db0 100644 --- a/pkg/gui/controllers/worktrees_controller.go +++ b/pkg/gui/controllers/worktrees_controller.go @@ -2,7 +2,6 @@ package controllers import ( "fmt" - "os" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/context" @@ -56,7 +55,11 @@ func (self *WorktreesController) GetOnRenderToMain() func() error { if worktree == nil { task = types.NewRenderStringTask("No worktrees") } else { - task = types.NewRenderStringTask(fmt.Sprintf("%s\nPath: %s", style.FgGreen.Sprint(worktree.Name), worktree.Path)) + missing := "" + if worktree.Missing() { + missing = style.FgRed.Sprint(" (missing)") + } + task = types.NewRenderStringTask(fmt.Sprintf("%s\nPath: %s%s", style.FgGreen.Sprint(worktree.Name()), worktree.Path, missing)) } return self.c.RenderToMainViews(types.RefreshMainOpts{ @@ -86,11 +89,11 @@ func (self *WorktreesController) GetOnRenderToMain() func() error { //} func (self *WorktreesController) delete(worktree *models.Worktree) error { - if worktree.Main { + if worktree.Main() { return self.c.ErrorMsg(self.c.Tr.CantDeleteMainWorktree) } - if worktree.Current { + if worktree.Current() { return self.c.ErrorMsg(self.c.Tr.CantDeleteCurrentWorktree) } @@ -108,7 +111,7 @@ func (self *WorktreesController) deleteWithForce(worktree *models.Worktree, forc message := utils.ResolvePlaceholderString( templateStr, map[string]string{ - "worktreeName": worktree.Name, + "worktreeName": worktree.Name(), }, ) @@ -170,14 +173,11 @@ func (self *WorktreesController) GetOnClick() func() error { } func (self *WorktreesController) enter(worktree *models.Worktree) error { - wd, err := os.Getwd() - if err != nil { - return err - } + // if we were in a submodule, we want to forget about that stack of repos + // so that hitting escape in the new repo does nothing + self.c.State().GetRepoPathStack().Clear() - self.c.State().GetRepoPathStack().Push(wd) - - return self.c.Helpers().Repos.DispatchSwitchToRepo(worktree.Path, true) + return self.c.Helpers().Repos.DispatchSwitchTo(worktree.Path, true, self.c.Tr.ErrWorktreeMovedOrDeleted) } func (self *WorktreesController) checkSelected(callback func(worktree *models.Worktree) error) func() error { diff --git a/pkg/gui/presentation/icons/git_icons.go b/pkg/gui/presentation/icons/git_icons.go index 6b5689295..f6b56284e 100644 --- a/pkg/gui/presentation/icons/git_icons.go +++ b/pkg/gui/presentation/icons/git_icons.go @@ -7,14 +7,15 @@ import ( ) var ( - BRANCH_ICON = "\U000f062c" // 󰘬 - DETACHED_HEAD_ICON = "\ue729" //  - TAG_ICON = "\uf02b" //  - COMMIT_ICON = "\U000f0718" // 󰜘 - MERGE_COMMIT_ICON = "\U000f062d" // 󰘭 - DEFAULT_REMOTE_ICON = "\uf02a2" // 󰊢 - STASH_ICON = "\uf01c" //  - LINKED_WORKTREE_ICON = "\uf838" //  + BRANCH_ICON = "\U000f062c" // 󰘬 + DETACHED_HEAD_ICON = "\ue729" //  + TAG_ICON = "\uf02b" //  + COMMIT_ICON = "\U000f0718" // 󰜘 + MERGE_COMMIT_ICON = "\U000f062d" // 󰘭 + DEFAULT_REMOTE_ICON = "\uf02a2" // 󰊢 + STASH_ICON = "\uf01c" //  + LINKED_WORKTREE_ICON = "\uf838" //  + MISSING_LINKED_WORKTREE_ICON = "\uf839" //  ) var remoteIcons = map[string]string{ @@ -70,9 +71,12 @@ func IconForStash(stash *models.StashEntry) string { return STASH_ICON } -func IconForWorktree(worktree *models.Worktree) string { - if worktree.Main { +func IconForWorktree(worktree *models.Worktree, missing bool) string { + if worktree.Main() { return "" } + if missing { + return MISSING_LINKED_WORKTREE_ICON + } return LINKED_WORKTREE_ICON } diff --git a/pkg/gui/presentation/worktrees.go b/pkg/gui/presentation/worktrees.go index 4bb778944..cf7b08938 100644 --- a/pkg/gui/presentation/worktrees.go +++ b/pkg/gui/presentation/worktrees.go @@ -20,16 +20,22 @@ func getWorktreeDisplayStrings(w *models.Worktree) []string { current := "" currentColor := style.FgCyan - if w.Current { + if w.Current() { current = " *" currentColor = style.FgGreen } + icon := icons.IconForWorktree(w, false) + if w.Missing() { + textStyle = style.FgRed + icon = icons.IconForWorktree(w, true) + } + res := make([]string, 0, 3) res = append(res, currentColor.Sprint(current)) if icons.IsIconEnabled() { - res = append(res, textStyle.Sprint(icons.IconForWorktree(w))) + res = append(res, textStyle.Sprint(icon)) } - res = append(res, textStyle.Sprint(w.Name)) + res = append(res, textStyle.Sprint(w.Name())) return res } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 27288e078..6d479cfdc 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -482,6 +482,7 @@ type TranslationSet struct { ErrCannotEditDirectory string ErrStageDirWithInlineMergeConflicts string ErrRepositoryMovedOrDeleted string + ErrWorktreeMovedOrDeleted string CommandLog string ToggleShowCommandLog string FocusCommandLog string @@ -1191,6 +1192,7 @@ func EnglishTranslationSet() TranslationSet { ErrStageDirWithInlineMergeConflicts: "Cannot stage/unstage directory containing files with inline merge conflicts. Please fix up the merge conflicts first", ErrRepositoryMovedOrDeleted: "Cannot find repo. It might have been moved or deleted ¯\\_(ツ)_/¯", CommandLog: "Command log", + ErrWorktreeMovedOrDeleted: "Cannot find worktree. It might have been moved or deleted ¯\\_(ツ)_/¯", ToggleShowCommandLog: "Toggle show/hide command log", FocusCommandLog: "Focus command log", CommandLogHeader: "You can hide/focus this panel by pressing '%s'\n",