Repository: jesseduffield/lazygit (language: go)

Issue:
Show current checked out branch on "Recent Repositories" dialog
I work with 14 different repos and often use the same branch name when making related changes across different repos. It would be great to be able to see the current selected branch when showing the "Recent Repositories" to make it easier to see what branches are being worked on across projects, i.e. see the project/checked out branches in one shot.





Discussion / hints:
@jesseduffield this seems doable, just a `git -C <repo-path-here> rev-parse --abbrev-ref HEAD` for every repository in the list, does that sound reasonable?

It could be triggered by opening the `recent repos` window, which would also be a great time to filter out invalid paths?

Fix patch:
diff --git a/pkg/app/app.go b/pkg/app/app.go
index 9a7b1ffcc00..418c1406ed8 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 605ae79d312..3b817fbca14 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) 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 {
-	recentRepoPaths := gui.c.GetAppState().RecentRepos
+	// 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 af5a8a99be9..18d64016bb6 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
@@ -1045,6 +1046,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",


Test patch: