mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-27 17:56:37 -04:00
Put the remotes in the model before loading their branches
When lazygit starts up in a repo with many remote branches, the pull request icons in the branches panel show up a good while after the branch list itself, even though the pull requests come from the cache file and are in the model before the first render. In the repo where this showed up (5400 remote branches) the icons were up to half a second late. The icons are rendered from Model.PullRequestsMap, and that map is built from the remotes' URLs, because a branch's upstream remote tells us which repo owner's pull requests to look for. The map therefore stays empty until the remotes are in the model, and the remotes refresh doesn't put them there until it has also enumerated and sorted all remote branches. In a big repo that takes hundreds of milliseconds; reading the remotes themselves takes ten. Load the two separately, and put the remotes in the model as soon as they have been read from the git config. The branches refresh then finds them there, and the pull request icons are part of the first render of the branch list. Carry over the branches of the remotes we already have in the model in that first update, so that the remote branches, and the branch counts in the remotes panel, stay in place until the fresh ones are loaded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
01ce5b0800
commit
1b662a8ec3
@@ -5,10 +5,8 @@ import (
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -20,30 +18,12 @@ func NewRemoteLoader(gitCommon *GitCommon) *RemoteLoader {
|
||||
return &RemoteLoader{GitCommon: gitCommon}
|
||||
}
|
||||
|
||||
func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
var remoteBranchesByRemoteName map[string][]*models.RemoteBranch
|
||||
var remoteBranchesErr error
|
||||
go utils.Safe(func() {
|
||||
defer wg.Done()
|
||||
|
||||
remoteBranchesByRemoteName, remoteBranchesErr = self.getRemoteBranchesByRemoteName()
|
||||
})
|
||||
|
||||
// GetRemotes returns the repo's remotes, without their branches; those are
|
||||
// loaded separately with GetRemoteBranchesByRemoteName, which takes a lot longer
|
||||
// in a repo with many remote branches.
|
||||
func (self *RemoteLoader) GetRemotes() []*models.Remote {
|
||||
remotes := self.getRemotesFromConfig()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if remoteBranchesErr != nil {
|
||||
return nil, remoteBranchesErr
|
||||
}
|
||||
|
||||
for _, remote := range remotes {
|
||||
remote.Branches = remoteBranchesByRemoteName[remote.Name]
|
||||
}
|
||||
|
||||
// now lets sort our remotes by name alphabetically
|
||||
slices.SortFunc(remotes, func(a, b *models.Remote) int {
|
||||
// we want origin at the top because we'll be most likely to want it
|
||||
@@ -56,7 +36,7 @@ func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
|
||||
return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
|
||||
})
|
||||
|
||||
return remotes, nil
|
||||
return remotes
|
||||
}
|
||||
|
||||
func (self *RemoteLoader) getRemotesFromConfig() []*models.Remote {
|
||||
@@ -103,7 +83,9 @@ func (self *RemoteLoader) getRemotesFromConfig() []*models.Remote {
|
||||
return slices.Collect(maps.Values(remotesByName))
|
||||
}
|
||||
|
||||
func (self *RemoteLoader) getRemoteBranchesByRemoteName() (map[string][]*models.RemoteBranch, error) {
|
||||
// GetRemoteBranchesByRemoteName returns all remote branches, keyed by the name
|
||||
// of the remote they belong to.
|
||||
func (self *RemoteLoader) GetRemoteBranchesByRemoteName() (map[string][]*models.RemoteBranch, error) {
|
||||
remoteBranches, err := self.getRemoteBranches()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -470,7 +470,7 @@ func (self *RefreshHelper) performRefresh(options types.RefreshOptions, calledFr
|
||||
}
|
||||
branchesAndRemotesWg.Add(1)
|
||||
refresh("remotes", func() {
|
||||
loadedRemotes, _ = self.refreshRemotes(prevSelectedRemote, env)
|
||||
loadedRemotes = self.refreshRemotes(prevSelectedRemote, env)
|
||||
branchesAndRemotesWg.Done()
|
||||
})
|
||||
}
|
||||
@@ -1544,14 +1544,16 @@ func (self *RefreshHelper) refreshReflogCommits(captured capturedReflogState, en
|
||||
return reflogCommits, nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRemotes(prevSelectedRemote *models.Remote, env refreshEnv) ([]*models.Remote, error) {
|
||||
remotes, err := env.git.Loaders.RemoteLoader.GetRemotes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (self *RefreshHelper) refreshRemotes(prevSelectedRemote *models.Remote, env refreshEnv) []*models.Remote {
|
||||
remotes := env.git.Loaders.RemoteLoader.GetRemotes()
|
||||
|
||||
// Put the remotes in the model before loading their branches below. The map
|
||||
// from branches to pull requests is built from the remotes' URLs (see
|
||||
// GenerateGithubPullRequestMap), and loading the remote branches takes much
|
||||
// longer than loading the remotes themselves. If the map had to wait for the
|
||||
// branches, the pull request icons would show up long after the branch list.
|
||||
self.onUIThreadUnlessRepoChanged(env, func() {
|
||||
self.c.Model().Remotes = remotes
|
||||
self.c.Model().Remotes = remotesWithCarriedOverBranches(remotes, self.c.Model().Remotes)
|
||||
|
||||
hadPrs := len(self.c.Model().PullRequestsMap) != 0
|
||||
self.rebuildPullRequestsMap()
|
||||
@@ -1559,11 +1561,31 @@ func (self *RefreshHelper) refreshRemotes(prevSelectedRemote *models.Remote, env
|
||||
// if we didn't have PRs in the map before but now we do, we need to redraw the branches view
|
||||
self.refreshView(self.c.Contexts().Branches, env)
|
||||
}
|
||||
})
|
||||
|
||||
self.refreshView(self.c.Contexts().Remotes, env)
|
||||
|
||||
remoteBranchesByRemoteName, err := env.git.Loaders.RemoteLoader.GetRemoteBranchesByRemoteName()
|
||||
if err != nil {
|
||||
// The remotes themselves are in the model already; log the failure and
|
||||
// leave them there without their branches.
|
||||
self.c.Log.Error(err)
|
||||
return remotes
|
||||
}
|
||||
|
||||
remotesWithBranches := lo.Map(remotes, func(remote *models.Remote, _ int) *models.Remote {
|
||||
withBranches := *remote
|
||||
withBranches.Branches = remoteBranchesByRemoteName[remote.Name]
|
||||
return &withBranches
|
||||
})
|
||||
|
||||
self.onUIThreadUnlessRepoChanged(env, func() {
|
||||
self.c.Model().Remotes = remotesWithBranches
|
||||
|
||||
// we need to ensure our selected remote branches aren't now outdated
|
||||
if prevSelectedRemote != nil && self.c.Model().RemoteBranches != nil {
|
||||
// find remote now
|
||||
for _, remote := range remotes {
|
||||
for _, remote := range remotesWithBranches {
|
||||
if remote.Name == prevSelectedRemote.Name {
|
||||
self.c.Model().RemoteBranches = remote.Branches
|
||||
break
|
||||
@@ -1574,7 +1596,25 @@ func (self *RefreshHelper) refreshRemotes(prevSelectedRemote *models.Remote, env
|
||||
|
||||
self.refreshView(self.c.Contexts().Remotes, env)
|
||||
self.refreshView(self.c.Contexts().RemoteBranches, env)
|
||||
return remotes, nil
|
||||
return remotesWithBranches
|
||||
}
|
||||
|
||||
// remotesWithCarriedOverBranches returns copies of the freshly loaded remotes,
|
||||
// each carrying the branches of the remote of the same name in the model. Those
|
||||
// branches are the ones the views are showing right now, so carrying them over
|
||||
// keeps the remote branches, and the branch counts in the remotes view, in place
|
||||
// until the fresh ones are loaded. The copies also leave the remotes we were
|
||||
// given untouched, so that the caller can go on reading them off the UI thread.
|
||||
func remotesWithCarriedOverBranches(remotes []*models.Remote, modelRemotes []*models.Remote) []*models.Remote {
|
||||
return lo.Map(remotes, func(remote *models.Remote, _ int) *models.Remote {
|
||||
copied := *remote
|
||||
if previous, found := lo.Find(modelRemotes, func(modelRemote *models.Remote) bool {
|
||||
return modelRemote.Name == remote.Name
|
||||
}); found {
|
||||
copied.Branches = previous.Branches
|
||||
}
|
||||
return &copied
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) loadWorktrees(env refreshEnv) []*models.Worktree {
|
||||
|
||||
Reference in New Issue
Block a user