1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-12-01 22:52:01 +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

@@ -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 {