1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/gui/controllers/bisect_controller.go
Stefan Haller 6794149ec8 When bisecting, always mark the current commit as good/bad, not the selected
For marking as good or bad, the current commit is pretty much always the one you
want to mark, not the selected. It's different for skipping; sometimes you know
already that a certain commit doesn't compile, for example, so you might
navigate there and mark it as skipped. So in the case that the current commit is
not the selected one, we now offer two separate menu entries for skipping, one
for the current commit and one for the selected.
2023-07-29 14:44:00 +02:00

294 lines
8.7 KiB
Go

package controllers
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type BisectController struct {
baseController
c *ControllerCommon
}
var _ types.IController = &BisectController{}
func NewBisectController(
common *ControllerCommon,
) *BisectController {
return &BisectController{
baseController: baseController{},
c: common,
}
}
func (self *BisectController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Commits.ViewBisectOptions),
Handler: opts.Guards.OutsideFilterMode(self.checkSelected(self.openMenu)),
Description: self.c.Tr.ViewBisectOptions,
OpensMenu: true,
},
}
return bindings
}
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)
} else {
return self.openStartBisectMenu(info, commit)
}
}
func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
// if there is not yet a 'current' bisect commit, or if we have
// selected the current commit, we need to jump to the next 'current' commit
// after we perform a bisect action. The reason we don't unconditionally jump
// is that sometimes the user will want to go and mark a few commits as skipped
// in a row and they wouldn't want to be jumped back to the current bisect
// commit each time.
// Originally we were allowing the user to, from the bisect menu, select whether
// they were talking about the selected commit or the current bisect commit,
// and that was a bit confusing (and required extra keypresses).
selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha
// we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
// ref, because we'll be reloading our commits in that case.
waitToReselect := selectCurrentAfter && !self.c.Git().Bisect.ReachableFromStart(info)
// If we have a current sha 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.
shaToMark := lo.Ternary(info.GetCurrentSha() != "", info.GetCurrentSha(), commit.Sha)
shortShaToMark := utils.ShortSha(shaToMark)
menuItems := []*types.MenuItem{
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortShaToMark, info.NewTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(shaToMark, info.NewTerm()); err != nil {
return self.c.Error(err)
}
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 'b',
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, shortShaToMark, info.OldTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(shaToMark, info.OldTerm()); err != nil {
return self.c.Error(err)
}
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 'g',
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipCurrent, shortShaToMark),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.c.Git().Bisect.Skip(shaToMark); err != nil {
return self.c.Error(err)
}
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 's',
},
}
if info.GetCurrentSha() != "" && info.GetCurrentSha() != commit.Sha {
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
Label: fmt.Sprintf(self.c.Tr.Bisect.SkipSelected, commit.ShortSha()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.c.Git().Bisect.Skip(commit.Sha); err != nil {
return self.c.Error(err)
}
return self.afterMark(selectCurrentAfter, waitToReselect)
},
Key: 'S',
}))
}
menuItems = append(menuItems, lo.ToPtr(types.MenuItem{
Label: self.c.Tr.Bisect.ResetOption,
OnPress: func() error {
return self.c.Helpers().Bisect.Reset()
},
Key: 'r',
}))
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Bisect.BisectMenuTitle,
Items: menuItems,
})
}
func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Bisect.BisectMenuTitle,
Items: []*types.MenuItem{
{
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
if err := self.c.Git().Bisect.Start(); err != nil {
return self.c.Error(err)
}
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
return self.c.Error(err)
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
Key: 'b',
},
{
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
if err := self.c.Git().Bisect.Start(); err != nil {
return self.c.Error(err)
}
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
return self.c.Error(err)
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
Key: 'g',
},
{
Label: self.c.Tr.Bisect.ChooseTerms,
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.Bisect.OldTermPrompt,
HandleConfirm: func(oldTerm string) error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.Bisect.NewTermPrompt,
HandleConfirm: func(newTerm string) error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
if err := self.c.Git().Bisect.StartWithTerms(oldTerm, newTerm); err != nil {
return self.c.Error(err)
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
})
},
})
},
Key: 't',
},
},
})
}
func (self *BisectController) showBisectCompleteMessage(candidateShas []string) error {
prompt := self.c.Tr.Bisect.CompletePrompt
if len(candidateShas) > 1 {
prompt = self.c.Tr.Bisect.CompletePromptIndeterminate
}
formattedCommits, err := self.c.Git().Commit.GetCommitsOneline(candidateShas)
if err != nil {
return self.c.Error(err)
}
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Bisect.CompleteTitle,
Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.ResetBisect)
if err := self.c.Git().Bisect.Reset(); err != nil {
return self.c.Error(err)
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
})
}
func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
done, candidateShas, err := self.c.Git().Bisect.IsDone()
if err != nil {
return self.c.Error(err)
}
if err := self.afterBisectMarkRefresh(selectCurrent, waitToReselect); err != nil {
return self.c.Error(err)
}
if done {
return self.showBisectCompleteMessage(candidateShas)
}
return nil
}
func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
selectFn := func() {
if selectCurrent {
self.selectCurrentBisectCommit()
}
}
if waitToReselect {
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{}, Then: selectFn})
} else {
selectFn()
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
}
}
func (self *BisectController) selectCurrentBisectCommit() {
info := self.c.Git().Bisect.GetInfo()
if info.GetCurrentSha() != "" {
// find index of commit with that sha, move cursor to that.
for i, commit := range self.c.Model().Commits {
if commit.Sha == info.GetCurrentSha() {
self.context().SetSelectedLineIdx(i)
_ = self.context().HandleFocus(types.OnFocusOpts{})
break
}
}
}
}
func (self *BisectController) checkSelected(callback func(*models.Commit) error) func() error {
return func() error {
commit := self.context().GetSelected()
if commit == nil {
return nil
}
return callback(commit)
}
}
func (self *BisectController) Context() types.Context {
return self.context()
}
func (self *BisectController) context() *context.LocalCommitsContext {
return self.c.Contexts().LocalCommits
}