diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go index b46802320fc..62eda703ef3 100644 --- a/pkg/gui/controllers/branches_controller.go +++ b/pkg/gui/controllers/branches_controller.go @@ -100,14 +100,13 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty DisplayOnScreen: true, }, { - Key: opts.GetKey(opts.Config.Branches.RebaseBranch), - Handler: opts.Guards.OutsideFilterMode(self.rebase), - GetDisabledReason: self.require( - self.singleItemSelected(self.notRebasingOntoSelf), - ), - Description: self.c.Tr.RebaseBranch, - Tooltip: self.c.Tr.RebaseBranchTooltip, - DisplayOnScreen: true, + Key: opts.GetKey(opts.Config.Branches.RebaseBranch), + Handler: opts.Guards.OutsideFilterMode(self.withItem(self.rebase)), + GetDisabledReason: self.require(self.singleItemSelected()), + Description: self.c.Tr.RebaseBranch, + Tooltip: self.c.Tr.RebaseBranchTooltip, + OpensMenu: true, + DisplayOnScreen: true, }, { Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch), @@ -633,19 +632,8 @@ func (self *BranchesController) merge() error { return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName) } -func (self *BranchesController) rebase() error { - selectedBranchName := self.context().GetSelected().Name - return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName) -} - -func (self *BranchesController) notRebasingOntoSelf(branch *models.Branch) *types.DisabledReason { - selectedBranchName := branch.Name - checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name - if selectedBranchName == checkedOutBranch { - return &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf} - } - - return nil +func (self *BranchesController) rebase(branch *models.Branch) error { + return self.c.Helpers().MergeAndRebase.RebaseOntoRef(branch.Name) } func (self *BranchesController) fastForward(branch *models.Branch) error { diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index 4bffcfa99ee..c2aa1418ae3 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -234,11 +234,29 @@ func (self *MergeAndRebaseHelper) PromptToContinueRebase() error { } func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { - checkedOutBranch := self.refsHelper.GetCheckedOutRef().Name + 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: self.c.Tr.SimpleRebase, - Key: 's', + Label: utils.ResolvePlaceholderString(self.c.Tr.SimpleRebase, + map[string]string{"ref": ref}, + ), + Key: 's', + DisabledReason: disabledReason, OnPress: func() error { self.c.LogAction(self.c.Tr.Actions.RebaseBranch) return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error { @@ -258,9 +276,12 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { }, }, { - Label: self.c.Tr.InteractiveRebase, - Key: 'i', - Tooltip: self.c.Tr.InteractiveRebaseTooltip, + Label: utils.ResolvePlaceholderString(self.c.Tr.InteractiveRebase, + map[string]string{"ref": ref}, + ), + Key: 'i', + DisabledReason: disabledReason, + Tooltip: self.c.Tr.InteractiveRebaseTooltip, OnPress: func() error { self.c.LogAction(self.c.Tr.Actions.RebaseBranch) baseCommit := self.c.Modes().MarkedBaseCommit.GetHash() @@ -279,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( @@ -286,8 +332,7 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error { self.c.Tr.RebasingFromBaseCommitTitle, self.c.Tr.RebasingTitle), map[string]string{ - "checkedOutBranch": checkedOutBranch, - "ref": ref, + "checkedOutBranch": checkedOutBranchName, }, ) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index a0f205508da..d32aafbf2bc 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 @@ -1253,11 +1255,13 @@ func EnglishTranslationSet() TranslationSet { KeybindingsMenuSectionLocal: "Local", KeybindingsMenuSectionGlobal: "Global", KeybindingsMenuSectionNavigation: "Navigation", - RebasingTitle: "Rebase '{{.checkedOutBranch}}' onto '{{.ref}}'", - RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' from marked base onto '{{.ref}}'", - SimpleRebase: "Simple rebase", - InteractiveRebase: "Interactive rebase", + RebasingTitle: "Rebase '{{.checkedOutBranch}}'", + 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", @@ -1443,7 +1447,7 @@ func EnglishTranslationSet() TranslationSet { ViewUpstreamResetOptions: "Reset checked-out branch onto {{.upstream}}", ViewUpstreamResetOptionsTooltip: "View options for resetting the checked-out branch onto {{upstream}}. Note: this will not reset the selected branch onto the upstream, it will reset the checked-out branch onto the upstream.", ViewUpstreamRebaseOptions: "Rebase checked-out branch onto {{.upstream}}", - ViewUpstreamRebaseOptionsTooltip: "View options for rebasing the checked-out branch onto {{upstream}}. Note: this will not rebase the selected branch onto the upstream, it will rebased the checked-out branch onto the upstream.", + ViewUpstreamRebaseOptionsTooltip: "View options for rebasing the checked-out branch onto {{upstream}}. Note: this will not rebase the selected branch onto the upstream, it will rebase the checked-out branch onto the upstream.", UpstreamGenericName: "upstream of selected branch", SetUpstreamTitle: "Set upstream branch", SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index ae99829aa17..5ca7d7ca69c 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -274,10 +274,10 @@ func polishTranslationSet() TranslationSet { KeybindingsMenuSectionLocal: "Lokalne", KeybindingsMenuSectionGlobal: "Globalne", KeybindingsMenuSectionNavigation: "Nawigacja", - RebasingTitle: "Rebase '{{.checkedOutBranch}}' na '{{.ref}}'", - RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' od oznaczonego commita bazowego na '{{.ref}}'", - SimpleRebase: "Prosty rebase", - InteractiveRebase: "Interaktywny rebase", + RebasingTitle: "Rebase '{{.checkedOutBranch}}'", + RebasingFromBaseCommitTitle: "Rebase '{{.checkedOutBranch}}' od oznaczonego commita bazowego", + SimpleRebase: "Prosty rebase na '{{.ref}}'", + InteractiveRebase: "Interaktywny rebase na '{{.ref}}'", InteractiveRebaseTooltip: "Rozpocznij interaktywny rebase z przerwaniem na początku, abyś mógł zaktualizować commity TODO przed kontynuacją.", MustSelectTodoCommits: "Podczas rebase ta akcja działa tylko na zaznaczonych commitach TODO.", ConfirmMerge: "Czy na pewno chcesz scalić '{{.selectedBranch}}' z '{{.checkedOutBranch}}'?", diff --git a/pkg/i18n/russian.go b/pkg/i18n/russian.go index ebcfaacfe5c..d7b7a61d4ef 100644 --- a/pkg/i18n/russian.go +++ b/pkg/i18n/russian.go @@ -226,9 +226,9 @@ func RussianTranslationSet() TranslationSet { ConflictsResolved: "Все конфликты слияния разрешены. Продолжить?", Continue: "Продолжить", Keybindings: "Связки клавиш", - RebasingTitle: "Перебазировать '{{.checkedOutBranch}}' на '{{.ref}}'", - SimpleRebase: "Простая перебазировка", - InteractiveRebase: "Интерактивная перебазировка", + RebasingTitle: "Перебазировать '{{.checkedOutBranch}}'", + SimpleRebase: "Простая перебазировка на '{{.ref}}'", + InteractiveRebase: "Интерактивная перебазировка на '{{.ref}}'", InteractiveRebaseTooltip: "Начать интерактивную перебазировку с перерыва в начале, чтобы можно было обновить TODO коммиты, прежде чем продолжить.", ConfirmMerge: "Вы уверены, что хотите to merge '{{.selectedBranch}}' into '{{.checkedOutBranch}}'?", FwdNoUpstream: "Невозможно перемотать ветку без upstream-ветки", diff --git a/pkg/i18n/traditional_chinese.go b/pkg/i18n/traditional_chinese.go index 2da3d52c04e..1e80344e4a3 100644 --- a/pkg/i18n/traditional_chinese.go +++ b/pkg/i18n/traditional_chinese.go @@ -256,9 +256,9 @@ func traditionalChineseTranslationSet() TranslationSet { ConflictsResolved: "所有合併衝突都已解決。是否繼續?", Continue: "確認", Keybindings: "鍵盤快捷鍵", - RebasingTitle: "將 '{{.checkedOutBranch}}' 變基至 '{{.ref}}'", - SimpleRebase: "簡單變基", - InteractiveRebase: "互動變基", + RebasingTitle: "將 '{{.checkedOutBranch}}'", + SimpleRebase: "簡單變基 變基至 '{{.ref}}'", + InteractiveRebase: "互動變基 變基至 '{{.ref}}'", InteractiveRebaseTooltip: "開始一個互動變基,以中斷開始,這樣你可以在繼續之前更新TODO提交", ConfirmMerge: "是否將 '{{.selectedBranch}}' 合併至 '{{.checkedOutBranch}}' ?", FwdNoUpstream: "無法快進無上游分支", diff --git a/pkg/integration/tests/branch/rebase.go b/pkg/integration/tests/branch/rebase.go index 66a23510782..c7dc028afa4 100644 --- a/pkg/integration/tests/branch/rebase.go +++ b/pkg/integration/tests/branch/rebase.go @@ -31,7 +31,7 @@ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Branches.RebaseBranch) t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")). + Title(Equals("Rebase 'first-change-branch'")). Select(Contains("Simple rebase")). Confirm() diff --git a/pkg/integration/tests/branch/rebase_abort_on_conflict.go b/pkg/integration/tests/branch/rebase_abort_on_conflict.go index 4eba7762716..47f59d3d1bc 100644 --- a/pkg/integration/tests/branch/rebase_abort_on_conflict.go +++ b/pkg/integration/tests/branch/rebase_abort_on_conflict.go @@ -31,7 +31,7 @@ var RebaseAbortOnConflict = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Branches.RebaseBranch) t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")). + Title(Equals("Rebase 'first-change-branch'")). Select(Contains("Simple rebase")). Confirm() diff --git a/pkg/integration/tests/branch/rebase_and_drop.go b/pkg/integration/tests/branch/rebase_and_drop.go index 4c6712f2369..60ef19e6ad1 100644 --- a/pkg/integration/tests/branch/rebase_and_drop.go +++ b/pkg/integration/tests/branch/rebase_and_drop.go @@ -37,7 +37,7 @@ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Branches.RebaseBranch) t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")). + Title(Equals("Rebase 'first-change-branch'")). Select(Contains("Simple rebase")). Confirm() diff --git a/pkg/integration/tests/branch/rebase_cancel_on_conflict.go b/pkg/integration/tests/branch/rebase_cancel_on_conflict.go index 23b7661b239..03923c81daf 100644 --- a/pkg/integration/tests/branch/rebase_cancel_on_conflict.go +++ b/pkg/integration/tests/branch/rebase_cancel_on_conflict.go @@ -31,7 +31,7 @@ var RebaseCancelOnConflict = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Branches.RebaseBranch) t.ExpectPopup().Menu(). - Title(Equals("Rebase 'first-change-branch' onto 'second-change-branch'")). + Title(Equals("Rebase 'first-change-branch'")). Select(Contains("Simple rebase")). Confirm() diff --git a/pkg/integration/tests/branch/rebase_copied_branch.go b/pkg/integration/tests/branch/rebase_copied_branch.go index faa31093e2f..bc9fcb4a6bb 100644 --- a/pkg/integration/tests/branch/rebase_copied_branch.go +++ b/pkg/integration/tests/branch/rebase_copied_branch.go @@ -42,7 +42,7 @@ var RebaseCopiedBranch = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Branches.RebaseBranch). Tap(func() { t.ExpectPopup().Menu(). - Title(Equals("Rebase 'branch2' onto 'master'")). + Title(Equals("Rebase 'branch2'")). Select(Contains("Simple rebase")). Confirm() }) diff --git a/pkg/integration/tests/branch/rebase_does_not_autosquash.go b/pkg/integration/tests/branch/rebase_does_not_autosquash.go index 66ad870c403..523682410de 100644 --- a/pkg/integration/tests/branch/rebase_does_not_autosquash.go +++ b/pkg/integration/tests/branch/rebase_does_not_autosquash.go @@ -40,7 +40,7 @@ var RebaseDoesNotAutosquash = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Branches.RebaseBranch) t.ExpectPopup().Menu(). - Title(Equals("Rebase 'my-branch' onto 'master'")). + Title(Equals("Rebase 'my-branch'")). Select(Contains("Simple rebase")). Confirm() diff --git a/pkg/integration/tests/branch/rebase_from_marked_base.go b/pkg/integration/tests/branch/rebase_from_marked_base.go index c2ee307e3e7..c26dce9a396 100644 --- a/pkg/integration/tests/branch/rebase_from_marked_base.go +++ b/pkg/integration/tests/branch/rebase_from_marked_base.go @@ -61,7 +61,7 @@ var RebaseFromMarkedBase = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Branches.RebaseBranch) t.ExpectPopup().Menu(). - Title(Equals("Rebase 'active-branch' from marked base onto 'target-branch'")). + Title(Equals("Rebase 'active-branch' from marked base")). Select(Contains("Simple rebase")). Confirm() 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 00000000000..3944f4fe699 --- /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/branch/rebase_to_upstream.go b/pkg/integration/tests/branch/rebase_to_upstream.go index 2469eb012d4..f8b2d6fd138 100644 --- a/pkg/integration/tests/branch/rebase_to_upstream.go +++ b/pkg/integration/tests/branch/rebase_to_upstream.go @@ -67,7 +67,7 @@ var RebaseToUpstream = NewIntegrationTest(NewIntegrationTestArgs{ Select(Contains("Rebase checked-out branch onto origin/master...")). Confirm() t.ExpectPopup().Menu(). - Title(Equals("Rebase 'target' onto 'origin/master'")). + Title(Equals("Rebase 'target'")). Select(Contains("Simple rebase")). Confirm() }) diff --git a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go index 43ac8eb7f3d..771b2e164ee 100644 --- a/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go +++ b/pkg/integration/tests/interactive_rebase/advanced_interactive_rebase.go @@ -39,7 +39,7 @@ var AdvancedInteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{ Press(keys.Branches.RebaseBranch) t.ExpectPopup().Menu(). - Title(Equals(fmt.Sprintf("Rebase '%s' onto '%s'", TOP_BRANCH, BASE_BRANCH))). + Title(Equals(fmt.Sprintf("Rebase '%s'", TOP_BRANCH))). Select(Contains("Interactive rebase")). Confirm() t.Views().Commits(). diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 92aaedd235d..c1e153a2b41 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,