From 2b7b6f71eea41f0d33a3212bb759aa6f47c8d50e Mon Sep 17 00:00:00 2001 From: AzraelSec Date: Wed, 16 Aug 2023 16:19:29 +0200 Subject: [PATCH] feat: add a menu to reset current branch to a target branch upstream --- pkg/gui/controllers/branches_controller.go | 150 +++++++++++------- pkg/i18n/english.go | 10 ++ .../tests/branch/reset_to_upstream.go | 102 ++++++++++++ pkg/integration/tests/test_list.go | 1 + 4 files changed, 209 insertions(+), 54 deletions(-) create mode 100644 pkg/integration/tests/branch/reset_to_upstream.go diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go index 8fc621e76..99cb3d63f 100644 --- a/pkg/gui/controllers/branches_controller.go +++ b/pkg/gui/controllers/branches_controller.go @@ -140,34 +140,57 @@ func (self *BranchesController) GetOnRenderToMain() func() error { } func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error { - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.BranchUpstreamOptionsTitle, - Items: []*types.MenuItem{ - { - LabelColumns: []string{self.c.Tr.ViewDivergenceFromUpstream}, - OnPress: func() error { - branch := self.context().GetSelected() - if branch == nil { - return nil + options := []*types.MenuItem{ + { + LabelColumns: []string{self.c.Tr.ViewDivergenceFromUpstream}, + OnPress: func() error { + branch := self.context().GetSelected() + if branch == nil { + return nil + } + + if !branch.RemoteBranchStoredLocally() { + return self.c.ErrorMsg(self.c.Tr.DivergenceNoUpstream) + } + return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{ + Ref: branch, + TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), branch.ShortUpstreamRefName()), + RefToShowDivergenceFrom: branch.FullUpstreamRefName(), + Context: self.context(), + ShowBranchHeads: false, + }) + }, + Key: 'v', + }, + { + LabelColumns: []string{self.c.Tr.UnsetUpstream}, + OnPress: func() error { + if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil { + return self.c.Error(err) + } + if err := self.c.Refresh(types.RefreshOptions{ + Mode: types.SYNC, + Scope: []types.RefreshableView{ + types.BRANCHES, + types.COMMITS, + }, + }); err != nil { + return self.c.Error(err) + } + return nil + }, + Key: 'u', + }, + { + LabelColumns: []string{self.c.Tr.SetUpstream}, + OnPress: func() error { + return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error { + upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream) + if err != nil { + return self.c.Error(err) } - if !branch.RemoteBranchStoredLocally() { - return self.c.ErrorMsg(self.c.Tr.DivergenceNoUpstream) - } - return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{ - Ref: branch, - TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), branch.ShortUpstreamRefName()), - RefToShowDivergenceFrom: branch.FullUpstreamRefName(), - Context: self.context(), - ShowBranchHeads: false, - }) - }, - Key: 'v', - }, - { - LabelColumns: []string{self.c.Tr.UnsetUpstream}, - OnPress: func() error { - if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil { + if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil { return self.c.Error(err) } if err := self.c.Refresh(types.RefreshOptions{ @@ -180,36 +203,55 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error return self.c.Error(err) } return nil - }, - Key: 'u', - }, - { - LabelColumns: []string{self.c.Tr.SetUpstream}, - OnPress: func() error { - return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error { - upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream) - if err != nil { - return self.c.Error(err) - } - - if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil { - return self.c.Error(err) - } - if err := self.c.Refresh(types.RefreshOptions{ - Mode: types.SYNC, - Scope: []types.RefreshableView{ - types.BRANCHES, - types.COMMITS, - }, - }); err != nil { - return self.c.Error(err) - } - return nil - }) - }, - Key: 's', + }) }, + Key: 's', }, + } + + if selectedBranch.IsTrackingRemote() { + upstream := fmt.Sprintf("%s/%s", selectedBranch.UpstreamRemote, selectedBranch.Name) + upstreamResetOptions := utils.ResolvePlaceholderString( + self.c.Tr.ViewUpstreamResetOptions, + map[string]string{"upstream": upstream}, + ) + upstreamResetTooltip := utils.ResolvePlaceholderString( + self.c.Tr.ViewUpstreamResetOptionsTooltip, + map[string]string{"upstream": upstream}, + ) + + options = append(options, &types.MenuItem{ + LabelColumns: []string{upstreamResetOptions}, + OpensMenu: true, + OnPress: func() error { + if selectedBranch.RemoteBranchNotStoredLocally() { + return self.c.ErrorMsg(self.c.Tr.UpstreamNotStoredLocallyError) + } + + err := self.c.Helpers().Refs.CreateGitResetMenu(upstream) + if err != nil { + return self.c.Error(err) + } + return nil + }, + Tooltip: upstreamResetTooltip, + Key: 'g', + }) + } else { + options = append(options, &types.MenuItem{ + LabelColumns: []string{self.c.Tr.ViewUpstreamDisabledResetOptions}, + OpensMenu: true, + OnPress: func() error { + return self.c.ErrorMsg(self.c.Tr.UpstreamNotSetError) + }, + Tooltip: self.c.Tr.UpstreamNotSetError, + Key: 'g', + }) + } + + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.BranchUpstreamOptionsTitle, + Items: options, }) } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 32825a272..87ca8c8ca 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -352,6 +352,9 @@ type TranslationSet struct { DivergenceNoUpstream string DivergenceSectionHeaderLocal string DivergenceSectionHeaderRemote string + ViewUpstreamResetOptions string + ViewUpstreamResetOptionsTooltip string + ViewUpstreamDisabledResetOptions string SetUpstreamTitle string SetUpstreamMessage string EditRemote string @@ -399,6 +402,8 @@ type TranslationSet struct { ViewBranchUpstreamOptions string BranchUpstreamOptionsTitle string ViewBranchUpstreamOptionsTooltip string + UpstreamNotStoredLocallyError string + UpstreamNotSetError string NewGitFlowBranchPrompt string RenameBranchWarning string OpenMenu string @@ -1138,6 +1143,9 @@ func EnglishTranslationSet() TranslationSet { DivergenceNoUpstream: "Cannot show divergence of a branch that has no (locally tracked) upstream", DivergenceSectionHeaderLocal: "Local", DivergenceSectionHeaderRemote: "Remote", + 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", + ViewUpstreamDisabledResetOptions: "Reset checked-out branch onto upstream of selected branch", SetUpstreamTitle: "Set upstream branch", SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'", EditRemote: "Edit remote", @@ -1181,6 +1189,8 @@ func EnglishTranslationSet() TranslationSet { RenameBranch: "Rename branch", BranchUpstreamOptionsTitle: "Upstream options", ViewBranchUpstreamOptionsTooltip: "View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream", + UpstreamNotStoredLocallyError: "Cannot reset to upstream branch because it is not stored locally", + UpstreamNotSetError: "The selected branch has no upstream", ViewBranchUpstreamOptions: "View upstream options", NewBranchNamePrompt: "Enter new branch name for branch", RenameBranchWarning: "This branch is tracking a remote. This action will only rename the local branch name, not the name of the remote branch. Continue?", diff --git a/pkg/integration/tests/branch/reset_to_upstream.go b/pkg/integration/tests/branch/reset_to_upstream.go new file mode 100644 index 000000000..0c749ee2d --- /dev/null +++ b/pkg/integration/tests/branch/reset_to_upstream.go @@ -0,0 +1,102 @@ +package branch + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ResetToUpstream = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Hard reset the current branch to the selected branch upstream", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell. + CloneIntoRemote("origin"). + NewBranch("hard-branch"). + EmptyCommit("hard commit"). + PushBranch("origin", "hard-branch"). + NewBranch("soft-branch"). + EmptyCommit("soft commit"). + PushBranch("origin", "soft-branch"). + NewBranch("base"). + EmptyCommit("base-branch commit"). + CreateFile("file-1", "content"). + GitAdd("file-1"). + Commit("commit with file"). + CreateFile("file-2", "content"). + GitAdd("file-2") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + // soft reset + t.Views().Branches(). + Focus(). + Lines( + Contains("base").IsSelected(), + Contains("soft-branch"), + Contains("hard-branch"), + ). + Press(keys.Branches.SetUpstream). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Upstream options")). + Select(Contains("Reset checked-out branch onto upstream of selected branch")). + Tooltip(Contains("The selected branch has no upstream")). + Confirm() + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Equals("The selected branch has no upstream")). + Confirm() + }). + SelectNextItem(). + Lines( + Contains("base"), + Contains("soft-branch").IsSelected(), + Contains("hard-branch"), + ). + Press(keys.Branches.SetUpstream). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Upstream options")). + Select(Contains("Reset checked-out branch onto origin/soft-branch...")). + Confirm() + + t.ExpectPopup().Menu(). + Title(Equals("Reset to origin/soft-branch")). + Select(Contains("Soft reset")). + Confirm() + }) + t.Views().Commits().Lines( + Contains("soft commit"), + Contains("hard commit"), + ) + t.Views().Files().Lines( + Contains("file-1").Contains("A"), + Contains("file-2").Contains("A"), + ) + + // hard reset + t.Views().Branches(). + Focus(). + Lines( + Contains("base"), + Contains("soft-branch").IsSelected(), + Contains("hard-branch"), + ). + NavigateToLine(Contains("hard-branch")). + Press(keys.Branches.SetUpstream). + Tap(func() { + t.ExpectPopup().Menu(). + Title(Equals("Upstream options")). + Select(Contains("Reset checked-out branch onto origin/hard-branch...")). + Confirm() + + t.ExpectPopup().Menu(). + Title(Equals("Reset to origin/hard-branch")). + Select(Contains("Hard reset")). + Confirm() + }) + t.Views().Commits().Lines(Contains("hard commit")) + t.Views().Files().IsEmpty() + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index c669bbae3..19447b6a1 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -47,6 +47,7 @@ var tests = []*components.IntegrationTest{ branch.RebaseDoesNotAutosquash, branch.RebaseFromMarkedBase, branch.Reset, + branch.ResetToUpstream, branch.ResetUpstream, branch.SetUpstream, branch.ShowDivergenceFromUpstream,