summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Haller <stefan@haller-berlin.de>2024-03-15 20:56:44 +0100
committerStefan Haller <stefan@haller-berlin.de>2024-03-17 07:53:38 +0100
commite42cbf95ae73d60142ee02e9ef057dc7cce7d05a (patch)
tree066fe54042c8b1fc65821342f02c848f6e5b4dde
parent0360b82aab50dff0ca2f86676c9c805de91d9809 (diff)
When checking out a remote branch by name, ask the user how
The choices are to create a new local branch that tracks the remote, or a detached head.
-rw-r--r--pkg/commands/git_commands/branch.go11
-rw-r--r--pkg/gui/controllers/branches_controller.go4
-rw-r--r--pkg/gui/controllers/helpers/refs_helper.go69
-rw-r--r--pkg/i18n/english.go10
4 files changed, 94 insertions, 0 deletions
diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go
index d05738ef3..6a347b8ac 100644
--- a/pkg/commands/git_commands/branch.go
+++ b/pkg/commands/git_commands/branch.go
@@ -28,6 +28,17 @@ func (self *BranchCommands) New(name string, base string) error {
return self.cmd.New(cmdArgs).Run()
}
+// CreateWithUpstream creates a new branch with a given upstream, but without
+// checking it out
+func (self *BranchCommands) CreateWithUpstream(name string, upstream string) error {
+ cmdArgs := NewGitCmd("branch").
+ Arg("--track").
+ Arg(name, upstream).
+ ToArgv()
+
+ return self.cmd.New(cmdArgs).Run()
+}
+
// CurrentBranchInfo get the current branch information.
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
branchName, err := self.cmd.New(
diff --git a/pkg/gui/controllers/branches_controller.go b/pkg/gui/controllers/branches_controller.go
index 068238ec7..ff9473ab6 100644
--- a/pkg/gui/controllers/branches_controller.go
+++ b/pkg/gui/controllers/branches_controller.go
@@ -436,6 +436,10 @@ func (self *BranchesController) checkoutByName() error {
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
self.c.LogAction("Checkout branch")
+ _, branchName, found := self.c.Helpers().Refs.ParseRemoteBranchName(response)
+ if found {
+ return self.c.Helpers().Refs.CheckoutRemoteBranch(response, branchName)
+ }
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
OnRefNotFound: func(ref string) error {
return self.c.Confirm(types.ConfirmOpts{
diff --git a/pkg/gui/controllers/helpers/refs_helper.go b/pkg/gui/controllers/helpers/refs_helper.go
index 8b066f447..362746288 100644
--- a/pkg/gui/controllers/helpers/refs_helper.go
+++ b/pkg/gui/controllers/helpers/refs_helper.go
@@ -96,6 +96,57 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
})
}
+// Shows a prompt to choose between creating a new branch or checking out a detached head
+func (self *RefsHelper) CheckoutRemoteBranch(fullBranchName string, localBranchName string) error {
+ checkout := func(branchName string) error {
+ if err := self.CheckoutRef(branchName, types.CheckoutRefOptions{}); err != nil {
+ return err
+ }
+ if self.c.CurrentContext() != self.c.Contexts().Branches {
+ return self.c.PushContext(self.c.Contexts().Branches)
+ }
+ return nil
+ }
+
+ // If a branch with this name already exists locally, just check it out. We
+ // don't bother checking whether it actually tracks this remote branch, since
+ // it's very unlikely that it doesn't.
+ if lo.ContainsBy(self.c.Model().Branches, func(branch *models.Branch) bool {
+ return branch.Name == localBranchName
+ }) {
+ return checkout(localBranchName)
+ }
+
+ return self.c.Menu(types.CreateMenuOptions{
+ Title: utils.ResolvePlaceholderString(self.c.Tr.RemoteBranchCheckoutTitle, map[string]string{
+ "branchName": fullBranchName,
+ }),
+ Items: []*types.MenuItem{
+ {
+ Label: self.c.Tr.CheckoutTypeNewBranch,
+ Tooltip: self.c.Tr.CheckoutTypeNewBranchTooltip,
+ OnPress: func() error {
+ // First create the local branch with the upstream set, and
+ // then check it out. We could do that in one step using
+ // "git checkout -b", but we want to benefit from all the
+ // nice features of the CheckoutRef function.
+ if err := self.c.Git().Branch.CreateWithUpstream(localBranchName, fullBranchName); err != nil {
+ return self.c.Error(err)
+ }
+ return checkout(localBranchName)
+ },
+ },
+ {
+ Label: self.c.Tr.CheckoutTypeDetachedHead,
+ Tooltip: self.c.Tr.CheckoutTypeDetachedHeadTooltip,
+ OnPress: func() error {
+ return checkout(fullBranchName)
+ },
+ },
+ },
+ })
+}
+
func (self *RefsHelper) GetCheckedOutRef() *models.Branch {
if len(self.c.Model().Branches) == 0 {
return nil
@@ -232,3 +283,21 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
func SanitizedBranchName(input string) string {
return strings.Replace(input, " ", "-", -1)
}
+
+// Checks if the given branch name is a remote branch, and returns the name of
+// the remote and the bare branch name if it is.
+func (self *RefsHelper) ParseRemoteBranchName(fullBranchName string) (string, string, bool) {
+ remoteName, branchName, found := strings.Cut(fullBranchName, "/")
+ if !found {
+ return "", "", false
+ }
+
+ // See if the part before the first slash is actually one of our remotes
+ if !lo.ContainsBy(self.c.Model().Remotes, func(remote *models.Remote) bool {
+ return remote.Name == remoteName
+ }) {
+ return "", "", false
+ }
+
+ return remoteName, branchName, true
+}
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 2f3f35ab5..5d3d37d74 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -113,6 +113,11 @@ type TranslationSet struct {
ForceCheckoutTooltip string
CheckoutByName string
CheckoutByNameTooltip string
+ RemoteBranchCheckoutTitle string
+ CheckoutTypeNewBranch string
+ CheckoutTypeNewBranchTooltip string
+ CheckoutTypeDetachedHead string
+ CheckoutTypeDetachedHeadTooltip string
NewBranch string
NewBranchFromStashTooltip string
NoBranchesThisRepo string
@@ -1065,6 +1070,11 @@ func EnglishTranslationSet() TranslationSet {
ForceCheckoutTooltip: "Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch.",
CheckoutByName: "Checkout by name",
CheckoutByNameTooltip: "Checkout by name. In the input box you can enter '-' to switch to the last branch.",
+ RemoteBranchCheckoutTitle: "Checkout {{.branchName}}",
+ CheckoutTypeNewBranch: "New local branch",
+ CheckoutTypeNewBranchTooltip: "Checkout the remote branch as a local branch, tracking the remote branch.",
+ CheckoutTypeDetachedHead: "Detached head",
+ CheckoutTypeDetachedHeadTooltip: "Checkout the remote branch as a detached head, which can be useful if you just want to test the branch but not work on it yourself. You can still create a local branch from it later.",
NewBranch: "New branch",
NewBranchFromStashTooltip: "Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit.",
NoBranchesThisRepo: "No branches for this repo",