diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index 3a1d21fbd..2626cb6eb 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -59,6 +59,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct f: fast-forward this branch from its upstream g: view reset options R: rename branch + u: set/unset upstream enter: view commits diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index 8778d1105..76981ce4a 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -86,6 +86,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct f: fast-forward deze branch vanaf zijn upstream g: bekijk reset opties R: hernoem branch + u: set/unset upstream enter: bekijk commits diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index 49940447a..b70e43a5e 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -59,6 +59,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct f: fast-forward this branch from its upstream g: wyświetl opcje resetu R: rename branch + u: set/unset upstream enter: view commits diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md index c3f313771..4baa639f0 100644 --- a/docs/keybindings/Keybindings_zh.md +++ b/docs/keybindings/Keybindings_zh.md @@ -74,6 +74,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct f: 从上游快进此分支 g: 查看重置选项 R: 重命名分支 + u: set/unset upstream enter: 查看提交 diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go index fd6994e01..acf40a7bd 100644 --- a/pkg/commands/git_commands/branch.go +++ b/pkg/commands/git_commands/branch.go @@ -109,6 +109,10 @@ func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName stri return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))).Run() } +func (self *BranchCommands) UnsetUpstream(branchName string) error { + return self.cmd.New(fmt.Sprintf("git branch --unset-upstream %s", self.cmd.Quote(branchName))).Run() +} + func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) { return self.GetCommitDifferences("HEAD", "HEAD@{u}") } diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go index 4987210c9..2c851739d 100644 --- a/pkg/gui/controllers.go +++ b/pkg/gui/controllers.go @@ -23,12 +23,13 @@ func (gui *Gui) resetControllers() { ) rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, gui.takeOverMergeConflictScrolling, refsHelper) + suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions) gui.helpers = &helpers.Helpers{ Refs: refsHelper, Host: helpers.NewHostHelper(helperCommon, gui.git), PatchBuilding: helpers.NewPatchBuildingHelper(helperCommon, gui.git), Bisect: helpers.NewBisectHelper(helperCommon, gui.git), - Suggestions: helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions), + Suggestions: suggestionsHelper, Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand), WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, gui.git, model), Tags: helpers.NewTagsHelper(helperCommon, gui.git), @@ -41,6 +42,7 @@ func (gui *Gui) resetControllers() { func() *cherrypicking.CherryPicking { return gui.State.Modes.CherryPicking }, rebaseHelper, ), + Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc), } gui.CustomCommandsClient = custom_commands.NewClient( @@ -64,7 +66,6 @@ func (gui *Gui) resetControllers() { syncController := controllers.NewSyncController( common, - gui.getSuggestedRemote, ) submodulesController := controllers.NewSubmodulesController( diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go index 2caa30525..4bf439f1c 100644 --- a/pkg/gui/controllers/branches_controller.go +++ b/pkg/gui/controllers/branches_controller.go @@ -97,9 +97,50 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty Handler: self.checkSelectedAndReal(self.rename), Description: self.c.Tr.LcRenameBranch, }, + { + Key: opts.GetKey(opts.Config.Branches.SetUpstream), + Handler: self.checkSelected(self.setUpstream), + Description: self.c.Tr.LcSetUnsetUpstream, + OpensMenu: true, + }, } } +func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error { + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.Actions.SetUnsetUpstream, + Items: []*types.MenuItem{ + { + DisplayStrings: []string{self.c.Tr.LcUnsetUpstream}, + OnPress: func() error { + if err := self.git.Branch.UnsetUpstream(selectedBranch.Name); err != nil { + return self.c.Error(err) + } + return nil + }, + Key: 'u', + }, + { + DisplayStrings: []string{self.c.Tr.LcSetUpstream}, + OnPress: func() error { + return self.helpers.Upstream.PromptForUpstream(selectedBranch, func(upstream string) error { + upstreamRemote, upstreamBranch, err := self.helpers.Upstream.ParseUpstream(upstream) + if err != nil { + return self.c.Error(err) + } + + if err := self.git.Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil { + return self.c.Error(err) + } + return nil + }) + }, + Key: 's', + }, + }, + }) +} + func (self *BranchesController) Context() types.Context { return self.context() } diff --git a/pkg/gui/controllers/helpers/helpers.go b/pkg/gui/controllers/helpers/helpers.go index c45852e29..61fdedf46 100644 --- a/pkg/gui/controllers/helpers/helpers.go +++ b/pkg/gui/controllers/helpers/helpers.go @@ -12,6 +12,7 @@ type Helpers struct { Host *HostHelper PatchBuilding *PatchBuildingHelper GPG *GpgHelper + Upstream *UpstreamHelper } func NewStubHelpers() *Helpers { @@ -27,5 +28,6 @@ func NewStubHelpers() *Helpers { Host: &HostHelper{}, PatchBuilding: &PatchBuildingHelper{}, GPG: &GpgHelper{}, + Upstream: &UpstreamHelper{}, } } diff --git a/pkg/gui/controllers/helpers/upstream_helper.go b/pkg/gui/controllers/helpers/upstream_helper.go new file mode 100644 index 000000000..a0307a9d4 --- /dev/null +++ b/pkg/gui/controllers/helpers/upstream_helper.go @@ -0,0 +1,78 @@ +package helpers + +import ( + "errors" + "strings" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type UpstreamHelper struct { + c *types.HelperCommon + model *types.Model + + getRemoteBranchesSuggestionsFunc func(string) func(string) []*types.Suggestion +} + +type IUpstreamHelper interface { + ParseUpstream(string) (string, string, error) + PromptForUpstream(*models.Branch, func(string) error) error + GetSuggestedRemote() string +} + +var _ IUpstreamHelper = &UpstreamHelper{} + +func NewUpstreamHelper( + c *types.HelperCommon, + model *types.Model, + getRemoteBranchesSuggestionsFunc func(string) func(string) []*types.Suggestion, +) *UpstreamHelper { + return &UpstreamHelper{ + c: c, + model: model, + getRemoteBranchesSuggestionsFunc: getRemoteBranchesSuggestionsFunc, + } +} + +func (self *UpstreamHelper) ParseUpstream(upstream string) (string, string, error) { + var upstreamBranch, upstreamRemote string + split := strings.Split(upstream, " ") + if len(split) != 2 { + return "", "", errors.New(self.c.Tr.InvalidUpstream) + } + + upstreamRemote = split[0] + upstreamBranch = split[1] + + return upstreamRemote, upstreamBranch, nil +} + +func (self *UpstreamHelper) PromptForUpstream(currentBranch *models.Branch, onConfirm func(string) error) error { + suggestedRemote := self.GetSuggestedRemote() + + return self.c.Prompt(types.PromptOpts{ + Title: self.c.Tr.EnterUpstream, + InitialContent: suggestedRemote + " " + currentBranch.Name, + FindSuggestionsFunc: self.getRemoteBranchesSuggestionsFunc(" "), + HandleConfirm: onConfirm, + }) +} + +func (self *UpstreamHelper) GetSuggestedRemote() string { + return getSuggestedRemote(self.model.Remotes) +} + +func getSuggestedRemote(remotes []*models.Remote) string { + if len(remotes) == 0 { + return "origin" + } + + for _, remote := range remotes { + if remote.Name == "origin" { + return remote.Name + } + } + + return remotes[0].Name +} diff --git a/pkg/gui/files_panel_test.go b/pkg/gui/controllers/helpers/upstream_helper_test.go similarity index 97% rename from pkg/gui/files_panel_test.go rename to pkg/gui/controllers/helpers/upstream_helper_test.go index 08d5d8838..ac7a6a8bf 100644 --- a/pkg/gui/files_panel_test.go +++ b/pkg/gui/controllers/helpers/upstream_helper_test.go @@ -1,4 +1,4 @@ -package gui +package helpers import ( "testing" diff --git a/pkg/gui/controllers/remote_branches_controller.go b/pkg/gui/controllers/remote_branches_controller.go index b75cf3b13..dcedde8c0 100644 --- a/pkg/gui/controllers/remote_branches_controller.go +++ b/pkg/gui/controllers/remote_branches_controller.go @@ -57,7 +57,7 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts) { Key: opts.GetKey(opts.Config.Branches.SetUpstream), Handler: self.checkSelected(self.setAsUpstream), - Description: self.c.Tr.LcSetUpstream, + Description: self.c.Tr.LcSetAsUpstream, }, { Key: opts.GetKey(opts.Config.Universal.Return), diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go index 69663f32e..046edb369 100644 --- a/pkg/gui/controllers/sync_controller.go +++ b/pkg/gui/controllers/sync_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "errors" "fmt" "strings" @@ -13,21 +12,16 @@ import ( type SyncController struct { baseController *controllerCommon - - getSuggestedRemote func() string } var _ types.IController = &SyncController{} func NewSyncController( common *controllerCommon, - getSuggestedRemote func() string, ) *SyncController { return &SyncController{ baseController: baseController{}, controllerCommon: common, - - getSuggestedRemote: getSuggestedRemote, } } @@ -85,8 +79,8 @@ func (self *SyncController) push(currentBranch *models.Branch) error { if self.git.Config.GetPushToCurrent() { return self.pushAux(pushOpts{setUpstream: true}) } else { - return self.promptForUpstream(currentBranch, func(upstream string) error { - upstreamRemote, upstreamBranch, err := self.parseUpstream(upstream) + return self.helpers.Upstream.PromptForUpstream(currentBranch, func(upstream string) error { + upstreamRemote, upstreamBranch, err := self.helpers.Upstream.ParseUpstream(upstream) if err != nil { return self.c.Error(err) } @@ -106,7 +100,7 @@ func (self *SyncController) pull(currentBranch *models.Branch) error { // if we have no upstream branch we need to set that first if !currentBranch.IsTrackingRemote() { - return self.promptForUpstream(currentBranch, func(upstream string) error { + return self.helpers.Upstream.PromptForUpstream(currentBranch, func(upstream string) error { if err := self.setCurrentBranchUpstream(upstream); err != nil { return self.c.Error(err) } @@ -119,7 +113,7 @@ func (self *SyncController) pull(currentBranch *models.Branch) error { } func (self *SyncController) setCurrentBranchUpstream(upstream string) error { - upstreamRemote, upstreamBranch, err := self.parseUpstream(upstream) + upstreamRemote, upstreamBranch, err := self.helpers.Upstream.ParseUpstream(upstream) if err != nil { return err } @@ -136,30 +130,6 @@ func (self *SyncController) setCurrentBranchUpstream(upstream string) error { return nil } -func (self *SyncController) parseUpstream(upstream string) (string, string, error) { - var upstreamBranch, upstreamRemote string - split := strings.Split(upstream, " ") - if len(split) != 2 { - return "", "", errors.New(self.c.Tr.InvalidUpstream) - } - - upstreamRemote = split[0] - upstreamBranch = split[1] - - return upstreamRemote, upstreamBranch, nil -} - -func (self *SyncController) promptForUpstream(currentBranch *models.Branch, onConfirm func(string) error) error { - suggestedRemote := self.getSuggestedRemote() - - return self.c.Prompt(types.PromptOpts{ - Title: self.c.Tr.EnterUpstream, - InitialContent: suggestedRemote + " " + currentBranch.Name, - FindSuggestionsFunc: self.helpers.Suggestions.GetRemoteBranchesSuggestionsFunc(" "), - HandleConfirm: onConfirm, - }) -} - type PullFilesOptions struct { UpstreamRemote string UpstreamBranch string diff --git a/pkg/gui/misc.go b/pkg/gui/misc.go deleted file mode 100644 index ecd5c9ae8..000000000 --- a/pkg/gui/misc.go +++ /dev/null @@ -1,25 +0,0 @@ -package gui - -import "github.com/jesseduffield/lazygit/pkg/commands/models" - -// this file is to put things where it's not obvious where they belong while this refactor takes place - -func (gui *Gui) getSuggestedRemote() string { - remotes := gui.State.Model.Remotes - - return getSuggestedRemote(remotes) -} - -func getSuggestedRemote(remotes []*models.Remote) string { - if len(remotes) == 0 { - return "origin" - } - - for _, remote := range remotes { - if remote.Name == "origin" { - return remote.Name - } - } - - return remotes[0].Name -} diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go index 2e4c4abc9..59b075420 100644 --- a/pkg/i18n/chinese.go +++ b/pkg/i18n/chinese.go @@ -312,6 +312,7 @@ func chineseTranslationSet() TranslationSet { DeleteRemoteBranch: "删除远程分支", DeleteRemoteBranchMessage: "您确定要删除远程分支吗?", LcSetUpstream: "设置为检出分支的上游", + LcSetAsUpstream: "设置为检出分支的上游", SetUpstreamTitle: "设置上游分支", SetUpstreamMessage: "您确定要将 {{.checkedOut}} 的上游分支设置为 {{.selected}} 吗?", LcEditRemote: "编辑远程仓库", diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 7069734c9..c6f1d4912 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -271,6 +271,7 @@ func dutchTranslationSet() TranslationSet { DeleteRemoteBranch: "Verwijder Remote Branch", DeleteRemoteBranchMessage: "Weet je zeker dat je deze remote branch wilt verwijderen", LcSetUpstream: "stel in als upstream van uitgecheckte branch", + LcSetAsUpstream: "stel in als upstream van uitgecheckte branch", SetUpstreamTitle: "Stel in als upstream branch", SetUpstreamMessage: "Weet je zeker dat je de upstream branch van '{{.checkedOut}}' naar '{{.selected}}' wilt zetten", LcEditRemote: "wijzig remote", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 585da5e30..daeaff4bf 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -306,7 +306,9 @@ type TranslationSet struct { LcRemoveRemotePrompt string DeleteRemoteBranch string DeleteRemoteBranchMessage string + LcSetAsUpstream string LcSetUpstream string + LcUnsetUpstream string SetUpstreamTitle string SetUpstreamMessage string LcEditRemote string @@ -339,6 +341,7 @@ type TranslationSet struct { Panel string Keybindings string LcRenameBranch string + LcSetUnsetUpstream string NewGitFlowBranchPrompt string RenameBranchWarning string LcOpenMenu string @@ -506,6 +509,7 @@ type Actions struct { Merge string RebaseBranch string RenameBranch string + SetUnsetUpstream string CreateBranch string FastForwardBranch string CherryPick string @@ -906,7 +910,9 @@ func EnglishTranslationSet() TranslationSet { LcRemoveRemotePrompt: "Are you sure you want to remove remote", DeleteRemoteBranch: "Delete Remote Branch", DeleteRemoteBranchMessage: "Are you sure you want to delete remote branch", - LcSetUpstream: "set as upstream of checked-out branch", + LcSetAsUpstream: "set as upstream of checked-out branch", + LcSetUpstream: "set upstream of selected branch", + LcUnsetUpstream: "unset upstream of selected branch", SetUpstreamTitle: "Set upstream branch", SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'", LcEditRemote: "edit remote", @@ -939,6 +945,7 @@ func EnglishTranslationSet() TranslationSet { Panel: "Panel", Keybindings: "Keybindings", LcRenameBranch: "rename branch", + LcSetUnsetUpstream: "set/unset upstream", 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?", LcOpenMenu: "open menu", @@ -1088,6 +1095,7 @@ func EnglishTranslationSet() TranslationSet { Merge: "Merge", RebaseBranch: "Rebase branch", RenameBranch: "Rename branch", + SetUnsetUpstream: "Set/unset upstream", CreateBranch: "Create branch", CherryPick: "(Cherry-pick) Paste commits", CheckoutFile: "Checkout file",