mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-19 22:33:16 +02:00
Allow user to select remote and branch when creating a PR (#1889)
When creating a PR against a selected branch (via O = "create pull request options"), the user will first be asked to select a remote (if there is more than one). After that, the suggestion area is populated with all remote branches at that origin - instead of all local ones. After all, creating a PR against a branch that doesn't exist on the remote won't work. Please note that for the "PR is not filed against 'origin' remote" use case (e.g. when contributing via a fork that is 'origin' to a GitHub project that is 'upstream'), the opened URL will not be correct. This is not a regression and will be fixed in an upcoming PR. Fixes #1826.
This commit is contained in:
commit
c3cf48cc49
@ -730,11 +730,23 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
|||||||
{
|
{
|
||||||
LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.SelectBranch),
|
LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.SelectBranch),
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
|
if !branch.IsTrackingRemote() {
|
||||||
|
return errors.New(self.c.Tr.PullRequestNoUpstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(self.c.Model().Remotes) == 1 {
|
||||||
|
toRemote := self.c.Model().Remotes[0].Name
|
||||||
|
self.c.Log.Debugf("PR will target the only existing remote '%s'", toRemote)
|
||||||
|
return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote)
|
||||||
|
}
|
||||||
|
|
||||||
self.c.Prompt(types.PromptOpts{
|
self.c.Prompt(types.PromptOpts{
|
||||||
Title: branch.Name + " →",
|
Title: self.c.Tr.SelectTargetRemote,
|
||||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesSuggestionsFunc("/"),
|
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
|
||||||
HandleConfirm: func(targetBranchName string) error {
|
HandleConfirm: func(toRemote string) error {
|
||||||
return self.createPullRequest(branch.Name, targetBranchName)
|
self.c.Log.Debugf("PR will target remote '%s'", toRemote)
|
||||||
|
|
||||||
|
return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -764,6 +776,26 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
|||||||
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprint(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
|
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprint(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *BranchesController) promptForTargetBranchNameAndCreatePullRequest(fromBranch *models.Branch, toRemote string) error {
|
||||||
|
remoteDoesNotExist := lo.NoneBy(self.c.Model().Remotes, func(remote *models.Remote) bool {
|
||||||
|
return remote.Name == toRemote
|
||||||
|
})
|
||||||
|
if remoteDoesNotExist {
|
||||||
|
return fmt.Errorf(self.c.Tr.NoValidRemoteName, toRemote)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.c.Prompt(types.PromptOpts{
|
||||||
|
Title: fmt.Sprintf("%s → %s/", fromBranch.UpstreamBranch, toRemote),
|
||||||
|
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesForRemoteSuggestionsFunc(toRemote),
|
||||||
|
HandleConfirm: func(toBranch string) error {
|
||||||
|
self.c.Log.Debugf("PR will target branch '%s' on remote '%s'", toBranch, toRemote)
|
||||||
|
return self.createPullRequest(fromBranch.UpstreamBranch, toBranch)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (self *BranchesController) createPullRequest(from string, to string) error {
|
func (self *BranchesController) createPullRequest(from string, to string) error {
|
||||||
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
|
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -162,10 +162,26 @@ func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *SuggestionsHelper) getRemoteBranchNamesForRemote(remoteName string) []string {
|
||||||
|
remote, ok := lo.Find(self.c.Model().Remotes, func(remote *models.Remote) bool {
|
||||||
|
return remote.Name == remoteName
|
||||||
|
})
|
||||||
|
if ok {
|
||||||
|
return lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) string {
|
||||||
|
return branch.Name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
|
func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
|
||||||
return FilterFunc(self.getRemoteBranchNames(separator), self.c.UserConfig().Gui.UseFuzzySearch())
|
return FilterFunc(self.getRemoteBranchNames(separator), self.c.UserConfig().Gui.UseFuzzySearch())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *SuggestionsHelper) GetRemoteBranchesForRemoteSuggestionsFunc(remoteName string) func(string) []*types.Suggestion {
|
||||||
|
return FilterFunc(self.getRemoteBranchNamesForRemote(remoteName), self.c.UserConfig().Gui.UseFuzzySearch())
|
||||||
|
}
|
||||||
|
|
||||||
func (self *SuggestionsHelper) getTagNames() []string {
|
func (self *SuggestionsHelper) getTagNames() []string {
|
||||||
return lo.Map(self.c.Model().Tags, func(tag *models.Tag, _ int) string {
|
return lo.Map(self.c.Model().Tags, func(tag *models.Tag, _ int) string {
|
||||||
return tag.Name
|
return tag.Name
|
||||||
|
@ -686,6 +686,8 @@ type TranslationSet struct {
|
|||||||
CreatePullRequestOptions string
|
CreatePullRequestOptions string
|
||||||
DefaultBranch string
|
DefaultBranch string
|
||||||
SelectBranch string
|
SelectBranch string
|
||||||
|
SelectTargetRemote string
|
||||||
|
NoValidRemoteName string
|
||||||
CreatePullRequest string
|
CreatePullRequest string
|
||||||
SelectConfigFile string
|
SelectConfigFile string
|
||||||
NoConfigFileFoundErr string
|
NoConfigFileFoundErr string
|
||||||
@ -1676,6 +1678,8 @@ func EnglishTranslationSet() *TranslationSet {
|
|||||||
CreatePullRequestOptions: "View create pull request options",
|
CreatePullRequestOptions: "View create pull request options",
|
||||||
DefaultBranch: "Default branch",
|
DefaultBranch: "Default branch",
|
||||||
SelectBranch: "Select branch",
|
SelectBranch: "Select branch",
|
||||||
|
SelectTargetRemote: "Select target remote",
|
||||||
|
NoValidRemoteName: "A remote named '%s' does not exist",
|
||||||
SelectConfigFile: "Select config file",
|
SelectConfigFile: "Select config file",
|
||||||
NoConfigFileFoundErr: "No config file found",
|
NoConfigFileFoundErr: "No config file found",
|
||||||
LoadingFileSuggestions: "Loading file suggestions",
|
LoadingFileSuggestions: "Loading file suggestions",
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package branch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var OpenPullRequestInvalidTargetRemoteName = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Open up a pull request, specifying a non-existing target remote",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
// Create an initial commit ('git branch set-upstream-to' bails out otherwise)
|
||||||
|
shell.CreateFileAndAdd("file", "content1")
|
||||||
|
shell.Commit("one")
|
||||||
|
|
||||||
|
// Create a new branch
|
||||||
|
shell.NewBranch("branch-1")
|
||||||
|
|
||||||
|
// Create a couple of remotes
|
||||||
|
shell.CloneIntoRemote("upstream")
|
||||||
|
shell.CloneIntoRemote("origin")
|
||||||
|
|
||||||
|
// To allow a pull request to be created from a branch, it must have an upstream set.
|
||||||
|
shell.SetBranchUpstream("branch-1", "origin/branch-1")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
// Open a PR for the current branch (i.e. 'branch-1')
|
||||||
|
t.Views().
|
||||||
|
Branches().
|
||||||
|
Focus().
|
||||||
|
Press(keys.Branches.ViewPullRequestOptions)
|
||||||
|
|
||||||
|
t.ExpectPopup().
|
||||||
|
Menu().
|
||||||
|
Title(Equals("View create pull request options")).
|
||||||
|
Select(Contains("Select branch")).
|
||||||
|
Confirm()
|
||||||
|
|
||||||
|
// Verify that we're prompted to enter the remote and enter the name of a non-existing one.
|
||||||
|
t.ExpectPopup().
|
||||||
|
Prompt().
|
||||||
|
Title(Equals("Select target remote")).
|
||||||
|
Type("non-existing-remote").
|
||||||
|
Confirm()
|
||||||
|
|
||||||
|
// Verify that this leads to an error being shown (instead of progressing to branch selection).
|
||||||
|
t.ExpectPopup().Alert().
|
||||||
|
Title(Equals("Error")).
|
||||||
|
Content(Contains("A remote named 'non-existing-remote' does not exist")).
|
||||||
|
Confirm()
|
||||||
|
},
|
||||||
|
})
|
@ -0,0 +1,74 @@
|
|||||||
|
package branch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var OpenPullRequestSelectRemoteAndTargetBranch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Open up a pull request, specifying a remote and target branch",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {
|
||||||
|
config.GetUserConfig().OS.OpenLink = "echo {{link}} > /tmp/openlink"
|
||||||
|
},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
// Create an initial commit ('git branch set-upstream-to' bails out otherwise)
|
||||||
|
shell.CreateFileAndAdd("file", "content1")
|
||||||
|
shell.Commit("one")
|
||||||
|
|
||||||
|
// Create a new branch and a remote that has that branch
|
||||||
|
shell.NewBranch("branch-1")
|
||||||
|
shell.CloneIntoRemote("upstream")
|
||||||
|
|
||||||
|
// Create another branch and a second remote. The first remote doesn't have this branch.
|
||||||
|
shell.NewBranch("branch-2")
|
||||||
|
shell.CloneIntoRemote("origin")
|
||||||
|
|
||||||
|
// To allow a pull request to be created from a branch, it must have an upstream set.
|
||||||
|
shell.SetBranchUpstream("branch-2", "origin/branch-2")
|
||||||
|
|
||||||
|
shell.RunCommand([]string{"git", "remote", "set-url", "origin", "https://github.com/my-personal-fork/lazygit"})
|
||||||
|
shell.RunCommand([]string{"git", "remote", "set-url", "upstream", "https://github.com/jesseduffield/lazygit"})
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
// Open a PR for the current branch (i.e. 'branch-2')
|
||||||
|
t.Views().
|
||||||
|
Branches().
|
||||||
|
Focus().
|
||||||
|
Press(keys.Branches.ViewPullRequestOptions)
|
||||||
|
|
||||||
|
t.ExpectPopup().
|
||||||
|
Menu().
|
||||||
|
Title(Equals("View create pull request options")).
|
||||||
|
Select(Contains("Select branch")).
|
||||||
|
Confirm()
|
||||||
|
|
||||||
|
// Verify that we're prompted to enter the remote
|
||||||
|
t.ExpectPopup().
|
||||||
|
Prompt().
|
||||||
|
Title(Equals("Select target remote")).
|
||||||
|
SuggestionLines(
|
||||||
|
Equals("origin"),
|
||||||
|
Equals("upstream")).
|
||||||
|
ConfirmSuggestion(Equals("upstream"))
|
||||||
|
|
||||||
|
// Verify that we're prompted to enter the target branch and that only those branches
|
||||||
|
// present in the selected remote are listed as suggestions (i.e. 'branch-2' is not there).
|
||||||
|
t.ExpectPopup().
|
||||||
|
Prompt().
|
||||||
|
Title(Equals("branch-2 → upstream/")).
|
||||||
|
SuggestionLines(
|
||||||
|
Equals("branch-1"),
|
||||||
|
Equals("master")).
|
||||||
|
ConfirmSuggestion(Equals("master"))
|
||||||
|
|
||||||
|
// Verify that the expected URL is used (by checking the openlink file)
|
||||||
|
//
|
||||||
|
// Please note that when targeting a different remote - like it's done here in this test -
|
||||||
|
// the link is not yet correct. Thus, this test is expected to fail once this is fixed.
|
||||||
|
t.FileSystem().FileContent(
|
||||||
|
"/tmp/openlink",
|
||||||
|
Equals("https://github.com/my-personal-fork/lazygit/compare/master...branch-2?expand=1\n"))
|
||||||
|
},
|
||||||
|
})
|
@ -48,7 +48,9 @@ var tests = []*components.IntegrationTest{
|
|||||||
branch.NewBranchAutostash,
|
branch.NewBranchAutostash,
|
||||||
branch.NewBranchFromRemoteTrackingDifferentName,
|
branch.NewBranchFromRemoteTrackingDifferentName,
|
||||||
branch.NewBranchFromRemoteTrackingSameName,
|
branch.NewBranchFromRemoteTrackingSameName,
|
||||||
|
branch.OpenPullRequestInvalidTargetRemoteName,
|
||||||
branch.OpenPullRequestNoUpstream,
|
branch.OpenPullRequestNoUpstream,
|
||||||
|
branch.OpenPullRequestSelectRemoteAndTargetBranch,
|
||||||
branch.OpenWithCliArg,
|
branch.OpenWithCliArg,
|
||||||
branch.Rebase,
|
branch.Rebase,
|
||||||
branch.RebaseAbortOnConflict,
|
branch.RebaseAbortOnConflict,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user