Skip to content

Commit

Permalink
Keep same branch selected when refreshing branches
Browse files Browse the repository at this point in the history
This wasn't necessary before, because the only available branch sorting option
was by recency, so the sort order couldn't change except by checking out
branches. Now, you can sort by committer date, so the branch order can change by
fetching; in this case it's important to keep the same branch selected. One
important use case is to rebase the checked-out branch onto master; you select
master, press "f" to fetch it (this can now change its position in the list),
and then press "r" to rebase. To make this work smoothly it's important to keep
master selected after pressing "f".
  • Loading branch information
stefanhaller committed Jan 19, 2024
1 parent 9867180 commit 2c9b477
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 19 deletions.
2 changes: 1 addition & 1 deletion pkg/gui/controllers/branches_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
}

self.context().SetSelection(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
}

func (self *BranchesController) checkedOutByOtherWorktree(branch *models.Branch) bool {
Expand Down
20 changes: 15 additions & 5 deletions pkg/gui/controllers/helpers/refresh_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
refresh("commits and commit files", self.refreshCommitsAndCommitFiles)

includeWorktreesWithBranches = scopeSet.Includes(types.WORKTREES)
refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches) })
refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches, options.KeepBranchSelectionIndex) })
} else if scopeSet.Includes(types.REBASE_COMMITS) {
// the above block handles rebase commits so we only need to call this one
// if we've asked specifically for rebase commits and not those other things
Expand Down Expand Up @@ -248,7 +248,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
case types.INITIAL:
self.c.OnWorker(func(_ gocui.Task) {
_ = self.refreshReflogCommits()
self.refreshBranches(false)
self.refreshBranches(false, true)
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
})

Expand All @@ -257,10 +257,10 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
}
}

func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool) {
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) {
self.refreshReflogCommitsConsideringStartup()

self.refreshBranches(refreshWorktrees)
self.refreshBranches(refreshWorktrees, keepBranchSelectionIndex)
}

func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
Expand Down Expand Up @@ -425,10 +425,12 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {

// 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) {
func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSelectionIndex bool) {
self.c.Mutexes().RefreshingBranchesMutex.Lock()
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()

prevSelectedBranch := self.c.Contexts().Branches.GetSelected()

reflogCommits := self.c.Model().FilteredReflogCommits
if self.c.Modes().Filtering.Active() && self.c.AppState.LocalBranchSortOrder == "recency" {
// in filter mode we filter our reflog commits to just those containing the path
Expand Down Expand Up @@ -456,6 +458,14 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
}
}

if !keepBranchSelectionIndex && prevSelectedBranch != nil {
_, idx, found := lo.FindIndexOf(self.c.Contexts().Branches.GetItems(),
func(b *models.Branch) bool { return b.Name == prevSelectedBranch.Name })
if found {
self.c.Contexts().Branches.SetSelectedLineIdx(idx)
}
}

if err := self.refreshView(self.c.Contexts().Branches); err != nil {
self.c.Log.Error(err)
}
Expand Down
10 changes: 6 additions & 4 deletions pkg/gui/controllers/helpers/refs_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
self.c.Contexts().LocalCommits.SetLimitCommits(true)
}

refreshOptions := types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true}

return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
Expand All @@ -74,12 +76,12 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions

onSuccess()
if err := self.c.Git().Stash.Pop(0); err != nil {
if err := self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI}); err != nil {
if err := self.c.Refresh(refreshOptions); err != nil {
return err
}
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI})
return self.c.Refresh(refreshOptions)
},
})
}
Expand All @@ -90,7 +92,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
}
onSuccess()

return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI})
return self.c.Refresh(refreshOptions)
})
}

Expand Down Expand Up @@ -218,7 +220,7 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
self.c.Contexts().LocalCommits.SetSelection(0)
self.c.Contexts().Branches.SetSelection(0)

return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
},
})
}
Expand Down
7 changes: 7 additions & 0 deletions pkg/gui/types/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,11 @@ type RefreshOptions struct {
Then func()
Scope []RefreshableView // e.g. []RefreshableView{COMMITS, BRANCHES}. Leave empty to refresh everything
Mode RefreshMode // one of SYNC (default), ASYNC, and BLOCK_UI

// Normally a refresh of the branches tries to keep the same branch selected
// (by name); this is usually important in case the order of branches
// changes. Passing true for KeepBranchSelectionIndex suppresses this and
// keeps the selection index the same. Useful after checking out a detached
// head, and selecting index 0.
KeepBranchSelectionIndex bool
}
4 changes: 2 additions & 2 deletions pkg/integration/tests/custom_commands/suggestions_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ var SuggestionsCommand = NewIntegrationTest(NewIntegrationTestArgs{

t.Views().Branches().
Lines(
Contains("branch-three").IsSelected(),
Contains("branch-four"),
Contains("branch-three"),
Contains("branch-four").IsSelected(),
Contains("branch-two"),
Contains("branch-one"),
)
Expand Down
4 changes: 2 additions & 2 deletions pkg/integration/tests/custom_commands/suggestions_preset.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ var SuggestionsPreset = NewIntegrationTest(NewIntegrationTestArgs{

t.Views().Branches().
Lines(
Contains("branch-three").IsSelected(),
Contains("branch-four"),
Contains("branch-three"),
Contains("branch-four").IsSelected(),
Contains("branch-two"),
Contains("branch-one"),
)
Expand Down
5 changes: 0 additions & 5 deletions pkg/integration/tests/sync/fetch_when_sorted_by_date.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,9 @@ var FetchWhenSortedByDate = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("master")).
Press(keys.Branches.FetchRemote).
Lines(
/* EXPECTED:
Contains("* branch1"),
Contains("master").IsSelected(),
Contains("branch2"),
ACTUAL: */
Contains("* branch1"),
Contains("master"),
Contains("branch2").IsSelected(),
)
},
})

0 comments on commit 2c9b477

Please sign in to comment.