Split the bisect menu for before and while bisecting

One function built the menu both for when git still waits for a good or
a bad commit, and for while bisecting. Its entries act on the selected
commit in the first case, and on the commit that git wants the user to
test in the second case. Several conditions in the function picked
between the two.

The next commits change the first menu only, so make it a function of
its own. Extract the menu entries that both menus have, so that they
don't get duplicated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-10-31 11:27:58 +01:00
co-authored by Claude Opus 5.5
parent 027a5babeb
commit 9761cec61a
+94 -82
View File
@@ -8,7 +8,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type BisectController struct {
@@ -51,95 +50,66 @@ func (self *BisectController) openMenu(commit *models.Commit) error {
// no shame in getting this directly rather than using the cached value
// given how cheap it is to obtain
info := self.c.Git().Bisect.GetInfo()
if info.Started() {
return self.openMidBisectMenu(info, commit)
if !info.Started() {
return self.openStartBisectMenu(info, commit)
}
return self.openStartBisectMenu(info, commit)
if info.GetCurrentHash() == "" {
return self.openMenuBeforeBisecting(info, commit)
}
return self.openMidBisectMenu(info, commit)
}
// The menu for when a bisect has started, but git doesn't know both a good and
// a bad commit yet. Its entries act on the selected commit.
func (self *BisectController) openMenuBeforeBisecting(info *git_commands.BisectInfo, commit *models.Commit) error {
disabledReason := self.require(self.singleItemSelected())()
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Bisect.BisectMenuTitle,
Items: []*types.MenuItem{
self.markMenuItem(
fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortHash(), info.NewTerm()),
commit.Hash(), info.NewTerm(), disabledReason, 'b'),
self.markMenuItem(
fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortHash(), info.OldTerm()),
commit.Hash(), info.OldTerm(), disabledReason, 'g'),
self.skipMenuItem(
fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, commit.ShortHash()),
commit.Hash(), types.KeepCommitSelectionByHash, disabledReason, 's'),
self.resetMenuItem(),
},
})
}
// The menu for while bisecting. Its entries act on the commit that git wants the
// user to test, except for the one that skips the selected commit.
func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
// If we have a current hash already, then we always want to use that one. If
// not, we're still picking the initial commits before we really start, so
// use the selected commit in that case.
bisecting := info.GetCurrentHash() != ""
hashToMark := lo.Ternary(bisecting, info.GetCurrentHash(), commit.Hash())
shortHashToMark := utils.ShortHash(hashToMark)
// For marking a commit as bad, when we're not already bisecting, we require
// a single item selected, but once we are bisecting, it doesn't matter because
// the action applies to the HEAD commit rather than the selected commit.
var singleItemIfNotBisecting *types.DisabledReason
if !bisecting {
singleItemIfNotBisecting = self.require(self.singleItemSelected())()
}
currentHash := info.GetCurrentHash()
shortCurrentHash := utils.ShortHash(currentHash)
menuItems := []*types.MenuItem{
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortHashToMark, info.NewTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(hashToMark, info.NewTerm()); err != nil {
return err
}
return self.afterMark(types.KeepCommitSelectionByHash)
},
DisabledReason: singleItemIfNotBisecting,
Keys: menuKey('b'),
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortHashToMark, info.OldTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(hashToMark, info.OldTerm()); err != nil {
return err
}
return self.afterMark(types.KeepCommitSelectionByHash)
},
DisabledReason: singleItemIfNotBisecting,
Keys: menuKey('g'),
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, shortHashToMark),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.c.Git().Bisect.Skip(hashToMark); err != nil {
return err
}
return self.afterMark(types.KeepCommitSelectionByHash)
},
DisabledReason: singleItemIfNotBisecting,
Keys: menuKey('s'),
},
self.markMenuItem(
fmt.Sprintf(self.c.Tr.Bisect.Mark, shortCurrentHash, info.NewTerm()),
currentHash, info.NewTerm(), nil, 'b'),
self.markMenuItem(
fmt.Sprintf(self.c.Tr.Bisect.Mark, shortCurrentHash, info.OldTerm()),
currentHash, info.OldTerm(), nil, 'g'),
self.skipMenuItem(
fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, shortCurrentHash),
currentHash, types.KeepCommitSelectionByHash, nil, 's'),
}
if info.GetCurrentHash() != "" && info.GetCurrentHash() != commit.Hash() {
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipSelected, commit.ShortHash()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.c.Git().Bisect.Skip(commit.Hash()); err != nil {
return err
}
// Git may pick a different commit to test now. Keep the skipped
// commit selected anyway, so that the user can go on skipping
// commits near it.
return self.afterMark(types.KeepCommitSelectionIndex)
},
DisabledReason: self.require(self.singleItemSelected())(),
Keys: menuKey('S'),
}))
if currentHash != commit.Hash() {
menuItems = append(menuItems, self.skipMenuItem(
fmt.Sprintf(self.c.Tr.Bisect.SkipSelected, commit.ShortHash()),
commit.Hash(),
// Git may pick a different commit to test now. Keep the skipped
// commit selected anyway, so that the user can go on skipping
// commits near it.
types.KeepCommitSelectionIndex,
self.require(self.singleItemSelected())(),
'S'))
}
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
Label: self.c.Tr.Bisect.ResetOption,
OnPress: func() error {
return self.c.Helpers().Bisect.Reset()
},
Keys: menuKey('r'),
}))
menuItems = append(menuItems, self.resetMenuItem())
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Bisect.BisectMenuTitle,
@@ -147,6 +117,48 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
})
}
func (self *BisectController) markMenuItem(label string, hash string, term string, disabledReason *types.DisabledReason, key rune) *types.MenuItem {
return &types.MenuItem{
Label: label,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(hash, term); err != nil {
return err
}
return self.afterMark(types.KeepCommitSelectionByHash)
},
DisabledReason: disabledReason,
Keys: menuKey(key),
}
}
func (self *BisectController) skipMenuItem(label string, hash string, commitSelection types.CommitSelectionBehavior, disabledReason *types.DisabledReason, key rune) *types.MenuItem {
return &types.MenuItem{
Label: label,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.c.Git().Bisect.Skip(hash); err != nil {
return err
}
return self.afterMark(commitSelection)
},
DisabledReason: disabledReason,
Keys: menuKey(key),
}
}
func (self *BisectController) resetMenuItem() *types.MenuItem {
return &types.MenuItem{
Label: self.c.Tr.Bisect.ResetOption,
OnPress: func() error {
return self.c.Helpers().Bisect.Reset()
},
Keys: menuKey('r'),
}
}
func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Bisect.BisectMenuTitle,