diff --git a/pkg/app/app.go b/pkg/app/app.go index 9a7b1ffcc..418c1406e 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -148,7 +148,7 @@ func isGitVersionValid(versionStr string) bool { func isDirectoryAGitRepository(dir string) (bool, error) { info, err := os.Stat(filepath.Join(dir, ".git")) - return info != nil && info.IsDir(), err + return info != nil, err } func (app *App) setupRepo() (bool, error) { diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go index 605ae79d3..3b817fbca 100644 --- a/pkg/gui/recent_repos_panel.go +++ b/pkg/gui/recent_repos_panel.go @@ -1,24 +1,92 @@ package gui import ( + "fmt" + "io/ioutil" "os" "path/filepath" + "strings" + "sync" "github.com/jesseduffield/generics/slices" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/env" + "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) handleCreateRecentReposMenu() error { - recentRepoPaths := gui.c.GetAppState().RecentRepos +func (gui *Gui) getCurrentBranch(path string) string { + readHeadFile := func(path string) (string, error) { + headFile, err := ioutil.ReadFile(filepath.Join(path, "HEAD")) + if err == nil { + content := strings.TrimSpace(string(headFile)) + refsPrefix := "ref: refs/heads/" + branchDisplay := "" + if strings.HasPrefix(content, refsPrefix) { + // is a branch + branchDisplay = strings.TrimPrefix(content, refsPrefix) + } else { + // detached HEAD state, displaying short SHA + branchDisplay = utils.ShortSha(content) + } + return branchDisplay, nil + } + return "", err + } + + gitDirPath := filepath.Join(path, ".git") + + if gitDir, err := os.Stat(gitDirPath); err == nil { + if gitDir.IsDir() { + // ordinary repo + if branch, err := readHeadFile(gitDirPath); err == nil { + return branch + } + } else { + // worktree + if worktreeGitDir, err := ioutil.ReadFile(gitDirPath); err == nil { + content := strings.TrimSpace(string(worktreeGitDir)) + worktreePath := strings.TrimPrefix(content, "gitdir: ") + if branch, err := readHeadFile(worktreePath); err == nil { + return branch + } + } + } + } + + return gui.c.Tr.LcBranchUnknown +} + +func (gui *Gui) handleCreateRecentReposMenu() error { + // we skip the first one because we're currently in it + recentRepoPaths := gui.c.GetAppState().RecentRepos[1:] + + currentBranches := sync.Map{} + + wg := sync.WaitGroup{} + wg.Add(len(recentRepoPaths)) + + for _, path := range recentRepoPaths { + go func(path string) { + defer wg.Done() + currentBranches.Store(path, gui.getCurrentBranch(path)) + }(path) + } + + wg.Wait() + + menuItems := slices.Map(recentRepoPaths, func(path string) *types.MenuItem { + branchName, _ := currentBranches.Load(path) + if icons.IsIconEnabled() { + branchName = icons.BRANCH_ICON + " " + fmt.Sprintf("%v", branchName) + } - // we won't show the current repo hence the -1 - menuItems := slices.Map(recentRepoPaths[1:], func(path string) *types.MenuItem { return &types.MenuItem{ LabelColumns: []string{ filepath.Base(path), + style.FgCyan.Sprint(branchName), style.FgMagenta.Sprint(path), }, OnPress: func() error { @@ -110,7 +178,7 @@ func newRecentReposList(recentRepos []string, currentRepo string) (bool, []strin newRepos := []string{currentRepo} for _, repo := range recentRepos { if repo != currentRepo { - if _, err := os.Stat(repo); err != nil { + if _, err := os.Stat(filepath.Join(repo, ".git")); err != nil { continue } newRepos = append(newRepos, repo) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index be771d813..b57c1aad8 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -409,6 +409,7 @@ type TranslationSet struct { NoFilesStagedPrompt string BranchNotFoundTitle string BranchNotFoundPrompt string + LcBranchUnknown string UnstageLinesTitle string UnstageLinesPrompt string LcCreateNewBranchFromCommit string @@ -1046,6 +1047,7 @@ func EnglishTranslationSet() TranslationSet { NoFilesStagedPrompt: "You have not staged any files. Commit all files?", BranchNotFoundTitle: "Branch not found", BranchNotFoundPrompt: "Branch not found. Create a new branch named", + LcBranchUnknown: "branch unknown", UnstageLinesTitle: "Unstage lines", UnstageLinesPrompt: "Are you sure you want to delete the selected lines (git reset)? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipUnstageLineWarning' to true", LcCreateNewBranchFromCommit: "create new branch off of commit",