Thread reflog commits explicitly into the branches load

BranchLoader.Load reads the reflog commits to sort branches by recency.
Today it reads them straight from Model.ReflogCommits, which works
because in the recency path the reflog refresh writes that field
synchronously just before the branches refresh reads it (same goroutine,
sequential).

An upcoming commit bounces the reflog model write onto the UI thread, at
which point Model.ReflogCommits wouldn't be updated yet when branches
runs — branches would sort by the previous refresh's reflog. To decouple
the branches load from *when* that write lands, pass the reflog commits
to refreshBranches explicitly: refreshReflogCommits now returns the
commits it loaded, and the recency path hands them straight to
refreshBranches. The non-recency path (branches and reflog run
concurrently, as before) keeps passing Model.ReflogCommits.

Pure refactor: behavior is identical, since the value passed is exactly
what Load read from the model before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-03 15:17:43 +02:00
parent db5eb6fd39
commit bd47106d03

View file

@ -179,10 +179,13 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) {
} else {
branchesAndRemotesWg.Add(1)
refresh("branches", func() {
self.refreshBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex, true)
// Not a recency sort, so branches doesn't depend on the reflog
// being fresh; it runs concurrently with the reflog refresh
// below and reads whatever's in the model, as it always has.
self.refreshBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex, true, self.c.Model().ReflogCommits)
branchesAndRemotesWg.Done()
})
refresh("reflog", func() { _ = self.refreshReflogCommits() })
refresh("reflog", func() { _, _ = self.refreshReflogCommits() })
}
} else if scopeSet.Includes(types.REBASE_COMMITS) {
// the above block handles rebase commits so we only need to call this one
@ -378,27 +381,38 @@ func getModeName(mode types.RefreshMode) string {
// on startup to sort the branches by recency. So we have two phases: INITIAL, and COMPLETE.
// In the initial phase we don't get any reflog commits, but we asynchronously get them
// and refresh the branches after that
func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
// refreshReflogCommitsConsideringStartup returns the reflog commits that the
// caller should hand to refreshBranches for recency sorting. In the COMPLETE
// (normal) case that's the freshly-loaded reflog; in the INITIAL case the
// reflog is loaded asynchronously (and drives its own branches refresh once
// ready), so we return the current model value for the immediate,
// non-recency-sorted branches refresh the caller does in the meantime.
func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() []*models.Commit {
switch self.c.State().GetRepoState().GetStartupStage() {
case types.INITIAL:
self.c.OnWorker(func(_ gocui.Task) error {
_ = self.refreshReflogCommits()
self.refreshBranches(false, true, true)
reflogCommits, _ := self.refreshReflogCommits()
self.refreshBranches(false, true, true, reflogCommits)
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
return nil
})
return self.c.Model().ReflogCommits
case types.COMPLETE:
_ = self.refreshReflogCommits()
reflogCommits, _ := self.refreshReflogCommits()
return reflogCommits
}
return self.c.Model().ReflogCommits
}
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) {
loadBehindCounts := self.c.State().GetRepoState().GetStartupStage() == types.COMPLETE
self.refreshReflogCommitsConsideringStartup()
reflogCommits := self.refreshReflogCommitsConsideringStartup()
self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex, loadBehindCounts)
self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex, loadBehindCounts, reflogCommits)
}
func (self *RefreshHelper) refreshCommitsAndCommitFiles(commitSelection types.CommitSelectionBehavior) {
@ -700,12 +714,12 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() ([]*models.SubmoduleCo
// self.refreshStatus is called at the end of this because that's when we can
// be sure there is a State.Model.Branches array to pick the current branch from
func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSelectionIndex bool, loadBehindCounts bool) {
func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSelectionIndex bool, loadBehindCounts bool, reflogCommits []*models.Commit) {
self.c.Mutexes().RefreshingBranchesMutex.Lock()
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
branches, err := self.c.Git().Loaders.BranchLoader.Load(
self.c.Model().ReflogCommits,
reflogCommits,
self.c.Model().MainBranches,
self.c.Model().Branches,
loadBehindCounts,
@ -900,7 +914,10 @@ func (self *RefreshHelper) refreshStateFiles(background bool, submoduleConfigs [
// This method also manages two things: ReflogCommits and FilteredReflogCommits.
// FilteredReflogCommits are rendered in the reflogs panel, and ReflogCommits
// are used by the branches panel to obtain recency values for sorting.
func (self *RefreshHelper) refreshReflogCommits() error {
// refreshReflogCommits returns the (non-filtered) ReflogCommits it loaded, so
// that a subsequent branches refresh can use them for recency sorting without
// having to read them back out of the model.
func (self *RefreshHelper) refreshReflogCommits() ([]*models.Commit, error) {
// pulling state into its own variable in case it gets swapped out for another state
// and we get an out of bounds exception
model := self.c.Model()
@ -926,19 +943,19 @@ func (self *RefreshHelper) refreshReflogCommits() error {
}
if err := refresh(&model.ReflogCommits, "", ""); err != nil {
return err
return nil, err
}
if self.c.Modes().Filtering.Active() {
if err := refresh(&model.FilteredReflogCommits, self.c.Modes().Filtering.GetPath(), self.c.Modes().Filtering.GetAuthor()); err != nil {
return err
return nil, err
}
} else {
model.FilteredReflogCommits = model.ReflogCommits
}
self.refreshView(self.c.Contexts().ReflogCommits)
return nil
return model.ReflogCommits, nil
}
func (self *RefreshHelper) refreshRemotes() error {