2022-02-06 15:54:26 +11:00
package helpers
2022-01-30 20:03:08 +11:00
import (
"fmt"
"strings"
2022-03-19 19:12:58 +11:00
"github.com/jesseduffield/generics/slices"
2022-01-30 20:03:08 +11:00
"github.com/jesseduffield/lazygit/pkg/commands"
2022-02-06 15:54:26 +11:00
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
2022-01-30 20:03:08 +11:00
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
2022-02-06 15:54:26 +11:00
"github.com/jesseduffield/lazygit/pkg/utils"
2022-01-30 20:03:08 +11:00
)
2022-02-06 15:54:26 +11:00
type MergeAndRebaseHelper struct {
c * types . HelperCommon
2022-01-31 22:20:28 +11:00
contexts * context . ContextTree
2022-01-30 20:03:08 +11:00
git * commands . GitCommand
takeOverMergeConflictScrolling func ( )
2022-02-06 15:54:26 +11:00
refsHelper * RefsHelper
2022-01-30 20:03:08 +11:00
}
2022-02-06 15:54:26 +11:00
func NewMergeAndRebaseHelper (
c * types . HelperCommon ,
2022-01-31 22:20:28 +11:00
contexts * context . ContextTree ,
2022-01-30 20:03:08 +11:00
git * commands . GitCommand ,
takeOverMergeConflictScrolling func ( ) ,
2022-02-06 15:54:26 +11:00
refsHelper * RefsHelper ,
) * MergeAndRebaseHelper {
return & MergeAndRebaseHelper {
2022-01-30 20:03:08 +11:00
c : c ,
2022-01-31 22:20:28 +11:00
contexts : contexts ,
2022-01-30 20:03:08 +11:00
git : git ,
takeOverMergeConflictScrolling : takeOverMergeConflictScrolling ,
2022-02-06 15:54:26 +11:00
refsHelper : refsHelper ,
2022-01-30 20:03:08 +11:00
}
}
type RebaseOption string
const (
REBASE_OPTION_CONTINUE string = "continue"
REBASE_OPTION_ABORT string = "abort"
REBASE_OPTION_SKIP string = "skip"
)
2022-02-06 15:54:26 +11:00
func ( self * MergeAndRebaseHelper ) CreateRebaseOptionsMenu ( ) error {
2022-03-27 17:19:31 +11:00
type optionAndKey struct {
option string
key types . Key
}
options := [ ] optionAndKey {
{ option : REBASE_OPTION_CONTINUE , key : 'c' } ,
{ option : REBASE_OPTION_ABORT , key : 'a' } ,
}
2022-01-30 20:03:08 +11:00
if self . git . Status . WorkingTreeState ( ) == enums . REBASE_MODE_REBASING {
2022-03-27 17:19:31 +11:00
options = append ( options , optionAndKey {
option : REBASE_OPTION_SKIP , key : 's' ,
} )
2022-01-30 20:03:08 +11:00
}
2022-03-27 17:19:31 +11:00
menuItems := slices . Map ( options , func ( row optionAndKey ) * types . MenuItem {
2022-03-19 19:12:58 +11:00
return & types . MenuItem {
2022-05-08 14:23:32 +10:00
Label : row . option ,
2022-01-30 20:03:08 +11:00
OnPress : func ( ) error {
2022-03-27 17:19:31 +11:00
return self . genericMergeCommand ( row . option )
2022-01-30 20:03:08 +11:00
} ,
2022-03-27 17:19:31 +11:00
Key : row . key ,
2022-01-30 20:03:08 +11:00
}
2022-03-19 19:12:58 +11:00
} )
2022-01-30 20:03:08 +11:00
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 } )
}
2022-02-06 15:54:26 +11:00
func ( self * MergeAndRebaseHelper ) genericMergeCommand ( command string ) error {
2022-01-30 20:03:08 +11:00
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
}
2022-02-06 15:54:26 +11:00
func ( self * MergeAndRebaseHelper ) CheckMergeOrRebase ( result error ) error {
2022-01-30 20:03:08 +11:00
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 ( ) ) {
2022-03-30 08:48:29 +02:00
return self . c . Confirm ( types . ConfirmOpts {
2022-01-30 20:03:08 +11:00
Title : self . c . Tr . FoundConflictsTitle ,
Prompt : self . c . Tr . FoundConflicts ,
HandlersManageFocus : true ,
HandleConfirm : func ( ) error {
2022-01-31 22:20:28 +11:00
return self . c . PushContext ( self . contexts . Files )
2022-01-30 20:03:08 +11:00
} ,
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 ( ) )
}
}
2022-02-06 15:54:26 +11:00
func ( self * MergeAndRebaseHelper ) AbortMergeOrRebaseWithConfirm ( ) error {
2022-01-30 20:03:08 +11:00
// prompt user to confirm that they want to abort, then do it
mode := self . workingTreeStateNoun ( )
2022-03-30 08:48:29 +02:00
return self . c . Confirm ( types . ConfirmOpts {
2022-01-30 20:03:08 +11:00
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 )
} ,
} )
}
2022-02-06 15:54:26 +11:00
func ( self * MergeAndRebaseHelper ) workingTreeStateNoun ( ) string {
2022-01-30 20:03:08 +11:00
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
2022-02-06 15:54:26 +11:00
func ( self * MergeAndRebaseHelper ) PromptToContinueRebase ( ) error {
2022-01-30 20:03:08 +11:00
self . takeOverMergeConflictScrolling ( )
2022-03-30 08:48:29 +02:00
return self . c . Confirm ( types . ConfirmOpts {
2022-01-30 20:03:08 +11:00
Title : "continue" ,
Prompt : self . c . Tr . ConflictsResolved ,
HandleConfirm : func ( ) error {
return self . genericMergeCommand ( REBASE_OPTION_CONTINUE )
} ,
} )
}
2022-02-06 15:54:26 +11:00
func ( self * MergeAndRebaseHelper ) RebaseOntoRef ( ref string ) error {
checkedOutBranch := self . refsHelper . GetCheckedOutRef ( ) . Name
if ref == checkedOutBranch {
return self . c . ErrorMsg ( self . c . Tr . CantRebaseOntoSelf )
}
prompt := utils . ResolvePlaceholderString (
self . c . Tr . ConfirmRebase ,
map [ string ] string {
"checkedOutBranch" : checkedOutBranch ,
"selectedBranch" : ref ,
} ,
)
2022-03-30 08:48:29 +02:00
return self . c . Confirm ( types . ConfirmOpts {
2022-02-06 15:54:26 +11:00
Title : self . c . Tr . RebasingTitle ,
Prompt : prompt ,
HandleConfirm : func ( ) error {
self . c . LogAction ( self . c . Tr . Actions . RebaseBranch )
err := self . git . Rebase . RebaseBranch ( ref )
return self . CheckMergeOrRebase ( err )
} ,
} )
}
func ( self * MergeAndRebaseHelper ) MergeRefIntoCheckedOutBranch ( refName string ) error {
if self . git . Branch . IsHeadDetached ( ) {
return self . c . ErrorMsg ( "Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on" )
}
checkedOutBranchName := self . refsHelper . GetCheckedOutRef ( ) . Name
if checkedOutBranchName == refName {
return self . c . ErrorMsg ( self . c . Tr . CantMergeBranchIntoItself )
}
prompt := utils . ResolvePlaceholderString (
self . c . Tr . ConfirmMerge ,
map [ string ] string {
"checkedOutBranch" : checkedOutBranchName ,
"selectedBranch" : refName ,
} ,
)
2022-03-30 08:48:29 +02:00
return self . c . Confirm ( types . ConfirmOpts {
2022-03-27 17:29:22 +11:00
Title : self . c . Tr . MergeConfirmTitle ,
2022-02-06 15:54:26 +11:00
Prompt : prompt ,
HandleConfirm : func ( ) error {
self . c . LogAction ( self . c . Tr . Actions . Merge )
err := self . git . Branch . Merge ( refName , git_commands . MergeOpts { } )
return self . CheckMergeOrRebase ( err )
} ,
} )
}