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",