Bounce REMOTES model updates onto the UI thread

refreshRemotes now loads the remotes on the worker and writes
Model.Remotes, rebuilds the pull-requests map, and updates the selected
remote's RemoteBranches inside an onUIThreadUnlessRepoChanged bounce.

RemotesController.addAndCheckoutRemote read Model.Remotes right after its
SYNC REMOTES refresh to select the newly-added remote; since that write
now bounces, the selection (and the follow-up fetch) move into Then so
they run against the post-refresh model rather than the stale one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-03 14:27:35 +02:00
parent 0f85c2b2b4
commit db5eb6fd39
2 changed files with 37 additions and 29 deletions

View file

@ -942,6 +942,7 @@ func (self *RefreshHelper) refreshReflogCommits() error {
}
func (self *RefreshHelper) refreshRemotes() error {
generation := self.c.State().GetRepoGeneration()
prevSelectedRemote := self.c.Contexts().Remotes.GetSelected()
remotes, err := self.c.Git().Loaders.RemoteLoader.GetRemotes()
@ -949,25 +950,28 @@ func (self *RefreshHelper) refreshRemotes() error {
return err
}
self.c.Model().Remotes = remotes
self.onUIThreadUnlessRepoChanged(generation, func() error {
self.c.Model().Remotes = remotes
hadPrs := len(self.c.Model().PullRequestsMap) != 0
self.rebuildPullRequestsMap()
if !hadPrs && len(self.c.Model().PullRequestsMap) != 0 {
// 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)
}
hadPrs := len(self.c.Model().PullRequestsMap) != 0
self.rebuildPullRequestsMap()
if !hadPrs && len(self.c.Model().PullRequestsMap) != 0 {
// 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)
}
// 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 {
if remote.Name == prevSelectedRemote.Name {
self.c.Model().RemoteBranches = remote.Branches
break
// 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 {
if remote.Name == prevSelectedRemote.Name {
self.c.Model().RemoteBranches = remote.Branches
break
}
}
}
}
return nil
})
self.refreshView(self.c.Contexts().Remotes)
self.refreshView(self.c.Contexts().RemoteBranches)

View file

@ -156,24 +156,28 @@ func (self *RemotesController) addAndCheckoutRemote(remoteName string, remoteUrl
return err
}
// Do a sync refresh of the remotes so that we can select
// the new one. Loading remotes is not expensive, so we can
// afford it.
// Refresh the remotes so that we can select the new one. The remotes model
// update is bounced onto the UI thread, so the selection (which reads
// Model.Remotes) has to run in Then; reading it inline here would see the
// previous model. Loading remotes is not expensive, so a sync refresh is
// affordable.
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.REMOTES},
Mode: types.SYNC,
Then: func() error {
// Select the remote
for idx, remote := range self.c.Model().Remotes {
if remote.Name == remoteName {
self.c.Contexts().Remotes.SetSelection(idx)
break
}
}
// Fetch the remote
return self.fetchAndCheckout(self.c.Contexts().Remotes.GetSelected(), branchToCheckout)
},
})
// Select the remote
for idx, remote := range self.c.Model().Remotes {
if remote.Name == remoteName {
self.c.Contexts().Remotes.SetSelection(idx)
break
}
}
// Fetch the remote
return self.fetchAndCheckout(self.c.Contexts().Remotes.GetSelected(), branchToCheckout)
return nil
}
// Ensures the fork remote exists (matching the given URL).