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.
This commit is contained in:
Stefan Haller 2024-03-15 20:56:44 +01:00
parent 0360b82aab
commit e42cbf95ae
4 changed files with 94 additions and 0 deletions

View file

@ -28,6 +28,17 @@ func (self *BranchCommands) New(name string, base string) error {
return self.cmd.New(cmdArgs).Run() 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. // CurrentBranchInfo get the current branch information.
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) { func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
branchName, err := self.cmd.New( branchName, err := self.cmd.New(

View file

@ -436,6 +436,10 @@ func (self *BranchesController) checkoutByName() error {
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(), FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error { HandleConfirm: func(response string) error {
self.c.LogAction("Checkout branch") 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{ return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
OnRefNotFound: func(ref string) error { OnRefNotFound: func(ref string) error {
return self.c.Confirm(types.ConfirmOpts{ return self.c.Confirm(types.ConfirmOpts{

View file

@ -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 { func (self *RefsHelper) GetCheckedOutRef() *models.Branch {
if len(self.c.Model().Branches) == 0 { if len(self.c.Model().Branches) == 0 {
return nil return nil
@ -232,3 +283,21 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
func SanitizedBranchName(input string) string { func SanitizedBranchName(input string) string {
return strings.Replace(input, " ", "-", -1) 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
}

View file

@ -113,6 +113,11 @@ type TranslationSet struct {
ForceCheckoutTooltip string ForceCheckoutTooltip string
CheckoutByName string CheckoutByName string
CheckoutByNameTooltip string CheckoutByNameTooltip string
RemoteBranchCheckoutTitle string
CheckoutTypeNewBranch string
CheckoutTypeNewBranchTooltip string
CheckoutTypeDetachedHead string
CheckoutTypeDetachedHeadTooltip string
NewBranch string NewBranch string
NewBranchFromStashTooltip string NewBranchFromStashTooltip string
NoBranchesThisRepo 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.", 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", CheckoutByName: "Checkout by name",
CheckoutByNameTooltip: "Checkout by name. In the input box you can enter '-' to switch to the last branch.", 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", 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.", 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", NoBranchesThisRepo: "No branches for this repo",