diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index 2ec315d62..c2aa1418a 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -234,11 +234,22 @@ func (self *MergeAndRebaseHelper) PromptToContinueRebase() error { } func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { - checkedOutBranch := self.refsHelper.GetCheckedOutRef().Name - var disabledReason *types.DisabledReason - if checkedOutBranch == ref { + checkedOutBranch := self.refsHelper.GetCheckedOutRef() + checkedOutBranchName := self.refsHelper.GetCheckedOutRef().Name + var disabledReason, baseBranchDisabledReason *types.DisabledReason + if checkedOutBranchName == ref { disabledReason = &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf} } + + baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(checkedOutBranch, self.refsHelper.c.Model().MainBranches) + if err != nil { + return err + } + if baseBranch == "" { + baseBranch = self.c.Tr.CouldNotDetermineBaseBranch + baseBranchDisabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch} + } + menuItems := []*types.MenuItem{ { Label: utils.ResolvePlaceholderString(self.c.Tr.SimpleRebase, @@ -289,6 +300,31 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { return self.c.PushContext(self.c.Contexts().LocalCommits) }, }, + { + Label: utils.ResolvePlaceholderString(self.c.Tr.RebaseOntoBaseBranch, + map[string]string{"baseBranch": ShortBranchName(baseBranch)}, + ), + Key: 'b', + DisabledReason: baseBranchDisabledReason, + Tooltip: self.c.Tr.RebaseOntoBaseBranchTooltip, + OnPress: func() error { + self.c.LogAction(self.c.Tr.Actions.RebaseBranch) + return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error { + baseCommit := self.c.Modes().MarkedBaseCommit.GetHash() + var err error + if baseCommit != "" { + err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(baseBranch, baseCommit) + } else { + err = self.c.Git().Rebase.RebaseBranch(baseBranch) + } + err = self.CheckMergeOrRebase(err) + if err == nil { + return self.ResetMarkedBaseCommit() + } + return err + }) + }, + }, } title := utils.ResolvePlaceholderString( @@ -296,7 +332,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { self.c.Tr.RebasingFromBaseCommitTitle, self.c.Tr.RebasingTitle), map[string]string{ - "checkedOutBranch": checkedOutBranch, + "checkedOutBranch": checkedOutBranchName, }, ) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 9ca5e5af9..d32aafbf2 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -289,7 +289,9 @@ type TranslationSet struct { RebasingFromBaseCommitTitle string SimpleRebase string InteractiveRebase string + RebaseOntoBaseBranch string InteractiveRebaseTooltip string + RebaseOntoBaseBranchTooltip string MustSelectTodoCommits string ConfirmMerge string FwdNoUpstream string @@ -1257,7 +1259,9 @@ func EnglishTranslationSet() TranslationSet { RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' from marked base", SimpleRebase: "Simple rebase onto '{{.ref}}'", InteractiveRebase: "Interactive rebase onto '{{.ref}}'", + RebaseOntoBaseBranch: "Rebase onto base branch ({{.baseBranch}})", InteractiveRebaseTooltip: "Begin an interactive rebase with a break at the start, so you can update the TODO commits before continuing.", + RebaseOntoBaseBranchTooltip: "Rebase the checked out branch onto its base branch (i.e. the closest main branch).", MustSelectTodoCommits: "When rebasing, this action only works on a selection of TODO commits.", ConfirmMerge: "Are you sure you want to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?", FwdNoUpstream: "Cannot fast-forward a branch with no upstream", diff --git a/pkg/integration/tests/branch/rebase_onto_base_branch.go b/pkg/integration/tests/branch/rebase_onto_base_branch.go new file mode 100644 index 000000000..3944f4fe6 --- /dev/null +++ b/pkg/integration/tests/branch/rebase_onto_base_branch.go @@ -0,0 +1,53 @@ +package branch + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var RebaseOntoBaseBranch = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Rebase the current branch onto its base branch", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.UserConfig.Gui.ShowDivergenceFromBaseBranch = "arrowAndNumber" + }, + SetupRepo: func(shell *Shell) { + shell. + EmptyCommit("master 1"). + EmptyCommit("master 2"). + EmptyCommit("master 3"). + NewBranchFrom("feature", "master^"). + EmptyCommit("feature 1"). + EmptyCommit("feature 2") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits().Lines( + Contains("feature 2"), + Contains("feature 1"), + Contains("master 2"), + Contains("master 1"), + ) + + t.Views().Branches(). + Focus(). + Lines( + Contains("feature ↓1").IsSelected(), + Contains("master"), + ). + Press(keys.Branches.RebaseBranch) + + t.ExpectPopup().Menu(). + Title(Equals("Rebase 'feature'")). + Select(Contains("Rebase onto base branch (master)")). + Confirm() + + t.Views().Commits().Lines( + Contains("feature 2"), + Contains("feature 1"), + Contains("master 3"), + Contains("master 2"), + Contains("master 1"), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 92aaedd23..c1e153a2b 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -51,6 +51,7 @@ var tests = []*components.IntegrationTest{ branch.RebaseCopiedBranch, branch.RebaseDoesNotAutosquash, branch.RebaseFromMarkedBase, + branch.RebaseOntoBaseBranch, branch.RebaseToUpstream, branch.Rename, branch.Reset,