1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-10 11:10:18 +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/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -240,7 +239,7 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
HandleConfirm: func() error {
gui.c.LogAction(gui.c.Tr.Actions.Merge)
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 {
gui.c.LogAction(gui.c.Tr.Actions.RebaseBranch)
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
// git's branch naming requirement.
func sanitizedBranchName(input string) string {
@ -454,3 +404,12 @@ func (gui *Gui) handleEnterBranch() error {
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 {
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.checkMergeOrRebase(err); err != nil {
if err := gui.helpers.rebase.CheckMergeOrRebase(err); err != nil {
return err
}
}

View File

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

View File

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

View File

@ -62,6 +62,15 @@ func NewTagsContext(
return self
}
func (self *TagsContext) GetSelectedItemId() string {
item := self.GetSelectedTag()
if item == nil {
return ""
}
return item.ID()
}
type TagsViewModel struct {
*traits.ListCursor
getModel func() []*models.Tag
@ -79,11 +88,6 @@ func (self *TagsViewModel) GetSelectedTag() *models.Tag {
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 {
self := &TagsViewModel{
getModel: getModel,

View File

@ -62,7 +62,11 @@ func NewWorkingTreeContext(
return self
}
func (self *WorkingTreeContext) GetSelectedItem() (types.ListItem, bool) {
item := self.FileTreeViewModel.GetSelectedFileNode()
return item, item != nil
func (self *WorkingTreeContext) GetSelectedItemId() string {
item := self.GetSelectedFileNode()
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

@ -24,17 +24,19 @@ type (
)
type LocalCommitsController struct {
c *types.ControllerCommon
getContext func() types.IListContext
os *oscommands.OSCommand
git *commands.GitCommand
tagsHelper *TagsHelper
refsHelper IRefsHelper
c *types.ControllerCommon
getContext func() types.IListContext
os *oscommands.OSCommand
git *commands.GitCommand
tagsHelper *TagsHelper
refsHelper IRefsHelper
cherryPickHelper *CherryPickHelper
rebaseHelper *RebaseHelper
getSelectedLocalCommit func() *models.Commit
getCommits func() []*models.Commit
getSelectedLocalCommitIdx func() int
checkMergeOrRebase CheckMergeOrRebase
CheckMergeOrRebase CheckMergeOrRebase
pullFiles PullFilesFn
getHostingServiceMgr GetHostingServiceMgrFn
switchToCommitFilesContext SwitchToCommitFilesContextFn
@ -54,10 +56,12 @@ func NewLocalCommitsController(
git *commands.GitCommand,
tagsHelper *TagsHelper,
refsHelper IRefsHelper,
cherryPickHelper *CherryPickHelper,
rebaseHelper *RebaseHelper,
getSelectedLocalCommit func() *models.Commit,
getCommits func() []*models.Commit,
getSelectedLocalCommitIdx func() int,
checkMergeOrRebase CheckMergeOrRebase,
CheckMergeOrRebase CheckMergeOrRebase,
pullFiles PullFilesFn,
getHostingServiceMgr GetHostingServiceMgrFn,
switchToCommitFilesContext SwitchToCommitFilesContextFn,
@ -74,10 +78,12 @@ func NewLocalCommitsController(
git: git,
tagsHelper: tagsHelper,
refsHelper: refsHelper,
cherryPickHelper: cherryPickHelper,
rebaseHelper: rebaseHelper,
getSelectedLocalCommit: getSelectedLocalCommit,
getCommits: getCommits,
getSelectedLocalCommitIdx: getSelectedLocalCommitIdx,
checkMergeOrRebase: checkMergeOrRebase,
CheckMergeOrRebase: CheckMergeOrRebase,
pullFiles: pullFiles,
getHostingServiceMgr: getHostingServiceMgr,
switchToCommitFilesContext: switchToCommitFilesContext,
@ -160,6 +166,27 @@ func (self *LocalCommitsController) Keybindings(
Handler: self.checkSelected(self.handleCommitRevert),
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
// more commits on demand
{
@ -380,7 +407,7 @@ func (self *LocalCommitsController) pick() error {
func (self *LocalCommitsController) interactiveRebase(action string) error {
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
@ -448,7 +475,7 @@ func (self *LocalCommitsController) handleCommitMoveDown() error {
// TODO: use MoveSelectedLine
_ = self.getContext().HandleNextLine()
}
return self.checkMergeOrRebase(err)
return self.CheckMergeOrRebase(err)
})
}
@ -483,7 +510,7 @@ func (self *LocalCommitsController) handleCommitMoveUp() error {
if err == nil {
_ = 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 {
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
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 {
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
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 {
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
suggestionsHelper ISuggestionsHelper
getSuggestedRemote func() string
checkMergeOrRebase func(error) error
CheckMergeOrRebase func(error) error
}
var _ types.IController = &SyncController{}
@ -33,7 +33,7 @@ func NewSyncController(
getCheckedOutBranch func() *models.Branch,
suggestionsHelper ISuggestionsHelper,
getSuggestedRemote func() string,
checkMergeOrRebase func(error) error,
CheckMergeOrRebase func(error) error,
) *SyncController {
return &SyncController{
c: c,
@ -42,7 +42,7 @@ func NewSyncController(
getCheckedOutBranch: getCheckedOutBranch,
suggestionsHelper: suggestionsHelper,
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 {

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
// list panel functions
@ -77,19 +76,6 @@ func (gui *Gui) filesRenderToMain() error {
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 {
gui.takeOverMergeConflictScrolling()
return nil

View File

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

View File

@ -276,7 +276,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
{
ViewName: "",
Key: gui.getKey(config.Universal.CreateRebaseOptionsMenu),
Handler: gui.handleCreateRebaseOptionsMenu,
Handler: gui.helpers.rebase.CreateRebaseOptionsMenu,
Description: gui.c.Tr.ViewMergeRebaseOptions,
OpensMenu: true,
},
@ -423,7 +423,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "branches",
Contexts: []string{string(context.LOCAL_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem,
Handler: gui.handleNewBranchOffBranch,
Description: gui.c.Tr.LcNewBranch,
},
{
@ -513,13 +513,6 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Handler: gui.handleEnterRemoteBranch,
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",
Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)},
@ -527,33 +520,11 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Handler: gui.handleCopySelectedSideContextItemToClipboard,
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",
Contexts: []string{string(context.BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode,
Handler: gui.helpers.cherryPick.Reset,
Description: gui.c.Tr.LcResetCherryPick,
},
{
@ -582,21 +553,21 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "commits",
Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopy),
Handler: guards.OutsideFilterMode(gui.handleCopyCommit),
Handler: guards.OutsideFilterMode(gui.handleCopyReflogCommit),
Description: gui.c.Tr.LcCherryPickCopy,
},
{
ViewName: "commits",
Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopyRange),
Handler: guards.OutsideFilterMode(gui.handleCopyCommitRange),
Handler: guards.OutsideFilterMode(gui.handleCopyReflogCommitRange),
Description: gui.c.Tr.LcCherryPickCopyRange,
},
{
ViewName: "commits",
Contexts: []string{string(context.REFLOG_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode,
Handler: gui.helpers.cherryPick.Reset,
Description: gui.c.Tr.LcResetCherryPick,
},
{
@ -632,28 +603,28 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
ViewName: "branches",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem,
Handler: gui.handleNewBranchOffSubCommit,
Description: gui.c.Tr.LcNewBranch,
},
{
ViewName: "branches",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopy),
Handler: gui.handleCopyCommit,
Handler: gui.handleCopySubCommit,
Description: gui.c.Tr.LcCherryPickCopy,
},
{
ViewName: "branches",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.CherryPickCopyRange),
Handler: gui.handleCopyCommitRange,
Handler: gui.handleCopySubCommitRange,
Description: gui.c.Tr.LcCherryPickCopyRange,
},
{
ViewName: "branches",
Contexts: []string{string(context.SUB_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.ResetCherryPick),
Handler: gui.exitCherryPickingMode,
Handler: gui.helpers.cherryPick.Reset,
Description: gui.c.Tr.LcResetCherryPick,
},
{
@ -690,7 +661,7 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
{
ViewName: "stash",
Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem,
Handler: gui.handleNewBranchOffStashEntry,
Description: gui.c.Tr.LcNewBranch,
},
{
@ -1220,14 +1191,14 @@ func (gui *Gui) GetInitialKeybindings() []*types.Binding {
Contexts: []string{string(context.REMOTE_BRANCHES_CONTEXT_KEY)},
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
Handler: gui.handleNewBranchOffCurrentItem,
Handler: gui.handleNewBranchOffRemoteBranch,
Description: gui.c.Tr.LcCheckout,
},
{
ViewName: "branches",
Contexts: []string{string(context.REMOTE_BRANCHES_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.New),
Handler: gui.handleNewBranchOffCurrentItem,
Handler: gui.handleNewBranchOffRemoteBranch,
Description: gui.c.Tr.LcNewBranch,
},
{

View File

@ -16,9 +16,8 @@ type ListContext struct {
OnRenderToMain func(...types.OnFocusOpts) 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)
SelectedItem func() (types.ListItem, bool)
OnGetPanelState func() types.IListPanelState
OnGetSelectedItemId func() string
OnGetPanelState func() types.IListPanelState
// 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
// content based on the selection (e.g. for showing the selected commit)
@ -56,8 +55,8 @@ func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
}
func (self *ListContext) GetSelectedItem() (types.ListItem, bool) {
return self.SelectedItem()
func (self *ListContext) GetSelectedItemId() string {
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

View File

@ -63,9 +63,12 @@ func (gui *Gui) branchesListContext() types.IListContext {
GetDisplayStrings: func(startIdx int, length int) [][]string {
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()
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 {
return presentation.GetRemoteListDisplayStrings(gui.State.Remotes, gui.State.Modes.Diffing.Ref)
},
SelectedItem: func() (types.ListItem, bool) {
OnGetSelectedItemId: func() string {
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 {
return presentation.GetRemoteBranchListDisplayStrings(gui.State.RemoteBranches, gui.State.Modes.Diffing.Ref)
},
SelectedItem: func() (types.ListItem, bool) {
OnGetSelectedItemId: func() string {
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(
gui.State.Commits,
gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(),
gui.helpers.cherryPick.CherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref,
parseEmoji,
selectedCommitSha,
@ -173,9 +182,12 @@ func (gui *Gui) branchCommitsListContext() types.IListContext {
gui.State.BisectInfo,
)
},
SelectedItem: func() (types.ListItem, bool) {
OnGetSelectedItemId: func() string {
item := gui.getSelectedLocalCommit()
return item, item != nil
if item == nil {
return ""
}
return item.ID()
},
RenderSelection: true,
}
@ -205,7 +217,7 @@ func (gui *Gui) subCommitsListContext() types.IListContext {
return presentation.GetCommitListDisplayStrings(
gui.State.SubCommits,
gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(),
gui.helpers.cherryPick.CherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref,
parseEmoji,
selectedCommitSha,
@ -215,9 +227,12 @@ func (gui *Gui) subCommitsListContext() types.IListContext {
git_commands.NewNullBisectInfo(),
)
},
SelectedItem: func() (types.ListItem, bool) {
OnGetSelectedItemId: func() string {
item := gui.getSelectedSubCommit()
return item, item != nil
if item == nil {
return ""
}
return item.ID()
},
RenderSelection: true,
}
@ -259,14 +274,17 @@ func (gui *Gui) reflogCommitsListContext() types.IListContext {
return presentation.GetReflogCommitListDisplayStrings(
gui.State.FilteredReflogCommits,
gui.State.ScreenMode != SCREEN_NORMAL,
gui.cherryPickedCommitShaMap(),
gui.helpers.cherryPick.CherryPickedCommitShaMap(),
gui.State.Modes.Diffing.Ref,
parseEmoji,
)
},
SelectedItem: func() (types.ListItem, bool) {
OnGetSelectedItemId: func() string {
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 {
return presentation.GetStashEntryListDisplayStrings(gui.State.StashEntries, gui.State.Modes.Diffing.Ref)
},
SelectedItem: func() (types.ListItem, bool) {
OnGetSelectedItemId: func() string {
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 {
return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules)
},
SelectedItem: func() (types.ListItem, bool) {
OnGetSelectedItemId: func() string {
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,
)
},
reset: gui.exitCherryPickingMode,
reset: gui.helpers.cherryPick.Reset,
},
{
isActive: func() bool {
@ -73,7 +73,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
formatWorkingTreeState(workingTreeState), style.FgYellow,
)
},
reset: gui.abortMergeOrRebaseWithConfirm,
reset: gui.helpers.rebase.AbortMergeOrRebaseWithConfirm,
},
{
isActive: func() bool {

View File

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

View File

@ -102,7 +102,7 @@ func (gui *Gui) handleDeletePatchFromCommit() error {
commitIndex := gui.getPatchCommitIndex()
gui.c.LogAction(gui.c.Tr.Actions.RemovePatchFromCommit)
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()
gui.c.LogAction(gui.c.Tr.Actions.MovePatchToSelectedCommit)
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()
gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoIndex)
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()
gui.c.LogAction(gui.c.Tr.Actions.MovePatchIntoNewCommit)
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",
})
}
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 {
gui.OnUIThread(func() error { return gui.promptToContinueRebase() })
gui.OnUIThread(func() error { return gui.helpers.rebase.PromptToContinueRebase() })
}
fileTreeViewModel.RWMutex.Lock()

View File

@ -6,14 +6,17 @@ import (
"github.com/jesseduffield/lazygit/pkg/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/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type RefsHelper struct {
c *types.ControllerCommon
git *commands.GitCommand
c *types.ControllerCommon
git *commands.GitCommand
getContexts func() context.ContextTree
getState func() *GuiRepoState
}
@ -21,12 +24,14 @@ type RefsHelper struct {
func NewRefsHelper(
c *types.ControllerCommon,
git *commands.GitCommand,
getContexts func() context.ContextTree,
getState func() *GuiRepoState,
) *RefsHelper {
return &RefsHelper{
c: c,
git: git,
getState: getState,
c: c,
git: git,
getContexts: getContexts,
getState: getState,
}
}
@ -134,3 +139,34 @@ func (self *RefsHelper) CreateGitResetMenu(ref string) error {
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 (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@ -118,3 +119,15 @@ func (gui *Gui) handleEnterRemoteBranch() error {
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",
})
}
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:
workingTreeStatus := fmt.Sprintf("(%s)", formatWorkingTreeState(workingTreeState))
if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) {
return gui.handleCreateRebaseOptionsMenu()
return gui.helpers.rebase.CreateRebaseOptionsMenu()
}
if cursorInSubstring(cx, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
return gui.handleCreateRecentReposMenu()

View File

@ -102,3 +102,31 @@ func (gui *Gui) switchToSubCommitsContext(refName string) error {
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"
)
// 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 {
*common.Common
IGuiCommon

View File

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