1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00

some more refactoring

This commit is contained in:
Jesse Duffield 2022-01-30 20:03:08 +11:00
parent e2f5fe1016
commit 0a8cff6ab6
31 changed files with 647 additions and 540 deletions

View File

@ -7,7 +7,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models" "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/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -240,7 +239,7 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
HandleConfirm: func() error { HandleConfirm: func() error {
gui.c.LogAction(gui.c.Tr.Actions.Merge) gui.c.LogAction(gui.c.Tr.Actions.Merge)
err := gui.git.Branch.Merge(branchName, git_commands.MergeOpts{}) err := gui.git.Branch.Merge(branchName, git_commands.MergeOpts{})
return gui.checkMergeOrRebase(err) return gui.helpers.rebase.CheckMergeOrRebase(err)
}, },
}) })
} }
@ -274,7 +273,7 @@ func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
HandleConfirm: func() error { HandleConfirm: func() error {
gui.c.LogAction(gui.c.Tr.Actions.RebaseBranch) gui.c.LogAction(gui.c.Tr.Actions.RebaseBranch)
err := gui.git.Rebase.RebaseBranch(selectedBranchName) err := gui.git.Rebase.RebaseBranch(selectedBranchName)
return gui.checkMergeOrRebase(err) return gui.helpers.rebase.CheckMergeOrRebase(err)
}, },
}) })
} }
@ -391,55 +390,6 @@ func (gui *Gui) handleRenameBranch() error {
}) })
} }
func (gui *Gui) handleNewBranchOffCurrentItem() error {
ctx := gui.currentSideListContext()
item, ok := ctx.GetSelectedItem()
if !ok {
return nil
}
message := utils.ResolvePlaceholderString(
gui.c.Tr.NewBranchNameBranchOff,
map[string]string{
"branchName": item.Description(),
},
)
prefilledName := ""
if ctx.GetKey() == context.REMOTE_BRANCHES_CONTEXT_KEY {
// will set to the remote's branch name without the remote name
prefilledName = strings.SplitAfterN(item.ID(), "/", 2)[1]
}
return gui.c.Prompt(types.PromptOpts{
Title: message,
InitialContent: prefilledName,
HandleConfirm: func(response string) error {
gui.c.LogAction(gui.c.Tr.Actions.CreateBranch)
if err := gui.git.Branch.New(sanitizedBranchName(response), item.ID()); err != nil {
return err
}
// if we're currently in the branch commits context then the selected commit
// is about to go to the top of the list
if ctx.GetKey() == context.BRANCH_COMMITS_CONTEXT_KEY {
ctx.GetPanelState().SetSelectedLineIdx(0)
}
if ctx.GetKey() != gui.State.Contexts.Branches.GetKey() {
if err := gui.c.PushContext(gui.State.Contexts.Branches); err != nil {
return err
}
}
gui.State.Panels.Branches.SelectedLineIdx = 0
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
})
}
// sanitizedBranchName will remove all spaces in favor of a dash "-" to meet // sanitizedBranchName will remove all spaces in favor of a dash "-" to meet
// git's branch naming requirement. // git's branch naming requirement.
func sanitizedBranchName(input string) string { func sanitizedBranchName(input string) string {
@ -454,3 +404,12 @@ func (gui *Gui) handleEnterBranch() error {
return gui.switchToSubCommitsContext(branch.RefName()) return gui.switchToSubCommitsContext(branch.RefName())
} }
func (gui *Gui) handleNewBranchOffBranch() error {
selectedBranch := gui.getSelectedBranch()
if selectedBranch == nil {
return nil
}
return gui.helpers.refs.NewBranch(selectedBranch.RefName(), selectedBranch.RefName(), "")
}

View File

@ -1,187 +0,0 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// you can only copy from one context at a time, because the order and position of commits matter
func (gui *Gui) resetCherryPickingIfNecessary(context types.Context) error {
oldContextKey := types.ContextKey(gui.State.Modes.CherryPicking.ContextKey)
if oldContextKey != context.GetKey() {
// need to reset the cherry picking mode
gui.State.Modes.CherryPicking.ContextKey = string(context.GetKey())
gui.State.Modes.CherryPicking.CherryPickedCommits = make([]*models.Commit, 0)
return gui.rerenderContextViewIfPresent(oldContextKey)
}
return nil
}
func (gui *Gui) handleCopyCommit() error {
// get currently selected commit, add the sha to state.
context := gui.currentSideListContext()
if context == nil {
return nil
}
if err := gui.resetCherryPickingIfNecessary(context); err != nil {
return err
}
item, ok := context.GetSelectedItem()
if !ok {
return nil
}
commit, ok := item.(*models.Commit)
if !ok {
return nil
}
// we will un-copy it if it's already copied
for index, cherryPickedCommit := range gui.State.Modes.CherryPicking.CherryPickedCommits {
if commit.Sha == cherryPickedCommit.Sha {
gui.State.Modes.CherryPicking.CherryPickedCommits = append(gui.State.Modes.CherryPicking.CherryPickedCommits[0:index], gui.State.Modes.CherryPicking.CherryPickedCommits[index+1:]...)
return context.HandleRender()
}
}
gui.addCommitToCherryPickedCommits(context.GetPanelState().GetSelectedLineIdx())
return context.HandleRender()
}
func (gui *Gui) cherryPickedCommitShaMap() map[string]bool {
commitShaMap := map[string]bool{}
for _, commit := range gui.State.Modes.CherryPicking.CherryPickedCommits {
commitShaMap[commit.Sha] = true
}
return commitShaMap
}
func (gui *Gui) commitsListForContext() []*models.Commit {
ctx := gui.currentSideListContext()
if ctx == nil {
return nil
}
// using a switch statement, but we should use polymorphism
switch ctx.GetKey() {
case context.BRANCH_COMMITS_CONTEXT_KEY:
return gui.State.Commits
case context.REFLOG_COMMITS_CONTEXT_KEY:
return gui.State.FilteredReflogCommits
case context.SUB_COMMITS_CONTEXT_KEY:
return gui.State.SubCommits
default:
gui.c.Log.Errorf("no commit list for context %s", ctx.GetKey())
return nil
}
}
func (gui *Gui) addCommitToCherryPickedCommits(index int) {
commitShaMap := gui.cherryPickedCommitShaMap()
commitsList := gui.commitsListForContext()
commitShaMap[commitsList[index].Sha] = true
newCommits := []*models.Commit{}
for _, commit := range commitsList {
if commitShaMap[commit.Sha] {
// duplicating just the things we need to put in the rebase TODO list
newCommits = append(newCommits, &models.Commit{Name: commit.Name, Sha: commit.Sha})
}
}
gui.State.Modes.CherryPicking.CherryPickedCommits = newCommits
}
func (gui *Gui) handleCopyCommitRange() error {
// get currently selected commit, add the sha to state.
context := gui.currentSideListContext()
if context == nil {
return nil
}
if err := gui.resetCherryPickingIfNecessary(context); err != nil {
return err
}
commitShaMap := gui.cherryPickedCommitShaMap()
commitsList := gui.commitsListForContext()
selectedLineIdx := context.GetPanelState().GetSelectedLineIdx()
if selectedLineIdx > len(commitsList)-1 {
return nil
}
// find the last commit that is copied that's above our position
// if there are none, startIndex = 0
startIndex := 0
for index, commit := range commitsList[0:selectedLineIdx] {
if commitShaMap[commit.Sha] {
startIndex = index
}
}
for index := startIndex; index <= selectedLineIdx; index++ {
gui.addCommitToCherryPickedCommits(index)
}
return context.HandleRender()
}
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
func (gui *Gui) HandlePasteCommits() error {
return gui.c.Ask(types.AskOpts{
Title: gui.c.Tr.CherryPick,
Prompt: gui.c.Tr.SureCherryPick,
HandleConfirm: func() error {
return gui.c.WithWaitingStatus(gui.c.Tr.CherryPickingStatus, func() error {
gui.c.LogAction(gui.c.Tr.Actions.CherryPick)
err := gui.git.Rebase.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits)
return gui.checkMergeOrRebase(err)
})
},
})
}
func (gui *Gui) exitCherryPickingMode() error {
contextKey := types.ContextKey(gui.State.Modes.CherryPicking.ContextKey)
gui.State.Modes.CherryPicking.ContextKey = ""
gui.State.Modes.CherryPicking.CherryPickedCommits = nil
if contextKey == "" {
gui.c.Log.Warn("context key blank when trying to exit cherry picking mode")
return nil
}
return gui.rerenderContextViewIfPresent(contextKey)
}
func (gui *Gui) rerenderContextViewIfPresent(contextKey types.ContextKey) error {
if contextKey == "" {
return nil
}
context := gui.mustContextForContextKey(contextKey)
viewName := context.GetViewName()
view, err := gui.g.View(viewName)
if err != nil {
gui.c.Log.Error(err)
return nil
}
if types.ContextKey(view.Context) == contextKey {
if err := context.HandleRender(); err != nil {
return err
}
}
return nil
}

View File

@ -83,7 +83,7 @@ func (gui *Gui) handleDiscardOldFileChange() error {
return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error { return gui.c.WithWaitingStatus(gui.c.Tr.RebasingStatus, func() error {
gui.c.LogAction(gui.c.Tr.Actions.DiscardOldFileChange) gui.c.LogAction(gui.c.Tr.Actions.DiscardOldFileChange)
if err := gui.git.Rebase.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil { if err := gui.git.Rebase.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil {
if err := gui.checkMergeOrRebase(err); err != nil { if err := gui.helpers.rebase.CheckMergeOrRebase(err); err != nil {
return err return err
} }
} }

View File

@ -464,13 +464,7 @@ func (gui *Gui) getSideContextSelectedItemId() string {
return "" return ""
} }
item, ok := currentSideContext.GetSelectedItem() return currentSideContext.GetSelectedItemId()
if ok {
return item.ID()
}
return ""
} }
// currently unused // currently unused

View File

@ -62,7 +62,11 @@ func NewCommitFilesContext(
return self return self
} }
func (self *CommitFilesContext) GetSelectedItem() (types.ListItem, bool) { func (self *CommitFilesContext) GetSelectedItemId() string {
item := self.CommitFileTreeViewModel.GetSelectedFileNode() item := self.GetSelectedFileNode()
return item, item != nil if item == nil {
return ""
}
return item.ID()
} }

View File

@ -62,6 +62,15 @@ func NewTagsContext(
return self return self
} }
func (self *TagsContext) GetSelectedItemId() string {
item := self.GetSelectedTag()
if item == nil {
return ""
}
return item.ID()
}
type TagsViewModel struct { type TagsViewModel struct {
*traits.ListCursor *traits.ListCursor
getModel func() []*models.Tag getModel func() []*models.Tag
@ -79,11 +88,6 @@ func (self *TagsViewModel) GetSelectedTag() *models.Tag {
return self.getModel()[self.GetSelectedLineIdx()] return self.getModel()[self.GetSelectedLineIdx()]
} }
func (self *TagsViewModel) GetSelectedItem() (types.ListItem, bool) {
item := self.GetSelectedTag()
return item, item != nil
}
func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel { func NewTagsViewModel(getModel func() []*models.Tag) *TagsViewModel {
self := &TagsViewModel{ self := &TagsViewModel{
getModel: getModel, getModel: getModel,

View File

@ -62,7 +62,11 @@ func NewWorkingTreeContext(
return self return self
} }
func (self *WorkingTreeContext) GetSelectedItem() (types.ListItem, bool) { func (self *WorkingTreeContext) GetSelectedItemId() string {
item := self.FileTreeViewModel.GetSelectedFileNode() item := self.GetSelectedFileNode()
return item, item != nil if item == nil {
return ""
}
return item.ID()
} }

View File

@ -0,0 +1,156 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type CherryPickHelper struct {
c *types.ControllerCommon
git *commands.GitCommand
getContexts func() context.ContextTree
getData func() *cherrypicking.CherryPicking
rebaseHelper *RebaseHelper
}
// I'm using the analogy of copy+paste in the terminology here because it's intuitively what's going on,
// even if in truth we're running git cherry-pick
func NewCherryPickHelper(
c *types.ControllerCommon,
git *commands.GitCommand,
getContexts func() context.ContextTree,
getData func() *cherrypicking.CherryPicking,
rebaseHelper *RebaseHelper,
) *CherryPickHelper {
return &CherryPickHelper{
c: c,
git: git,
getContexts: getContexts,
getData: getData,
rebaseHelper: rebaseHelper,
}
}
func (self *CherryPickHelper) Copy(commit *models.Commit, commitsList []*models.Commit, context types.Context) error {
if err := self.resetIfNecessary(context); err != nil {
return err
}
// we will un-copy it if it's already copied
for index, cherryPickedCommit := range self.getData().CherryPickedCommits {
if commit.Sha == cherryPickedCommit.Sha {
self.getData().CherryPickedCommits = append(
self.getData().CherryPickedCommits[0:index],
self.getData().CherryPickedCommits[index+1:]...,
)
return self.rerender()
}
}
self.add(commit, commitsList)
return self.rerender()
}
func (self *CherryPickHelper) CopyRange(selectedIndex int, commitsList []*models.Commit, context types.Context) error {
if err := self.resetIfNecessary(context); err != nil {
return err
}
commitShaMap := self.CherryPickedCommitShaMap()
// find the last commit that is copied that's above our position
// if there are none, startIndex = 0
startIndex := 0
for index, commit := range commitsList[0:selectedIndex] {
if commitShaMap[commit.Sha] {
startIndex = index
}
}
for index := startIndex; index <= selectedIndex; index++ {
commit := commitsList[index]
self.add(commit, commitsList)
}
return self.rerender()
}
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied.
// Only to be called from the branch commits controller
func (self *CherryPickHelper) Paste() error {
return self.c.Ask(types.AskOpts{
Title: self.c.Tr.CherryPick,
Prompt: self.c.Tr.SureCherryPick,
HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.CherryPick)
err := self.git.Rebase.CherryPickCommits(self.getData().CherryPickedCommits)
return self.rebaseHelper.CheckMergeOrRebase(err)
})
},
})
}
func (self *CherryPickHelper) Reset() error {
self.getData().ContextKey = ""
self.getData().CherryPickedCommits = nil
return self.rerender()
}
func (self *CherryPickHelper) CherryPickedCommitShaMap() map[string]bool {
commitShaMap := map[string]bool{}
for _, commit := range self.getData().CherryPickedCommits {
commitShaMap[commit.Sha] = true
}
return commitShaMap
}
func (self *CherryPickHelper) add(selectedCommit *models.Commit, commitsList []*models.Commit) {
commitShaMap := self.CherryPickedCommitShaMap()
commitShaMap[selectedCommit.Sha] = true
newCommits := []*models.Commit{}
for _, commit := range commitsList {
if commitShaMap[commit.Sha] {
// duplicating just the things we need to put in the rebase TODO list
newCommits = append(newCommits, &models.Commit{Name: commit.Name, Sha: commit.Sha})
}
}
self.getData().CherryPickedCommits = newCommits
}
// you can only copy from one context at a time, because the order and position of commits matter
func (self *CherryPickHelper) resetIfNecessary(context types.Context) error {
oldContextKey := types.ContextKey(self.getData().ContextKey)
if oldContextKey != context.GetKey() {
// need to reset the cherry picking mode
self.getData().ContextKey = string(context.GetKey())
self.getData().CherryPickedCommits = make([]*models.Commit, 0)
}
return nil
}
func (self *CherryPickHelper) rerender() error {
for _, context := range []types.Context{
self.getContexts().BranchCommits,
self.getContexts().ReflogCommits,
self.getContexts().SubCommits,
} {
if err := self.c.PostRefreshUpdate(context); err != nil {
return err
}
}
return nil
}

View File

@ -30,11 +30,13 @@ type LocalCommitsController struct {
git *commands.GitCommand git *commands.GitCommand
tagsHelper *TagsHelper tagsHelper *TagsHelper
refsHelper IRefsHelper refsHelper IRefsHelper
cherryPickHelper *CherryPickHelper
rebaseHelper *RebaseHelper
getSelectedLocalCommit func() *models.Commit getSelectedLocalCommit func() *models.Commit
getCommits func() []*models.Commit getCommits func() []*models.Commit
getSelectedLocalCommitIdx func() int getSelectedLocalCommitIdx func() int
checkMergeOrRebase CheckMergeOrRebase CheckMergeOrRebase CheckMergeOrRebase
pullFiles PullFilesFn pullFiles PullFilesFn
getHostingServiceMgr GetHostingServiceMgrFn getHostingServiceMgr GetHostingServiceMgrFn
switchToCommitFilesContext SwitchToCommitFilesContextFn switchToCommitFilesContext SwitchToCommitFilesContextFn
@ -54,10 +56,12 @@ func NewLocalCommitsController(
git *commands.GitCommand, git *commands.GitCommand,
tagsHelper *TagsHelper, tagsHelper *TagsHelper,
refsHelper IRefsHelper, refsHelper IRefsHelper,
cherryPickHelper *CherryPickHelper,
rebaseHelper *RebaseHelper,
getSelectedLocalCommit func() *models.Commit, getSelectedLocalCommit func() *models.Commit,
getCommits func() []*models.Commit, getCommits func() []*models.Commit,
getSelectedLocalCommitIdx func() int, getSelectedLocalCommitIdx func() int,
checkMergeOrRebase CheckMergeOrRebase, CheckMergeOrRebase CheckMergeOrRebase,
pullFiles PullFilesFn, pullFiles PullFilesFn,
getHostingServiceMgr GetHostingServiceMgrFn, getHostingServiceMgr GetHostingServiceMgrFn,
switchToCommitFilesContext SwitchToCommitFilesContextFn, switchToCommitFilesContext SwitchToCommitFilesContextFn,
@ -74,10 +78,12 @@ func NewLocalCommitsController(
git: git, git: git,
tagsHelper: tagsHelper, tagsHelper: tagsHelper,
refsHelper: refsHelper, refsHelper: refsHelper,
cherryPickHelper: cherryPickHelper,
rebaseHelper: rebaseHelper,
getSelectedLocalCommit: getSelectedLocalCommit, getSelectedLocalCommit: getSelectedLocalCommit,
getCommits: getCommits, getCommits: getCommits,
getSelectedLocalCommitIdx: getSelectedLocalCommitIdx, getSelectedLocalCommitIdx: getSelectedLocalCommitIdx,
checkMergeOrRebase: checkMergeOrRebase, CheckMergeOrRebase: CheckMergeOrRebase,
pullFiles: pullFiles, pullFiles: pullFiles,
getHostingServiceMgr: getHostingServiceMgr, getHostingServiceMgr: getHostingServiceMgr,
switchToCommitFilesContext: switchToCommitFilesContext, switchToCommitFilesContext: switchToCommitFilesContext,
@ -160,6 +166,27 @@ func (self *LocalCommitsController) Keybindings(
Handler: self.checkSelected(self.handleCommitRevert), Handler: self.checkSelected(self.handleCommitRevert),
Description: self.c.Tr.LcRevertCommit, Description: self.c.Tr.LcRevertCommit,
}, },
{
Key: getKey(config.Universal.New),
Modifier: gocui.ModNone,
Handler: self.checkSelected(self.newBranch),
Description: self.c.Tr.LcCreateNewBranchFromCommit,
},
{
Key: getKey(config.Commits.CherryPickCopy),
Handler: self.checkSelected(self.copy),
Description: self.c.Tr.LcCherryPickCopy,
},
{
Key: getKey(config.Commits.CherryPickCopyRange),
Handler: self.checkSelected(self.copyRange),
Description: self.c.Tr.LcCherryPickCopyRange,
},
{
Key: getKey(config.Commits.PasteCommits),
Handler: guards.OutsideFilterMode(self.paste),
Description: self.c.Tr.LcPasteCommits,
},
// overriding these navigation keybindings because we might need to load // overriding these navigation keybindings because we might need to load
// more commits on demand // more commits on demand
{ {
@ -380,7 +407,7 @@ func (self *LocalCommitsController) pick() error {
func (self *LocalCommitsController) interactiveRebase(action string) error { func (self *LocalCommitsController) interactiveRebase(action string) error {
err := self.git.Rebase.InteractiveRebase(self.getCommits(), self.getSelectedLocalCommitIdx(), action) err := self.git.Rebase.InteractiveRebase(self.getCommits(), self.getSelectedLocalCommitIdx(), action)
return self.checkMergeOrRebase(err) return self.CheckMergeOrRebase(err)
} }
// handleMidRebaseCommand sees if the selected commit is in fact a rebasing // handleMidRebaseCommand sees if the selected commit is in fact a rebasing
@ -448,7 +475,7 @@ func (self *LocalCommitsController) handleCommitMoveDown() error {
// TODO: use MoveSelectedLine // TODO: use MoveSelectedLine
_ = self.getContext().HandleNextLine() _ = self.getContext().HandleNextLine()
} }
return self.checkMergeOrRebase(err) return self.CheckMergeOrRebase(err)
}) })
} }
@ -483,7 +510,7 @@ func (self *LocalCommitsController) handleCommitMoveUp() error {
if err == nil { if err == nil {
_ = self.getContext().HandlePrevLine() _ = self.getContext().HandlePrevLine()
} }
return self.checkMergeOrRebase(err) return self.CheckMergeOrRebase(err)
}) })
} }
@ -495,7 +522,7 @@ func (self *LocalCommitsController) handleCommitAmendTo() error {
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.AmendCommit) self.c.LogAction(self.c.Tr.Actions.AmendCommit)
err := self.git.Rebase.AmendTo(self.getSelectedLocalCommit().Sha) err := self.git.Rebase.AmendTo(self.getSelectedLocalCommit().Sha)
return self.checkMergeOrRebase(err) return self.CheckMergeOrRebase(err)
}) })
}, },
}) })
@ -601,7 +628,7 @@ func (self *LocalCommitsController) handleSquashAllAboveFixupCommits(commit *mod
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error { return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits) self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
err := self.git.Rebase.SquashAllAboveFixupCommits(commit.Sha) err := self.git.Rebase.SquashAllAboveFixupCommits(commit.Sha)
return self.checkMergeOrRebase(err) return self.CheckMergeOrRebase(err)
}) })
}, },
}) })
@ -781,3 +808,19 @@ func (self *LocalCommitsController) checkSelected(callback func(*models.Commit)
func (self *LocalCommitsController) Context() types.Context { func (self *LocalCommitsController) Context() types.Context {
return self.getContext() return self.getContext()
} }
func (self *LocalCommitsController) newBranch(commit *models.Commit) error {
return self.refsHelper.NewBranch(commit.RefName(), commit.Description(), "")
}
func (self *LocalCommitsController) copy(commit *models.Commit) error {
return self.cherryPickHelper.Copy(commit, self.getCommits(), self.getContext())
}
func (self *LocalCommitsController) copyRange(*models.Commit) error {
return self.cherryPickHelper.CopyRange(self.getContext().GetPanelState().GetSelectedLineIdx(), self.getCommits(), self.getContext())
}
func (self *LocalCommitsController) paste() error {
return self.cherryPickHelper.Paste()
}

View File

@ -0,0 +1,192 @@
package controllers
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type RebaseHelper struct {
c *types.ControllerCommon
getContexts func() context.ContextTree
git *commands.GitCommand
takeOverMergeConflictScrolling func()
}
func NewRebaseHelper(
c *types.ControllerCommon,
getContexts func() context.ContextTree,
git *commands.GitCommand,
takeOverMergeConflictScrolling func(),
) *RebaseHelper {
return &RebaseHelper{
c: c,
getContexts: getContexts,
git: git,
takeOverMergeConflictScrolling: takeOverMergeConflictScrolling,
}
}
type RebaseOption string
const (
REBASE_OPTION_CONTINUE string = "continue"
REBASE_OPTION_ABORT string = "abort"
REBASE_OPTION_SKIP string = "skip"
)
func (self *RebaseHelper) CreateRebaseOptionsMenu() error {
options := []string{REBASE_OPTION_CONTINUE, REBASE_OPTION_ABORT}
if self.git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
options = append(options, REBASE_OPTION_SKIP)
}
menuItems := make([]*types.MenuItem, len(options))
for i, option := range options {
// note to self. Never, EVER, close over loop variables in a function
option := option
menuItems[i] = &types.MenuItem{
DisplayString: option,
OnPress: func() error {
return self.genericMergeCommand(option)
},
}
}
var title string
if self.git.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
title = self.c.Tr.MergeOptionsTitle
} else {
title = self.c.Tr.RebaseOptionsTitle
}
return self.c.Menu(types.CreateMenuOptions{Title: title, Items: menuItems})
}
func (self *RebaseHelper) genericMergeCommand(command string) error {
status := self.git.Status.WorkingTreeState()
if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING {
return self.c.ErrorMsg(self.c.Tr.NotMergingOrRebasing)
}
self.c.LogAction(fmt.Sprintf("Merge/Rebase: %s", command))
commandType := ""
switch status {
case enums.REBASE_MODE_MERGING:
commandType = "merge"
case enums.REBASE_MODE_REBASING:
commandType = "rebase"
default:
// shouldn't be possible to land here
}
// we should end up with a command like 'git merge --continue'
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
if status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig.Git.Merging.ManualCommit {
// TODO: see if we should be calling more of the code from self.Git.Rebase.GenericMergeOrRebaseAction
return self.c.RunSubprocessAndRefresh(
self.git.Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
)
}
result := self.git.Rebase.GenericMergeOrRebaseAction(commandType, command)
if err := self.CheckMergeOrRebase(result); err != nil {
return err
}
return nil
}
var conflictStrings = []string{
"Failed to merge in the changes",
"When you have resolved this problem",
"fix conflicts",
"Resolve all conflicts manually",
}
func isMergeConflictErr(errStr string) bool {
for _, str := range conflictStrings {
if strings.Contains(errStr, str) {
return true
}
}
return false
}
func (self *RebaseHelper) CheckMergeOrRebase(result error) error {
if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
if result == nil {
return nil
} else if strings.Contains(result.Error(), "No changes - did you forget to use") {
return self.genericMergeCommand(REBASE_OPTION_SKIP)
} else if strings.Contains(result.Error(), "The previous cherry-pick is now empty") {
return self.genericMergeCommand(REBASE_OPTION_CONTINUE)
} else if strings.Contains(result.Error(), "No rebase in progress?") {
// assume in this case that we're already done
return nil
} else if isMergeConflictErr(result.Error()) {
return self.c.Ask(types.AskOpts{
Title: self.c.Tr.FoundConflictsTitle,
Prompt: self.c.Tr.FoundConflicts,
HandlersManageFocus: true,
HandleConfirm: func() error {
return self.c.PushContext(self.getContexts().Files)
},
HandleClose: func() error {
if err := self.c.PopContext(); err != nil {
return err
}
return self.genericMergeCommand(REBASE_OPTION_ABORT)
},
})
} else {
return self.c.ErrorMsg(result.Error())
}
}
func (self *RebaseHelper) AbortMergeOrRebaseWithConfirm() error {
// prompt user to confirm that they want to abort, then do it
mode := self.workingTreeStateNoun()
return self.c.Ask(types.AskOpts{
Title: fmt.Sprintf(self.c.Tr.AbortTitle, mode),
Prompt: fmt.Sprintf(self.c.Tr.AbortPrompt, mode),
HandleConfirm: func() error {
return self.genericMergeCommand(REBASE_OPTION_ABORT)
},
})
}
func (self *RebaseHelper) workingTreeStateNoun() string {
workingTreeState := self.git.Status.WorkingTreeState()
switch workingTreeState {
case enums.REBASE_MODE_NONE:
return ""
case enums.REBASE_MODE_MERGING:
return "merge"
default:
return "rebase"
}
}
// PromptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
func (self *RebaseHelper) PromptToContinueRebase() error {
self.takeOverMergeConflictScrolling()
return self.c.Ask(types.AskOpts{
Title: "continue",
Prompt: self.c.Tr.ConflictsResolved,
HandleConfirm: func() error {
return self.genericMergeCommand(REBASE_OPTION_CONTINUE)
},
})
}

View File

@ -22,7 +22,7 @@ type SyncController struct {
getCheckedOutBranch func() *models.Branch getCheckedOutBranch func() *models.Branch
suggestionsHelper ISuggestionsHelper suggestionsHelper ISuggestionsHelper
getSuggestedRemote func() string getSuggestedRemote func() string
checkMergeOrRebase func(error) error CheckMergeOrRebase func(error) error
} }
var _ types.IController = &SyncController{} var _ types.IController = &SyncController{}
@ -33,7 +33,7 @@ func NewSyncController(
getCheckedOutBranch func() *models.Branch, getCheckedOutBranch func() *models.Branch,
suggestionsHelper ISuggestionsHelper, suggestionsHelper ISuggestionsHelper,
getSuggestedRemote func() string, getSuggestedRemote func() string,
checkMergeOrRebase func(error) error, CheckMergeOrRebase func(error) error,
) *SyncController { ) *SyncController {
return &SyncController{ return &SyncController{
c: c, c: c,
@ -42,7 +42,7 @@ func NewSyncController(
getCheckedOutBranch: getCheckedOutBranch, getCheckedOutBranch: getCheckedOutBranch,
suggestionsHelper: suggestionsHelper, suggestionsHelper: suggestionsHelper,
getSuggestedRemote: getSuggestedRemote, getSuggestedRemote: getSuggestedRemote,
checkMergeOrRebase: checkMergeOrRebase, CheckMergeOrRebase: CheckMergeOrRebase,
} }
} }
@ -191,7 +191,7 @@ func (self *SyncController) pullWithLock(opts PullFilesOptions) error {
}, },
) )
return self.checkMergeOrRebase(err) return self.CheckMergeOrRebase(err)
} }
type pushOpts struct { type pushOpts struct {

View File

@ -9,6 +9,7 @@ type IRefsHelper interface {
CheckoutRef(ref string, options types.CheckoutRefOptions) error CheckoutRef(ref string, options types.CheckoutRefOptions) error
CreateGitResetMenu(ref string) error CreateGitResetMenu(ref string) error
ResetToRef(ref string, strength string, envVars []string) error ResetToRef(ref string, strength string, envVars []string) error
NewBranch(from string, fromDescription string, suggestedBranchname string) error
} }
type ISuggestionsHelper interface { type ISuggestionsHelper interface {

View File

@ -53,15 +53,11 @@ func (gui *Gui) currentDiffTerminals() []string {
} }
return nil return nil
default: default:
context := gui.currentSideListContext() itemId := gui.getSideContextSelectedItemId()
if context == nil { if itemId == "" {
return nil return nil
} }
item, ok := context.GetSelectedItem() return []string{itemId}
if !ok {
return nil
}
return []string{item.ID()}
} }
} }

View File

@ -4,7 +4,6 @@ import (
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/types"
) )
// list panel functions // list panel functions
@ -77,19 +76,6 @@ func (gui *Gui) filesRenderToMain() error {
return gui.refreshMainViews(refreshOpts) return gui.refreshMainViews(refreshOpts)
} }
// promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
func (gui *Gui) promptToContinueRebase() error {
gui.takeOverMergeConflictScrolling()
return gui.PopupHandler.Ask(types.AskOpts{
Title: "continue",
Prompt: gui.Tr.ConflictsResolved,
HandleConfirm: func() error {
return gui.genericMergeCommand(REBASE_OPTION_CONTINUE)
},
})
}
func (gui *Gui) onFocusFile() error { func (gui *Gui) onFocusFile() error {
gui.takeOverMergeConflictScrolling() gui.takeOverMergeConflictScrolling()
return nil return nil

View File

@ -74,6 +74,8 @@ type Helpers struct {
files *FilesHelper files *FilesHelper
workingTree *WorkingTreeHelper workingTree *WorkingTreeHelper
tags *controllers.TagsHelper tags *controllers.TagsHelper
rebase *controllers.RebaseHelper
cherryPick *controllers.CherryPickHelper
} }
type Repo string type Repo string
@ -373,7 +375,7 @@ const (
type Modes struct { type Modes struct {
Filtering filtering.Filtering Filtering filtering.Filtering
CherryPicking cherrypicking.CherryPicking CherryPicking *cherrypicking.CherryPicking
Diffing diffing.Diffing Diffing diffing.Diffing
} }
@ -556,10 +558,12 @@ func (gui *Gui) setControllers() {
getState := func() *GuiRepoState { return gui.State } getState := func() *GuiRepoState { return gui.State }
getContexts := func() context.ContextTree { return gui.State.Contexts } getContexts := func() context.ContextTree { return gui.State.Contexts }
// TODO: have a getGit function too // TODO: have a getGit function too
rebaseHelper := controllers.NewRebaseHelper(controllerCommon, getContexts, gui.git, gui.takeOverMergeConflictScrolling)
gui.helpers = &Helpers{ gui.helpers = &Helpers{
refs: NewRefsHelper( refs: NewRefsHelper(
controllerCommon, controllerCommon,
gui.git, gui.git,
getContexts,
getState, getState,
), ),
bisect: controllers.NewBisectHelper(controllerCommon, gui.git), bisect: controllers.NewBisectHelper(controllerCommon, gui.git),
@ -567,6 +571,14 @@ func (gui *Gui) setControllers() {
files: NewFilesHelper(controllerCommon, gui.git, osCommand), files: NewFilesHelper(controllerCommon, gui.git, osCommand),
workingTree: NewWorkingTreeHelper(func() []*models.File { return gui.State.Files }), workingTree: NewWorkingTreeHelper(func() []*models.File { return gui.State.Files }),
tags: controllers.NewTagsHelper(controllerCommon, gui.git), tags: controllers.NewTagsHelper(controllerCommon, gui.git),
rebase: rebaseHelper,
cherryPick: controllers.NewCherryPickHelper(
controllerCommon,
gui.git,
getContexts,
func() *cherrypicking.CherryPicking { return gui.State.Modes.CherryPicking },
rebaseHelper,
),
} }
syncController := controllers.NewSyncController( syncController := controllers.NewSyncController(
@ -575,7 +587,7 @@ func (gui *Gui) setControllers() {
gui.getCheckedOutBranch, gui.getCheckedOutBranch,
gui.helpers.suggestions, gui.helpers.suggestions,
gui.getSuggestedRemote, gui.getSuggestedRemote,
gui.checkMergeOrRebase, gui.helpers.rebase.CheckMergeOrRebase,
) )
gui.Controllers = Controllers{ gui.Controllers = Controllers{
@ -624,10 +636,12 @@ func (gui *Gui) setControllers() {
gui.git, gui.git,
gui.helpers.tags, gui.helpers.tags,
gui.helpers.refs, gui.helpers.refs,
gui.helpers.cherryPick,
gui.helpers.rebase,
gui.getSelectedLocalCommit, gui.getSelectedLocalCommit,
func() []*models.Commit { return gui.State.Commits }, func() []*models.Commit { return gui.State.Commits },
func() int { return gui.State.Panels.Commits.SelectedLineIdx }, func() int { return gui.State.Panels.Commits.SelectedLineIdx },
gui.checkMergeOrRebase, gui.helpers.rebase.CheckMergeOrRebase,
syncController.HandlePull, syncController.HandlePull,
gui.getHostingServiceMgr, gui.getHostingServiceMgr,
gui.SwitchToCommitFilesContext, gui.SwitchToCommitFilesContext,

View File

@ -276,7 +276,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
{ {
ViewName: "", ViewName: "",
Key: gui.getKey(config.Universal.CreateRebaseOptionsMenu), Key: gui.getKey(config.Universal.CreateRebaseOptionsMenu),
Handler: gui.handleCreateRebaseOptionsMenu, Handler: gui.helpers.rebase.CreateRebaseOptionsMenu,
Description: gui.c.Tr.ViewMergeRebaseOptions, Description: gui.c.Tr.ViewMergeRebaseOptions,
OpensMenu: true, OpensMenu: true,
}, },
@ -423,7 +423,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "branches", ViewName: "branches",
Contexts: []string{string(context.LOCAL_BRANCHES_CONTEXT_KEY)}, Contexts: []string{string(context.LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New), Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem, Handler: gui.handleNewBranchOffBranch,
Description: gui.c.Tr.LcNewBranch, Description: gui.c.Tr.LcNewBranch,
}, },
{ {
@ -513,13 +513,6 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Handler: gui.handleEnterRemoteBranch, Handler: gui.handleEnterRemoteBranch,
Description: gui.c.Tr.LcViewCommits, Description: gui.c.Tr.LcViewCommits,
}, },
{
ViewName: "commits",
Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopy),
Handler: gui.handleCopyCommit,
Description: gui.c.Tr.LcCherryPickCopy,
},
{ {
ViewName: "commits", ViewName: "commits",
Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)},
@ -527,33 +520,11 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Handler: gui.handleCopySelectedSideContextItemToClipboard, Handler: gui.handleCopySelectedSideContextItemToClipboard,
Description: gui.c.Tr.LcCopyCommitShaToClipboard, Description: gui.c.Tr.LcCopyCommitShaToClipboard,
}, },
{
ViewName: "commits",
Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopyRange),
Handler: gui.handleCopyCommitRange,
Description: gui.c.Tr.LcCherryPickCopyRange,
},
{
ViewName: "commits",
Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.PasteCommits),
Handler: guards.OutsideFilterMode(gui.HandlePasteCommits),
Description: gui.c.Tr.LcPasteCommits,
},
{
ViewName: "commits",
Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New),
Modifier: gocui.ModNone,
Handler: gui.handleNewBranchOffCurrentItem,
Description: gui.c.Tr.LcCreateNewBranchFromCommit,
},
{ {
ViewName: "commits", ViewName: "commits",
Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick), Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode, Handler: gui.helpers.cherryPick.Reset,
Description: gui.c.Tr.LcResetCherryPick, Description: gui.c.Tr.LcResetCherryPick,
}, },
{ {
@ -582,21 +553,21 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "commits", ViewName: "commits",
Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopy), Key: gui.getKey(config.Commits.CherryPickCopy),
Handler: guards.OutsideFilterMode(gui.handleCopyCommit), Handler: guards.OutsideFilterMode(gui.handleCopyReflogCommit),
Description: gui.c.Tr.LcCherryPickCopy, Description: gui.c.Tr.LcCherryPickCopy,
}, },
{ {
ViewName: "commits", ViewName: "commits",
Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopyRange), Key: gui.getKey(config.Commits.CherryPickCopyRange),
Handler: guards.OutsideFilterMode(gui.handleCopyCommitRange), Handler: guards.OutsideFilterMode(gui.handleCopyReflogCommitRange),
Description: gui.c.Tr.LcCherryPickCopyRange, Description: gui.c.Tr.LcCherryPickCopyRange,
}, },
{ {
ViewName: "commits", ViewName: "commits",
Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick), Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode, Handler: gui.helpers.cherryPick.Reset,
Description: gui.c.Tr.LcResetCherryPick, Description: gui.c.Tr.LcResetCherryPick,
}, },
{ {
@ -632,28 +603,28 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "branches", ViewName: "branches",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New), Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem, Handler: gui.handleNewBranchOffSubCommit,
Description: gui.c.Tr.LcNewBranch, Description: gui.c.Tr.LcNewBranch,
}, },
{ {
ViewName: "branches", ViewName: "branches",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopy), Key: gui.getKey(config.Commits.CherryPickCopy),
Handler: gui.handleCopyCommit, Handler: gui.handleCopySubCommit,
Description: gui.c.Tr.LcCherryPickCopy, Description: gui.c.Tr.LcCherryPickCopy,
}, },
{ {
ViewName: "branches", ViewName: "branches",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopyRange), Key: gui.getKey(config.Commits.CherryPickCopyRange),
Handler: gui.handleCopyCommitRange, Handler: gui.handleCopySubCommitRange,
Description: gui.c.Tr.LcCherryPickCopyRange, Description: gui.c.Tr.LcCherryPickCopyRange,
}, },
{ {
ViewName: "branches", ViewName: "branches",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)}, Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick), Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode, Handler: gui.helpers.cherryPick.Reset,
Description: gui.c.Tr.LcResetCherryPick, Description: gui.c.Tr.LcResetCherryPick,
}, },
{ {
@ -690,7 +661,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
{ {
ViewName: "stash", ViewName: "stash",
Key: gui.getKey(config.Universal.New), Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem, Handler: gui.handleNewBranchOffStashEntry,
Description: gui.c.Tr.LcNewBranch, Description: gui.c.Tr.LcNewBranch,
}, },
{ {
@ -1220,14 +1191,14 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(context.REMOTE_BRANCHES_CONTEXT_KEY)}, Contexts: []string{string(context.REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.Select), Key: gui.getKey(config.Universal.Select),
// gonna use the exact same handler as the 'n' keybinding because everybody wants this to happen when they checkout a remote branch // gonna use the exact same handler as the 'n' keybinding because everybody wants this to happen when they checkout a remote branch
Handler: gui.handleNewBranchOffCurrentItem, Handler: gui.handleNewBranchOffRemoteBranch,
Description: gui.c.Tr.LcCheckout, Description: gui.c.Tr.LcCheckout,
}, },
{ {
ViewName: "branches", ViewName: "branches",
Contexts: []string{string(context.REMOTE_BRANCHES_CONTEXT_KEY)}, Contexts: []string{string(context.REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New), Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem, Handler: gui.handleNewBranchOffRemoteBranch,
Description: gui.c.Tr.LcNewBranch, Description: gui.c.Tr.LcNewBranch,
}, },
{ {

View File

@ -16,8 +16,7 @@ type ListContext struct {
OnRenderToMain func(...types.OnFocusOpts) error OnRenderToMain func(...types.OnFocusOpts) error
OnFocusLost func() error OnFocusLost func() error
// the boolean here tells us whether the item is nil. This is needed because you can't work it out on the calling end once the pointer is wrapped in an interface (unless you want to use reflection) OnGetSelectedItemId func() string
SelectedItem func() (types.ListItem, bool)
OnGetPanelState func() types.IListPanelState OnGetPanelState func() types.IListPanelState
// if this is true, we'll call GetDisplayStrings for just the visible part of the // if this is true, we'll call GetDisplayStrings for just the visible part of the
// view and re-render that. This is useful when you need to render different // view and re-render that. This is useful when you need to render different
@ -56,8 +55,8 @@ func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length) return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
} }
func (self *ListContext) GetSelectedItem() (types.ListItem, bool) { func (self *ListContext) GetSelectedItemId() string {
return self.SelectedItem() return self.OnGetSelectedItemId()
} }
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view // OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view

View File

@ -63,9 +63,12 @@ func (gui *Gui) branchesListContext() types.IListContext {
GetDisplayStrings: func(startIdx int, length int) [][]string { GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref) return presentation.GetBranchListDisplayStrings(gui.State.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref)
}, },
SelectedItem: func() (types.ListItem, bool) { OnGetSelectedItemId: func() string {
item := gui.getSelectedBranch() item := gui.getSelectedBranch()
return item, item != nil if item == nil {
return ""
}
return item.ID()
}, },
} }
} }
@ -85,9 +88,12 @@ func (gui *Gui) remotesListContext() types.IListContext {
GetDisplayStrings: func(startIdx int, length int) [][]string { GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Modes.Diffing.Ref) return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Modes.Diffing.Ref)
}, },
SelectedItem: func() (types.ListItem, bool) { OnGetSelectedItemId: func() string {
item := gui.getSelectedRemote() item := gui.getSelectedRemote()
return item, item != nil if item == nil {
return ""
}
return item.ID()
}, },
} }
} }
@ -107,9 +113,12 @@ func (gui *Gui) remoteBranchesListContext() types.IListContext {
GetDisplayStrings: func(startIdx int, length int) [][]string { GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Modes.Diffing.Ref) return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Modes.Diffing.Ref)
}, },
SelectedItem: func() (types.ListItem, bool) { OnGetSelectedItemId: func() string {
item := gui.getSelectedRemoteBranch() item := gui.getSelectedRemoteBranch()
return item, item != nil if item == nil {
return ""
}
return item.ID()
}, },
} }
} }
@ -163,7 +172,7 @@ func (gui *Gui) branchCommitsListContext() types.IListContext {
return presentation.GetCommitListDisplayStrings( return presentation.GetCommitListDisplayStrings(
gui.State.Commits, gui.State.Commits,
gui.State.ScreenMode != SCREEN_NORMAL, gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(), gui.helpers.cherryPick.CherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref, gui.State.Modes.Diffing.Ref,
parseEmoji, parseEmoji,
selectedCommitSha, selectedCommitSha,
@ -173,9 +182,12 @@ func (gui *Gui) branchCommitsListContext() types.IListContext {
gui.State.BisectInfo, gui.State.BisectInfo,
) )
}, },
SelectedItem: func() (types.ListItem, bool) { OnGetSelectedItemId: func() string {
item := gui.getSelectedLocalCommit() item := gui.getSelectedLocalCommit()
return item, item != nil if item == nil {
return ""
}
return item.ID()
}, },
RenderSelection: true, RenderSelection: true,
} }
@ -205,7 +217,7 @@ func (gui *Gui) subCommitsListContext() types.IListContext {
return presentation.GetCommitListDisplayStrings( return presentation.GetCommitListDisplayStrings(
gui.State.SubCommits, gui.State.SubCommits,
gui.State.ScreenMode != SCREEN_NORMAL, gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(), gui.helpers.cherryPick.CherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref, gui.State.Modes.Diffing.Ref,
parseEmoji, parseEmoji,
selectedCommitSha, selectedCommitSha,
@ -215,9 +227,12 @@ func (gui *Gui) subCommitsListContext() types.IListContext {
git_commands.NewNullBisectInfo(), git_commands.NewNullBisectInfo(),
) )
}, },
SelectedItem: func() (types.ListItem, bool) { OnGetSelectedItemId: func() string {
item := gui.getSelectedSubCommit() item := gui.getSelectedSubCommit()
return item, item != nil if item == nil {
return ""
}
return item.ID()
}, },
RenderSelection: true, RenderSelection: true,
} }
@ -259,14 +274,17 @@ func (gui *Gui) reflogCommitsListContext() types.IListContext {
return presentation.GetReflogCommitListDisplayStrings( return presentation.GetReflogCommitListDisplayStrings(
gui.State.FilteredReflogCommits, gui.State.FilteredReflogCommits,
gui.State.ScreenMode != SCREEN_NORMAL, gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(), gui.helpers.cherryPick.CherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref, gui.State.Modes.Diffing.Ref,
parseEmoji, parseEmoji,
) )
}, },
SelectedItem: func() (types.ListItem, bool) { OnGetSelectedItemId: func() string {
item := gui.getSelectedReflogCommit() item := gui.getSelectedReflogCommit()
return item, item != nil if item == nil {
return ""
}
return item.ID()
}, },
} }
} }
@ -286,9 +304,12 @@ func (gui *Gui) stashListContext() types.IListContext {
GetDisplayStrings: func(startIdx int, length int) [][]string { GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Modes.Diffing.Ref) return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Modes.Diffing.Ref)
}, },
SelectedItem: func() (types.ListItem, bool) { OnGetSelectedItemId: func() string {
item := gui.getSelectedStashEntry() item := gui.getSelectedStashEntry()
return item, item != nil if item == nil {
return ""
}
return item.ID()
}, },
} }
} }
@ -332,9 +353,12 @@ func (gui *Gui) submodulesListContext() types.IListContext {
GetDisplayStrings: func(startIdx int, length int) [][]string { GetDisplayStrings: func(startIdx int, length int) [][]string {
return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules) return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules)
}, },
SelectedItem: func() (types.ListItem, bool) { OnGetSelectedItemId: func() string {
item := gui.getSelectedSubmodule() item := gui.getSelectedSubmodule()
return item, item != nil if item == nil {
return ""
}
return item.ID()
}, },
} }
} }

View File

@ -61,7 +61,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
style.FgCyan, style.FgCyan,
) )
}, },
reset: gui.exitCherryPickingMode, reset: gui.helpers.cherryPick.Reset,
}, },
{ {
isActive: func() bool { isActive: func() bool {
@ -73,7 +73,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
formatWorkingTreeState(workingTreeState), style.FgYellow, formatWorkingTreeState(workingTreeState), style.FgYellow,
) )
}, },
reset: gui.abortMergeOrRebaseWithConfirm, reset: gui.helpers.rebase.AbortMergeOrRebaseWithConfirm,
}, },
{ {
isActive: func() bool { isActive: func() bool {

View File

@ -11,8 +11,8 @@ type CherryPicking struct {
ContextKey string ContextKey string
} }
func New() CherryPicking { func New() *CherryPicking {
return CherryPicking{ return &CherryPicking{
CherryPickedCommits: make([]*models.Commit, 0), CherryPickedCommits: make([]*models.Commit, 0),
ContextKey: "", ContextKey: "",
} }

View File

@ -102,7 +102,7 @@ func (gui *Gui) handleDeletePatchFromCommit() error {
commitIndex := gui.getPatchCommitIndex() commitIndex := gui.getPatchCommitIndex()
gui.c.LogAction(gui.c.Tr.Actions.RemovePatchFromCommit) gui.c.LogAction(gui.c.Tr.Actions.RemovePatchFromCommit)
err := gui.git.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex) err := gui.git.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex)
return gui.checkMergeOrRebase(err) return gui.helpers.rebase.CheckMergeOrRebase(err)
}) })
} }
@ -119,7 +119,7 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error {
commitIndex := gui.getPatchCommitIndex() commitIndex := gui.getPatchCommitIndex()
gui.c.LogAction(gui.c.Tr.Actions.MovePatchToSelectedCommit) gui.c.LogAction(gui.c.Tr.Actions.MovePatchToSelectedCommit)
err := gui.git.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx) err := gui.git.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx)
return gui.checkMergeOrRebase(err) return gui.helpers.rebase.CheckMergeOrRebase(err)
}) })
} }
@ -137,7 +137,7 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error {
commitIndex := gui.getPatchCommitIndex() commitIndex := gui.getPatchCommitIndex()
gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoIndex) gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoIndex)
err := gui.git.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash) err := gui.git.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash)
return gui.checkMergeOrRebase(err) return gui.helpers.rebase.CheckMergeOrRebase(err)
}) })
} }
@ -167,7 +167,7 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
commitIndex := gui.getPatchCommitIndex() commitIndex := gui.getPatchCommitIndex()
gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoNewCommit) gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoNewCommit)
err := gui.git.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex) err := gui.git.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex)
return gui.checkMergeOrRebase(err) return gui.helpers.rebase.CheckMergeOrRebase(err)
}) })
} }

View File

@ -1,156 +0,0 @@
package gui
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type RebaseOption string
const (
REBASE_OPTION_CONTINUE = "continue"
REBASE_OPTION_ABORT = "abort"
REBASE_OPTION_SKIP = "skip"
)
func (gui *Gui) handleCreateRebaseOptionsMenu() error {
options := []string{REBASE_OPTION_CONTINUE, REBASE_OPTION_ABORT}
if gui.git.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
options = append(options, REBASE_OPTION_SKIP)
}
menuItems := make([]*types.MenuItem, len(options))
for i, option := range options {
// note to self. Never, EVER, close over loop variables in a function
option := option
menuItems[i] = &types.MenuItem{
DisplayString: option,
OnPress: func() error {
return gui.genericMergeCommand(option)
},
}
}
var title string
if gui.git.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
title = gui.c.Tr.MergeOptionsTitle
} else {
title = gui.c.Tr.RebaseOptionsTitle
}
return gui.c.Menu(types.CreateMenuOptions{Title: title, Items: menuItems})
}
func (gui *Gui) genericMergeCommand(command string) error {
status := gui.git.Status.WorkingTreeState()
if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING {
return gui.c.ErrorMsg(gui.c.Tr.NotMergingOrRebasing)
}
gui.c.LogAction(fmt.Sprintf("Merge/Rebase: %s", command))
commandType := ""
switch status {
case enums.REBASE_MODE_MERGING:
commandType = "merge"
case enums.REBASE_MODE_REBASING:
commandType = "rebase"
default:
// shouldn't be possible to land here
}
// we should end up with a command like 'git merge --continue'
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
if status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && gui.c.UserConfig.Git.Merging.ManualCommit {
// TODO: see if we should be calling more of the code from gui.Git.Rebase.GenericMergeOrRebaseAction
return gui.runSubprocessWithSuspenseAndRefresh(
gui.git.Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command),
)
}
result := gui.git.Rebase.GenericMergeOrRebaseAction(commandType, command)
if err := gui.checkMergeOrRebase(result); err != nil {
return err
}
return nil
}
var conflictStrings = []string{
"Failed to merge in the changes",
"When you have resolved this problem",
"fix conflicts",
"Resolve all conflicts manually",
}
func isMergeConflictErr(errStr string) bool {
for _, str := range conflictStrings {
if strings.Contains(errStr, str) {
return true
}
}
return false
}
func (gui *Gui) checkMergeOrRebase(result error) error {
if err := gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil {
return err
}
if result == nil {
return nil
} else if strings.Contains(result.Error(), "No changes - did you forget to use") {
return gui.genericMergeCommand(REBASE_OPTION_SKIP)
} else if strings.Contains(result.Error(), "The previous cherry-pick is now empty") {
return gui.genericMergeCommand(REBASE_OPTION_CONTINUE)
} else if strings.Contains(result.Error(), "No rebase in progress?") {
// assume in this case that we're already done
return nil
} else if isMergeConflictErr(result.Error()) {
return gui.c.Ask(types.AskOpts{
Title: gui.c.Tr.FoundConflictsTitle,
Prompt: gui.c.Tr.FoundConflicts,
HandlersManageFocus: true,
HandleConfirm: func() error {
return gui.c.PushContext(gui.State.Contexts.Files)
},
HandleClose: func() error {
if err := gui.returnFromContext(); err != nil {
return err
}
return gui.genericMergeCommand(REBASE_OPTION_ABORT)
},
})
} else {
return gui.c.ErrorMsg(result.Error())
}
}
func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
// prompt user to confirm that they want to abort, then do it
mode := gui.workingTreeStateNoun()
return gui.c.Ask(types.AskOpts{
Title: fmt.Sprintf(gui.c.Tr.AbortTitle, mode),
Prompt: fmt.Sprintf(gui.c.Tr.AbortPrompt, mode),
HandleConfirm: func() error {
return gui.genericMergeCommand(REBASE_OPTION_ABORT)
},
})
}
func (gui *Gui) workingTreeStateNoun() string {
workingTreeState := gui.git.Status.WorkingTreeState()
switch workingTreeState {
case enums.REBASE_MODE_NONE:
return ""
case enums.REBASE_MODE_MERGING:
return "merge"
default:
return "rebase"
}
}

View File

@ -79,3 +79,22 @@ func (gui *Gui) handleViewReflogCommitFiles() error {
WindowName: "commits", WindowName: "commits",
}) })
} }
func (gui *Gui) handleCopyReflogCommit() error {
commit := gui.getSelectedReflogCommit()
if commit == nil {
return nil
}
return gui.helpers.cherryPick.Copy(commit, gui.State.FilteredReflogCommits, gui.State.Contexts.ReflogCommits)
}
func (gui *Gui) handleCopyReflogCommitRange() error {
// just doing this to ensure something is selected
commit := gui.getSelectedReflogCommit()
if commit == nil {
return nil
}
return gui.helpers.cherryPick.CopyRange(gui.State.Contexts.ReflogCommits.GetPanelState().GetSelectedLineIdx(), gui.State.FilteredReflogCommits, gui.State.Contexts.ReflogCommits)
}

View File

@ -408,7 +408,7 @@ func (gui *Gui) refreshStateFiles() error {
} }
if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 { if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
gui.OnUIThread(func() error { return gui.promptToContinueRebase() }) gui.OnUIThread(func() error { return gui.helpers.rebase.PromptToContinueRebase() })
} }
fileTreeViewModel.RWMutex.Lock() fileTreeViewModel.RWMutex.Lock()

View File

@ -6,14 +6,17 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers" "github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
) )
type RefsHelper struct { type RefsHelper struct {
c *types.ControllerCommon c *types.ControllerCommon
git *commands.GitCommand git *commands.GitCommand
getContexts func() context.ContextTree
getState func() *GuiRepoState getState func() *GuiRepoState
} }
@ -21,11 +24,13 @@ type RefsHelper struct {
func NewRefsHelper( func NewRefsHelper(
c *types.ControllerCommon, c *types.ControllerCommon,
git *commands.GitCommand, git *commands.GitCommand,
getContexts func() context.ContextTree,
getState func() *GuiRepoState, getState func() *GuiRepoState,
) *RefsHelper { ) *RefsHelper {
return &RefsHelper{ return &RefsHelper{
c: c, c: c,
git: git, git: git,
getContexts: getContexts,
getState: getState, getState: getState,
} }
} }
@ -134,3 +139,34 @@ func (self *RefsHelper) CreateGitResetMenu(ref string) error {
Items: menuItems, Items: menuItems,
}) })
} }
func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggestedBranchName string) error {
message := utils.ResolvePlaceholderString(
self.c.Tr.NewBranchNameBranchOff,
map[string]string{
"branchName": fromFormattedName,
},
)
return self.c.Prompt(types.PromptOpts{
Title: message,
InitialContent: suggestedBranchName,
HandleConfirm: func(response string) error {
self.c.LogAction(self.c.Tr.Actions.CreateBranch)
if err := self.git.Branch.New(sanitizedBranchName(response), from); err != nil {
return err
}
if self.c.CurrentContext() != self.getContexts().Branches {
if err := self.c.PushContext(self.getContexts().Branches); err != nil {
return err
}
}
self.getContexts().BranchCommits.GetPanelState().SetSelectedLineIdx(0)
self.getContexts().Branches.GetPanelState().SetSelectedLineIdx(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
})
}

View File

@ -2,6 +2,7 @@ package gui
import ( import (
"fmt" "fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -118,3 +119,15 @@ func (gui *Gui) handleEnterRemoteBranch() error {
return gui.switchToSubCommitsContext(selectedBranch.RefName()) return gui.switchToSubCommitsContext(selectedBranch.RefName())
} }
func (gui *Gui) handleNewBranchOffRemoteBranch() error {
selectedBranch := gui.getSelectedRemoteBranch()
if selectedBranch == nil {
return nil
}
// will set to the remote's branch name without the remote name
nameSuggestion := strings.SplitAfterN(selectedBranch.RefName(), "/", 2)[1]
return gui.helpers.refs.NewBranch(selectedBranch.RefName(), selectedBranch.RefName(), nameSuggestion)
}

View File

@ -136,3 +136,12 @@ func (gui *Gui) handleViewStashFiles() error {
WindowName: "stash", WindowName: "stash",
}) })
} }
func (gui *Gui) handleNewBranchOffStashEntry() error {
stashEntry := gui.getSelectedStashEntry()
if stashEntry == nil {
return nil
}
return gui.helpers.refs.NewBranch(stashEntry.RefName(), stashEntry.Description(), "")
}

View File

@ -48,7 +48,7 @@ func (gui *Gui) handleStatusClick() error {
case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING: case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
workingTreeStatus := fmt.Sprintf("(%s)", formatWorkingTreeState(workingTreeState)) workingTreeStatus := fmt.Sprintf("(%s)", formatWorkingTreeState(workingTreeState))
if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) { if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) {
return gui.handleCreateRebaseOptionsMenu() return gui.helpers.rebase.CreateRebaseOptionsMenu()
} }
if cursorInSubstring(cx, upstreamStatus+" "+workingTreeStatus+" ", repoName) { if cursorInSubstring(cx, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
return gui.handleCreateRecentReposMenu() return gui.handleCreateRecentReposMenu()

View File

@ -102,3 +102,31 @@ func (gui *Gui) switchToSubCommitsContext(refName string) error {
return gui.c.PushContext(gui.State.Contexts.SubCommits) return gui.c.PushContext(gui.State.Contexts.SubCommits)
} }
func (gui *Gui) handleNewBranchOffSubCommit() error {
commit := gui.getSelectedSubCommit()
if commit == nil {
return nil
}
return gui.helpers.refs.NewBranch(commit.RefName(), commit.Description(), "")
}
func (gui *Gui) handleCopySubCommit() error {
commit := gui.getSelectedSubCommit()
if commit == nil {
return nil
}
return gui.helpers.cherryPick.Copy(commit, gui.State.SubCommits, gui.State.Contexts.SubCommits)
}
func (gui *Gui) handleCopySubCommitRange() error {
// just doing this to ensure something is selected
commit := gui.getSelectedSubCommit()
if commit == nil {
return nil
}
return gui.helpers.cherryPick.CopyRange(gui.State.Contexts.SubCommits.GetPanelState().GetSelectedLineIdx(), gui.State.SubCommits, gui.State.Contexts.SubCommits)
}

View File

@ -6,8 +6,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
) )
// if Go let me do private struct embedding of structs with public fields (which it should)
// I would just do that. But alas.
type ControllerCommon struct { type ControllerCommon struct {
*common.Common *common.Common
IGuiCommon IGuiCommon

View File

@ -61,8 +61,8 @@ type IController interface {
type IListContext interface { type IListContext interface {
HasKeybindings HasKeybindings
GetSelectedItem() (ListItem, bool)
GetSelectedItemId() string
HandlePrevLine() error HandlePrevLine() error
HandleNextLine() error HandleNextLine() error
HandleScrollLeft() error HandleScrollLeft() error