Isolate pr create command
This commit is contained in:
parent
e024184c6f
commit
558e3f3dea
7 changed files with 471 additions and 435 deletions
110
command/issue.go
110
command/issue.go
|
|
@ -508,11 +508,7 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
||||||
if isWeb, err := cmd.Flags().GetBool("web"); err == nil && isWeb {
|
if isWeb, err := cmd.Flags().GetBool("web"); err == nil && isWeb {
|
||||||
openURL := ghrepo.GenerateRepoURL(baseRepo, "issues/new")
|
openURL := ghrepo.GenerateRepoURL(baseRepo, "issues/new")
|
||||||
if title != "" || body != "" {
|
if title != "" || body != "" {
|
||||||
milestone := ""
|
openURL, err = shared.WithPrAndIssueQueryParams(openURL, title, body, assignees, labelNames, projectNames, milestoneTitles)
|
||||||
if len(milestoneTitles) > 0 {
|
|
||||||
milestone = milestoneTitles[0]
|
|
||||||
}
|
|
||||||
openURL, err = withPrAndIssueQueryParams(openURL, title, body, assignees, labelNames, projectNames, milestone)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -535,9 +531,9 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
||||||
return fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(baseRepo))
|
return fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(baseRepo))
|
||||||
}
|
}
|
||||||
|
|
||||||
action := SubmitAction
|
action := shared.SubmitAction
|
||||||
tb := issueMetadataState{
|
tb := shared.IssueMetadataState{
|
||||||
Type: issueMetadata,
|
Type: shared.IssueMetadata,
|
||||||
Assignees: assignees,
|
Assignees: assignees,
|
||||||
Labels: labelNames,
|
Labels: labelNames,
|
||||||
Projects: projectNames,
|
Projects: projectNames,
|
||||||
|
|
@ -558,14 +554,20 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
||||||
legacyTemplateFile = githubtemplate.FindLegacy(rootDir, "ISSUE_TEMPLATE")
|
legacyTemplateFile = githubtemplate.FindLegacy(rootDir, "ISSUE_TEMPLATE")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := titleBodySurvey(cmd, &tb, apiClient, baseRepo, title, body, defaults{}, nonLegacyTemplateFiles, legacyTemplateFile, false, repo.ViewerCanTriage())
|
|
||||||
|
editorCommand, err := cmdutil.DetermineEditor(ctx.Config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = shared.TitleBodySurvey(defaultStreams, editorCommand, &tb, apiClient, baseRepo, title, body, shared.Defaults{}, nonLegacyTemplateFiles, legacyTemplateFile, false, repo.ViewerCanTriage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not collect title and/or body: %w", err)
|
return fmt.Errorf("could not collect title and/or body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
action = tb.Action
|
action = tb.Action
|
||||||
|
|
||||||
if tb.Action == CancelAction {
|
if tb.Action == shared.CancelAction {
|
||||||
fmt.Fprintln(cmd.ErrOrStderr(), "Discarding.")
|
fmt.Fprintln(cmd.ErrOrStderr(), "Discarding.")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -583,26 +585,22 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == PreviewAction {
|
if action == shared.PreviewAction {
|
||||||
openURL := ghrepo.GenerateRepoURL(baseRepo, "issues/new")
|
openURL := ghrepo.GenerateRepoURL(baseRepo, "issues/new")
|
||||||
milestone := ""
|
openURL, err = shared.WithPrAndIssueQueryParams(openURL, title, body, assignees, labelNames, projectNames, milestoneTitles)
|
||||||
if len(milestoneTitles) > 0 {
|
|
||||||
milestone = milestoneTitles[0]
|
|
||||||
}
|
|
||||||
openURL, err = withPrAndIssueQueryParams(openURL, title, body, assignees, labelNames, projectNames, milestone)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TODO could exceed max url length for explorer
|
// TODO could exceed max url length for explorer
|
||||||
fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||||
return utils.OpenInBrowser(openURL)
|
return utils.OpenInBrowser(openURL)
|
||||||
} else if action == SubmitAction {
|
} else if action == shared.SubmitAction {
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
"title": title,
|
"title": title,
|
||||||
"body": body,
|
"body": body,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = addMetadataToIssueParams(apiClient, baseRepo, params, &tb)
|
err = shared.AddMetadataToIssueParams(apiClient, baseRepo, params, &tb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -620,82 +618,6 @@ func issueCreate(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addMetadataToIssueParams(client *api.Client, baseRepo ghrepo.Interface, params map[string]interface{}, tb *issueMetadataState) error {
|
|
||||||
if !tb.HasMetadata() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if tb.MetadataResult == nil {
|
|
||||||
resolveInput := api.RepoResolveInput{
|
|
||||||
Reviewers: tb.Reviewers,
|
|
||||||
Assignees: tb.Assignees,
|
|
||||||
Labels: tb.Labels,
|
|
||||||
Projects: tb.Projects,
|
|
||||||
Milestones: tb.Milestones,
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
tb.MetadataResult, err = api.RepoResolveMetadataIDs(client, baseRepo, resolveInput)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assigneeIDs, err := tb.MetadataResult.MembersToIDs(tb.Assignees)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not assign user: %w", err)
|
|
||||||
}
|
|
||||||
params["assigneeIds"] = assigneeIDs
|
|
||||||
|
|
||||||
labelIDs, err := tb.MetadataResult.LabelsToIDs(tb.Labels)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not add label: %w", err)
|
|
||||||
}
|
|
||||||
params["labelIds"] = labelIDs
|
|
||||||
|
|
||||||
projectIDs, err := tb.MetadataResult.ProjectsToIDs(tb.Projects)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not add to project: %w", err)
|
|
||||||
}
|
|
||||||
params["projectIds"] = projectIDs
|
|
||||||
|
|
||||||
if len(tb.Milestones) > 0 {
|
|
||||||
milestoneID, err := tb.MetadataResult.MilestoneToID(tb.Milestones[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not add to milestone '%s': %w", tb.Milestones[0], err)
|
|
||||||
}
|
|
||||||
params["milestoneId"] = milestoneID
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tb.Reviewers) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var userReviewers []string
|
|
||||||
var teamReviewers []string
|
|
||||||
for _, r := range tb.Reviewers {
|
|
||||||
if strings.ContainsRune(r, '/') {
|
|
||||||
teamReviewers = append(teamReviewers, r)
|
|
||||||
} else {
|
|
||||||
userReviewers = append(userReviewers, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
userReviewerIDs, err := tb.MetadataResult.MembersToIDs(userReviewers)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not request reviewer: %w", err)
|
|
||||||
}
|
|
||||||
params["userReviewerIds"] = userReviewerIDs
|
|
||||||
|
|
||||||
teamReviewerIDs, err := tb.MetadataResult.TeamsToIDs(teamReviewers)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not request reviewer: %w", err)
|
|
||||||
}
|
|
||||||
params["teamReviewerIds"] = teamReviewerIDs
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printIssues(w io.Writer, prefix string, totalCount int, issues []api.Issue) {
|
func printIssues(w io.Writer, prefix string, totalCount int, issues []api.Issue) {
|
||||||
table := utils.NewTablePrinter(w)
|
table := utils.NewTablePrinter(w)
|
||||||
for _, issue := range issues {
|
for _, issue := range issues {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ func init() {
|
||||||
prCmd.PersistentFlags().StringP("repo", "R", "", "Select another repository using the `OWNER/REPO` format")
|
prCmd.PersistentFlags().StringP("repo", "R", "", "Select another repository using the `OWNER/REPO` format")
|
||||||
|
|
||||||
RootCmd.AddCommand(prCmd)
|
RootCmd.AddCommand(prCmd)
|
||||||
prCmd.AddCommand(prCreateCmd)
|
|
||||||
prCmd.AddCommand(prCloseCmd)
|
prCmd.AddCommand(prCloseCmd)
|
||||||
prCmd.AddCommand(prReopenCmd)
|
prCmd.AddCommand(prReopenCmd)
|
||||||
prCmd.AddCommand(prReadyCmd)
|
prCmd.AddCommand(prReadyCmd)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
apiCmd "github.com/cli/cli/pkg/cmd/api"
|
apiCmd "github.com/cli/cli/pkg/cmd/api"
|
||||||
gistCreateCmd "github.com/cli/cli/pkg/cmd/gist/create"
|
gistCreateCmd "github.com/cli/cli/pkg/cmd/gist/create"
|
||||||
prCheckoutCmd "github.com/cli/cli/pkg/cmd/pr/checkout"
|
prCheckoutCmd "github.com/cli/cli/pkg/cmd/pr/checkout"
|
||||||
|
prCreateCmd "github.com/cli/cli/pkg/cmd/pr/create"
|
||||||
prDiffCmd "github.com/cli/cli/pkg/cmd/pr/diff"
|
prDiffCmd "github.com/cli/cli/pkg/cmd/pr/diff"
|
||||||
prMergeCmd "github.com/cli/cli/pkg/cmd/pr/merge"
|
prMergeCmd "github.com/cli/cli/pkg/cmd/pr/merge"
|
||||||
prReviewCmd "github.com/cli/cli/pkg/cmd/pr/review"
|
prReviewCmd "github.com/cli/cli/pkg/cmd/pr/review"
|
||||||
|
|
@ -179,6 +180,7 @@ func init() {
|
||||||
prCmd.AddCommand(prViewCmd.NewCmdView(&repoResolvingCmdFactory, nil))
|
prCmd.AddCommand(prViewCmd.NewCmdView(&repoResolvingCmdFactory, nil))
|
||||||
prCmd.AddCommand(prMergeCmd.NewCmdMerge(&repoResolvingCmdFactory, nil))
|
prCmd.AddCommand(prMergeCmd.NewCmdMerge(&repoResolvingCmdFactory, nil))
|
||||||
prCmd.AddCommand(prStatusCmd.NewCmdStatus(&repoResolvingCmdFactory, nil))
|
prCmd.AddCommand(prStatusCmd.NewCmdStatus(&repoResolvingCmdFactory, nil))
|
||||||
|
prCmd.AddCommand(prCreateCmd.NewCmdCreate(&repoResolvingCmdFactory, nil))
|
||||||
|
|
||||||
RootCmd.AddCommand(creditsCmd.NewCmdCredits(cmdFactory, nil))
|
RootCmd.AddCommand(creditsCmd.NewCmdCredits(cmdFactory, nil))
|
||||||
}
|
}
|
||||||
|
|
@ -398,41 +400,6 @@ func determineBaseRepo(apiClient *api.Client, cmd *cobra.Command, ctx context.Co
|
||||||
return baseRepo, nil
|
return baseRepo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO there is a parallel implementation for isolated commands
|
|
||||||
func formatRemoteURL(cmd *cobra.Command, repo ghrepo.Interface) string {
|
|
||||||
ctx := contextForCommand(cmd)
|
|
||||||
|
|
||||||
var protocol string
|
|
||||||
cfg, err := ctx.Config()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(colorableErr(cmd), "%s failed to load config: %s. using defaults\n", utils.Yellow("!"), err)
|
|
||||||
} else {
|
|
||||||
protocol, _ = cfg.Get(repo.RepoHost(), "git_protocol")
|
|
||||||
}
|
|
||||||
|
|
||||||
if protocol == "ssh" {
|
|
||||||
return fmt.Sprintf("git@%s:%s/%s.git", repo.RepoHost(), repo.RepoOwner(), repo.RepoName())
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("https://%s/%s/%s.git", repo.RepoHost(), repo.RepoOwner(), repo.RepoName())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO there is a parallel implementation for isolated commands
|
|
||||||
func determineEditor(cmd *cobra.Command) (string, error) {
|
|
||||||
editorCommand := os.Getenv("GH_EDITOR")
|
|
||||||
if editorCommand == "" {
|
|
||||||
ctx := contextForCommand(cmd)
|
|
||||||
cfg, err := ctx.Config()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("could not read config: %w", err)
|
|
||||||
}
|
|
||||||
// TODO: consider supporting setting an editor per GHE host
|
|
||||||
editorCommand, _ = cfg.Get(ghinstance.Default(), "editor")
|
|
||||||
}
|
|
||||||
|
|
||||||
return editorCommand, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecuteShellAlias(args []string) error {
|
func ExecuteShellAlias(args []string) error {
|
||||||
externalCmd := exec.Command(args[0], args[1:]...)
|
externalCmd := exec.Command(args[0], args[1:]...)
|
||||||
externalCmd.Stderr = os.Stderr
|
externalCmd.Stderr = os.Stderr
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package command
|
package create
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -11,60 +11,116 @@ import (
|
||||||
"github.com/cli/cli/api"
|
"github.com/cli/cli/api"
|
||||||
"github.com/cli/cli/context"
|
"github.com/cli/cli/context"
|
||||||
"github.com/cli/cli/git"
|
"github.com/cli/cli/git"
|
||||||
|
"github.com/cli/cli/internal/config"
|
||||||
"github.com/cli/cli/internal/ghrepo"
|
"github.com/cli/cli/internal/ghrepo"
|
||||||
|
"github.com/cli/cli/pkg/cmd/pr/shared"
|
||||||
"github.com/cli/cli/pkg/cmdutil"
|
"github.com/cli/cli/pkg/cmdutil"
|
||||||
"github.com/cli/cli/pkg/githubtemplate"
|
"github.com/cli/cli/pkg/githubtemplate"
|
||||||
|
"github.com/cli/cli/pkg/iostreams"
|
||||||
"github.com/cli/cli/utils"
|
"github.com/cli/cli/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type defaults struct {
|
type CreateOptions struct {
|
||||||
Title string
|
HttpClient func() (*http.Client, error)
|
||||||
Body string
|
Config func() (config.Config, error)
|
||||||
|
IO *iostreams.IOStreams
|
||||||
|
Remotes func() (context.Remotes, error)
|
||||||
|
Branch func() (string, error)
|
||||||
|
|
||||||
|
RepoOverride string
|
||||||
|
|
||||||
|
Autofill bool
|
||||||
|
WebMode bool
|
||||||
|
|
||||||
|
IsDraft bool
|
||||||
|
Title string
|
||||||
|
TitleProvided bool
|
||||||
|
Body string
|
||||||
|
BodyProvided bool
|
||||||
|
BaseBranch string
|
||||||
|
|
||||||
|
Reviewers []string
|
||||||
|
Assignees []string
|
||||||
|
Labels []string
|
||||||
|
Projects []string
|
||||||
|
Milestone string
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeDefaults(baseRef, headRef string) (defaults, error) {
|
func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
|
||||||
commits, err := git.Commits(baseRef, headRef)
|
opts := &CreateOptions{
|
||||||
|
IO: f.IOStreams,
|
||||||
|
HttpClient: f.HttpClient,
|
||||||
|
Config: f.Config,
|
||||||
|
Remotes: f.Remotes,
|
||||||
|
Branch: f.Branch,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "create",
|
||||||
|
Short: "Create a pull request",
|
||||||
|
Example: heredoc.Doc(`
|
||||||
|
$ gh pr create --title "The bug is fixed" --body "Everything works again"
|
||||||
|
$ gh issue create --label "bug,help wanted"
|
||||||
|
$ gh issue create --label bug --label "help wanted"
|
||||||
|
$ gh pr create --reviewer monalisa,hubot
|
||||||
|
$ gh pr create --project "Roadmap"
|
||||||
|
$ gh pr create --base develop
|
||||||
|
`),
|
||||||
|
Args: cmdutil.NoArgsQuoteReminder,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
opts.TitleProvided = cmd.Flags().Changed("title")
|
||||||
|
opts.BodyProvided = cmd.Flags().Changed("body")
|
||||||
|
opts.RepoOverride, _ = cmd.Flags().GetString("repo")
|
||||||
|
|
||||||
|
isTerminal := opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY()
|
||||||
|
if !isTerminal && !opts.WebMode && !opts.TitleProvided && !opts.Autofill {
|
||||||
|
return errors.New("--title or --fill required when not attached to a terminal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.IsDraft && opts.WebMode {
|
||||||
|
return errors.New("the --draft flag is not supported with --web")
|
||||||
|
}
|
||||||
|
if len(opts.Reviewers) > 0 && opts.WebMode {
|
||||||
|
return errors.New("the --reviewer flag is not supported with --web")
|
||||||
|
}
|
||||||
|
|
||||||
|
if runF != nil {
|
||||||
|
return runF(opts)
|
||||||
|
}
|
||||||
|
return createRun(opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fl := cmd.Flags()
|
||||||
|
fl.BoolVarP(&opts.IsDraft, "draft", "d", false, "Mark pull request as a draft")
|
||||||
|
fl.StringVarP(&opts.Title, "title", "t", "", "Supply a title. Will prompt for one otherwise.")
|
||||||
|
fl.StringVarP(&opts.Body, "body", "b", "", "Supply a body. Will prompt for one otherwise.")
|
||||||
|
fl.StringVarP(&opts.BaseBranch, "base", "B", "", "The branch into which you want your code merged")
|
||||||
|
fl.BoolVarP(&opts.WebMode, "web", "w", false, "Open the web browser to create a pull request")
|
||||||
|
fl.BoolVarP(&opts.Autofill, "fill", "f", false, "Do not prompt for title/body and just use commit info")
|
||||||
|
fl.StringSliceVarP(&opts.Reviewers, "reviewer", "r", nil, "Request reviews from people by their `login`")
|
||||||
|
fl.StringSliceVarP(&opts.Assignees, "assignee", "a", nil, "Assign people by their `login`")
|
||||||
|
fl.StringSliceVarP(&opts.Labels, "label", "l", nil, "Add labels by `name`")
|
||||||
|
fl.StringSliceVarP(&opts.Projects, "project", "p", nil, "Add the pull request to projects by `name`")
|
||||||
|
fl.StringVarP(&opts.Milestone, "milestone", "m", "", "Add the pull request to a milestone by `name`")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRun(opts *CreateOptions) error {
|
||||||
|
httpClient, err := opts.HttpClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return defaults{}, err
|
return err
|
||||||
}
|
}
|
||||||
|
client := api.NewClientFromHTTP(httpClient)
|
||||||
|
|
||||||
out := defaults{}
|
remotes, err := opts.Remotes()
|
||||||
|
|
||||||
if len(commits) == 1 {
|
|
||||||
out.Title = commits[0].Title
|
|
||||||
body, err := git.CommitBody(commits[0].Sha)
|
|
||||||
if err != nil {
|
|
||||||
return defaults{}, err
|
|
||||||
}
|
|
||||||
out.Body = body
|
|
||||||
} else {
|
|
||||||
out.Title = utils.Humanize(headRef)
|
|
||||||
|
|
||||||
body := ""
|
|
||||||
for i := len(commits) - 1; i >= 0; i-- {
|
|
||||||
body += fmt.Sprintf("- %s\n", commits[i].Title)
|
|
||||||
}
|
|
||||||
out.Body = body
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func prCreate(cmd *cobra.Command, _ []string) error {
|
|
||||||
ctx := contextForCommand(cmd)
|
|
||||||
remotes, err := ctx.Remotes()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := apiClientForContext(ctx)
|
repoContext, err := context.ResolveRemotesToRepos(remotes, client, opts.RepoOverride)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not initialize API client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
baseRepoOverride, _ := cmd.Flags().GetString("repo")
|
|
||||||
repoContext, err := context.ResolveRemotesToRepos(remotes, client, baseRepoOverride)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +130,7 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("could not determine base repository: %w", err)
|
return fmt.Errorf("could not determine base repository: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headBranch, err := ctx.Branch()
|
headBranch, err := opts.Branch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not determine the current branch: %w", err)
|
return fmt.Errorf("could not determine the current branch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -102,10 +158,7 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
baseBranch, err := cmd.Flags().GetString("base")
|
baseBranch := opts.BaseBranch
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if baseBranch == "" {
|
if baseBranch == "" {
|
||||||
baseBranch = baseRepo.DefaultBranchRef.Name
|
baseBranch = baseRepo.DefaultBranchRef.Name
|
||||||
}
|
}
|
||||||
|
|
@ -114,39 +167,12 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ucc, err := git.UncommittedChangeCount(); err == nil && ucc > 0 {
|
if ucc, err := git.UncommittedChangeCount(); err == nil && ucc > 0 {
|
||||||
fmt.Fprintf(cmd.ErrOrStderr(), "Warning: %s\n", utils.Pluralize(ucc, "uncommitted change"))
|
fmt.Fprintf(opts.IO.ErrOut, "Warning: %s\n", utils.Pluralize(ucc, "uncommitted change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
title, err := cmd.Flags().GetString("title")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse title: %w", err)
|
|
||||||
}
|
|
||||||
body, err := cmd.Flags().GetString("body")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse body: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reviewers, err := cmd.Flags().GetStringSlice("reviewer")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse reviewers: %w", err)
|
|
||||||
}
|
|
||||||
assignees, err := cmd.Flags().GetStringSlice("assignee")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse assignees: %w", err)
|
|
||||||
}
|
|
||||||
labelNames, err := cmd.Flags().GetStringSlice("label")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse labels: %w", err)
|
|
||||||
}
|
|
||||||
projectNames, err := cmd.Flags().GetStringSlice("project")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse projects: %w", err)
|
|
||||||
}
|
|
||||||
var milestoneTitles []string
|
var milestoneTitles []string
|
||||||
if milestoneTitle, err := cmd.Flags().GetString("milestone"); err != nil {
|
if opts.Milestone != "" {
|
||||||
return fmt.Errorf("could not parse milestone: %w", err)
|
milestoneTitles = []string{opts.Milestone}
|
||||||
} else if milestoneTitle != "" {
|
|
||||||
milestoneTitles = append(milestoneTitles, milestoneTitle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
baseTrackingBranch := baseBranch
|
baseTrackingBranch := baseBranch
|
||||||
|
|
@ -155,23 +181,16 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
defs, defaultsErr := computeDefaults(baseTrackingBranch, headBranch)
|
defs, defaultsErr := computeDefaults(baseTrackingBranch, headBranch)
|
||||||
|
|
||||||
isWeb, err := cmd.Flags().GetBool("web")
|
title := opts.Title
|
||||||
if err != nil {
|
body := opts.Body
|
||||||
return fmt.Errorf("could not parse web: %q", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
autofill, err := cmd.Flags().GetBool("fill")
|
action := shared.SubmitAction
|
||||||
if err != nil {
|
if opts.WebMode {
|
||||||
return fmt.Errorf("could not parse fill: %q", err)
|
action = shared.PreviewAction
|
||||||
}
|
|
||||||
|
|
||||||
action := SubmitAction
|
|
||||||
if isWeb {
|
|
||||||
action = PreviewAction
|
|
||||||
if (title == "" || body == "") && defaultsErr != nil {
|
if (title == "" || body == "") && defaultsErr != nil {
|
||||||
return fmt.Errorf("could not compute title or body defaults: %w", defaultsErr)
|
return fmt.Errorf("could not compute title or body defaults: %w", defaultsErr)
|
||||||
}
|
}
|
||||||
} else if autofill {
|
} else if opts.Autofill {
|
||||||
if defaultsErr != nil {
|
if defaultsErr != nil {
|
||||||
return fmt.Errorf("could not compute title or body defaults: %w", defaultsErr)
|
return fmt.Errorf("could not compute title or body defaults: %w", defaultsErr)
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +198,7 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
body = defs.Body
|
body = defs.Body
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isWeb {
|
if !opts.WebMode {
|
||||||
headBranchLabel := headBranch
|
headBranchLabel := headBranch
|
||||||
if headRepo != nil && !ghrepo.IsSame(baseRepo, headRepo) {
|
if headRepo != nil && !ghrepo.IsSame(baseRepo, headRepo) {
|
||||||
headBranchLabel = fmt.Sprintf("%s:%s", headRepo.RepoOwner(), headBranch)
|
headBranchLabel = fmt.Sprintf("%s:%s", headRepo.RepoOwner(), headBranch)
|
||||||
|
|
@ -194,46 +213,37 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isDraft, err := cmd.Flags().GetBool("draft")
|
isTerminal := opts.IO.IsStdinTTY() && opts.IO.IsStdoutTTY()
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse draft: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isWeb && !autofill {
|
if !opts.WebMode && !opts.Autofill {
|
||||||
message := "\nCreating pull request for %s into %s in %s\n\n"
|
message := "\nCreating pull request for %s into %s in %s\n\n"
|
||||||
if isDraft {
|
if opts.IsDraft {
|
||||||
message = "\nCreating draft pull request for %s into %s in %s\n\n"
|
message = "\nCreating draft pull request for %s into %s in %s\n\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectedToTerminal(cmd) {
|
if isTerminal {
|
||||||
fmt.Fprintf(colorableErr(cmd), message,
|
fmt.Fprintf(opts.IO.ErrOut, message,
|
||||||
utils.Cyan(headBranch),
|
utils.Cyan(headBranch),
|
||||||
utils.Cyan(baseBranch),
|
utils.Cyan(baseBranch),
|
||||||
ghrepo.FullName(baseRepo))
|
ghrepo.FullName(baseRepo))
|
||||||
if (title == "" || body == "") && defaultsErr != nil {
|
if (title == "" || body == "") && defaultsErr != nil {
|
||||||
fmt.Fprintf(colorableErr(cmd), "%s warning: could not compute title or body defaults: %s\n", utils.Yellow("!"), defaultsErr)
|
fmt.Fprintf(opts.IO.ErrOut, "%s warning: could not compute title or body defaults: %s\n", utils.Yellow("!"), defaultsErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tb := issueMetadataState{
|
tb := shared.IssueMetadataState{
|
||||||
Type: prMetadata,
|
Type: shared.PRMetadata,
|
||||||
Reviewers: reviewers,
|
Reviewers: opts.Reviewers,
|
||||||
Assignees: assignees,
|
Assignees: opts.Assignees,
|
||||||
Labels: labelNames,
|
Labels: opts.Labels,
|
||||||
Projects: projectNames,
|
Projects: opts.Projects,
|
||||||
Milestones: milestoneTitles,
|
Milestones: milestoneTitles,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !connectedToTerminal(cmd) {
|
interactive := isTerminal && !(opts.TitleProvided && opts.BodyProvided)
|
||||||
if !isWeb && (!cmd.Flags().Changed("title") && !autofill) {
|
|
||||||
return errors.New("--title or --fill required when not attached to a tty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interactive := connectedToTerminal(cmd) && !(cmd.Flags().Changed("title") && cmd.Flags().Changed("body"))
|
if !opts.WebMode && !opts.Autofill && interactive {
|
||||||
|
|
||||||
if !isWeb && !autofill && interactive {
|
|
||||||
var nonLegacyTemplateFiles []string
|
var nonLegacyTemplateFiles []string
|
||||||
var legacyTemplateFile *string
|
var legacyTemplateFile *string
|
||||||
if rootDir, err := git.ToplevelDir(); err == nil {
|
if rootDir, err := git.ToplevelDir(); err == nil {
|
||||||
|
|
@ -241,15 +251,21 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
nonLegacyTemplateFiles = githubtemplate.FindNonLegacy(rootDir, "PULL_REQUEST_TEMPLATE")
|
nonLegacyTemplateFiles = githubtemplate.FindNonLegacy(rootDir, "PULL_REQUEST_TEMPLATE")
|
||||||
legacyTemplateFile = githubtemplate.FindLegacy(rootDir, "PULL_REQUEST_TEMPLATE")
|
legacyTemplateFile = githubtemplate.FindLegacy(rootDir, "PULL_REQUEST_TEMPLATE")
|
||||||
}
|
}
|
||||||
err := titleBodySurvey(cmd, &tb, client, baseRepo, title, body, defs, nonLegacyTemplateFiles, legacyTemplateFile, true, baseRepo.ViewerCanTriage())
|
|
||||||
|
editorCommand, err := cmdutil.DetermineEditor(opts.Config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = shared.TitleBodySurvey(opts.IO, editorCommand, &tb, client, baseRepo, title, body, defs, nonLegacyTemplateFiles, legacyTemplateFile, true, baseRepo.ViewerCanTriage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not collect title and/or body: %w", err)
|
return fmt.Errorf("could not collect title and/or body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
action = tb.Action
|
action = tb.Action
|
||||||
|
|
||||||
if action == CancelAction {
|
if action == shared.CancelAction {
|
||||||
fmt.Fprintln(cmd.ErrOrStderr(), "Discarding.")
|
fmt.Fprintln(opts.IO.ErrOut, "Discarding.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,17 +277,10 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == SubmitAction && title == "" {
|
if action == shared.SubmitAction && title == "" {
|
||||||
return errors.New("pull request title must not be blank")
|
return errors.New("pull request title must not be blank")
|
||||||
}
|
}
|
||||||
|
|
||||||
if isDraft && isWeb {
|
|
||||||
return errors.New("the --draft flag is not supported with --web")
|
|
||||||
}
|
|
||||||
if len(reviewers) > 0 && isWeb {
|
|
||||||
return errors.New("the --reviewer flag is not supported with --web")
|
|
||||||
}
|
|
||||||
|
|
||||||
didForkRepo := false
|
didForkRepo := false
|
||||||
// if a head repository could not be determined so far, automatically create
|
// if a head repository could not be determined so far, automatically create
|
||||||
// one by forking the base repository
|
// one by forking the base repository
|
||||||
|
|
@ -303,7 +312,13 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
// In either case, we want to add the head repo as a new git remote so we
|
// In either case, we want to add the head repo as a new git remote so we
|
||||||
// can push to it.
|
// can push to it.
|
||||||
if headRemote == nil {
|
if headRemote == nil {
|
||||||
headRepoURL := formatRemoteURL(cmd, headRepo)
|
cfg, err := opts.Config()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cloneProtocol, _ := cfg.Get(headRepo.RepoHost(), "git_protocol")
|
||||||
|
|
||||||
|
headRepoURL := ghrepo.FormatRemoteURL(headRepo, cloneProtocol)
|
||||||
|
|
||||||
// TODO: prevent clashes with another remote of a same name
|
// TODO: prevent clashes with another remote of a same name
|
||||||
gitRemote, err := git.AddRemote("fork", headRepoURL)
|
gitRemote, err := git.AddRemote("fork", headRepoURL)
|
||||||
|
|
@ -326,7 +341,7 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
pushTries++
|
pushTries++
|
||||||
// first wait 2 seconds after forking, then 4s, then 6s
|
// first wait 2 seconds after forking, then 4s, then 6s
|
||||||
waitSeconds := 2 * pushTries
|
waitSeconds := 2 * pushTries
|
||||||
fmt.Fprintf(cmd.ErrOrStderr(), "waiting %s before retrying...\n", utils.Pluralize(waitSeconds, "second"))
|
fmt.Fprintf(opts.IO.ErrOut, "waiting %s before retrying...\n", utils.Pluralize(waitSeconds, "second"))
|
||||||
time.Sleep(time.Duration(waitSeconds) * time.Second)
|
time.Sleep(time.Duration(waitSeconds) * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -336,16 +351,16 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == SubmitAction {
|
if action == shared.SubmitAction {
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
"title": title,
|
"title": title,
|
||||||
"body": body,
|
"body": body,
|
||||||
"draft": isDraft,
|
"draft": opts.IsDraft,
|
||||||
"baseRefName": baseBranch,
|
"baseRefName": baseBranch,
|
||||||
"headRefName": headBranchLabel,
|
"headRefName": headBranchLabel,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = addMetadataToIssueParams(client, baseRepo, params, &tb)
|
err = shared.AddMetadataToIssueParams(client, baseRepo, params, &tb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -355,19 +370,14 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
return fmt.Errorf("failed to create pull request: %w", err)
|
return fmt.Errorf("failed to create pull request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(cmd.OutOrStdout(), pr.URL)
|
fmt.Fprintln(opts.IO.Out, pr.URL)
|
||||||
} else if action == PreviewAction {
|
} else if action == shared.PreviewAction {
|
||||||
milestone := ""
|
openURL, err := generateCompareURL(baseRepo, baseBranch, headBranchLabel, title, body, tb.Assignees, tb.Labels, tb.Projects, tb.Milestones)
|
||||||
if len(milestoneTitles) > 0 {
|
|
||||||
milestone = milestoneTitles[0]
|
|
||||||
}
|
|
||||||
openURL, err := generateCompareURL(baseRepo, baseBranch, headBranchLabel, title, body, assignees, labelNames, projectNames, milestone)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if connectedToTerminal(cmd) {
|
if isTerminal {
|
||||||
// TODO could exceed max url length for explorer
|
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
||||||
fmt.Fprintf(cmd.ErrOrStderr(), "Opening %s in your browser.\n", utils.DisplayURL(openURL))
|
|
||||||
}
|
}
|
||||||
return utils.OpenInBrowser(openURL)
|
return utils.OpenInBrowser(openURL)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -377,6 +387,34 @@ func prCreate(cmd *cobra.Command, _ []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func computeDefaults(baseRef, headRef string) (shared.Defaults, error) {
|
||||||
|
out := shared.Defaults{}
|
||||||
|
|
||||||
|
commits, err := git.Commits(baseRef, headRef)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(commits) == 1 {
|
||||||
|
out.Title = commits[0].Title
|
||||||
|
body, err := git.CommitBody(commits[0].Sha)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
out.Body = body
|
||||||
|
} else {
|
||||||
|
out.Title = utils.Humanize(headRef)
|
||||||
|
|
||||||
|
body := ""
|
||||||
|
for i := len(commits) - 1; i >= 0; i-- {
|
||||||
|
body += fmt.Sprintf("- %s\n", commits[i].Title)
|
||||||
|
}
|
||||||
|
out.Body = body
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func determineTrackingBranch(remotes context.Remotes, headBranch string) *git.TrackingRef {
|
func determineTrackingBranch(remotes context.Remotes, headBranch string) *git.TrackingRef {
|
||||||
refsForLookup := []string{"HEAD"}
|
refsForLookup := []string{"HEAD"}
|
||||||
var trackingRefs []git.TrackingRef
|
var trackingRefs []git.TrackingRef
|
||||||
|
|
@ -418,73 +456,11 @@ func determineTrackingBranch(remotes context.Remotes, headBranch string) *git.Tr
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func withPrAndIssueQueryParams(baseURL, title, body string, assignees, labels, projects []string, milestone string) (string, error) {
|
func generateCompareURL(r ghrepo.Interface, base, head, title, body string, assignees, labels, projects []string, milestones []string) (string, error) {
|
||||||
u, err := url.Parse(baseURL)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
q := u.Query()
|
|
||||||
if title != "" {
|
|
||||||
q.Set("title", title)
|
|
||||||
}
|
|
||||||
if body != "" {
|
|
||||||
q.Set("body", body)
|
|
||||||
}
|
|
||||||
if len(assignees) > 0 {
|
|
||||||
q.Set("assignees", strings.Join(assignees, ","))
|
|
||||||
}
|
|
||||||
if len(labels) > 0 {
|
|
||||||
q.Set("labels", strings.Join(labels, ","))
|
|
||||||
}
|
|
||||||
if len(projects) > 0 {
|
|
||||||
q.Set("projects", strings.Join(projects, ","))
|
|
||||||
}
|
|
||||||
if milestone != "" {
|
|
||||||
q.Set("milestone", milestone)
|
|
||||||
}
|
|
||||||
u.RawQuery = q.Encode()
|
|
||||||
return u.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateCompareURL(r ghrepo.Interface, base, head, title, body string, assignees, labels, projects []string, milestone string) (string, error) {
|
|
||||||
u := ghrepo.GenerateRepoURL(r, "compare/%s...%s?expand=1", base, head)
|
u := ghrepo.GenerateRepoURL(r, "compare/%s...%s?expand=1", base, head)
|
||||||
url, err := withPrAndIssueQueryParams(u, title, body, assignees, labels, projects, milestone)
|
url, err := shared.WithPrAndIssueQueryParams(u, title, body, assignees, labels, projects, milestones)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var prCreateCmd = &cobra.Command{
|
|
||||||
Use: "create",
|
|
||||||
Short: "Create a pull request",
|
|
||||||
Args: cmdutil.NoArgsQuoteReminder,
|
|
||||||
RunE: prCreate,
|
|
||||||
Example: heredoc.Doc(`
|
|
||||||
$ gh pr create --title "The bug is fixed" --body "Everything works again"
|
|
||||||
$ gh issue create --label "bug,help wanted"
|
|
||||||
$ gh issue create --label bug --label "help wanted"
|
|
||||||
$ gh pr create --reviewer monalisa,hubot
|
|
||||||
$ gh pr create --project "Roadmap"
|
|
||||||
$ gh pr create --base develop
|
|
||||||
`),
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
prCreateCmd.Flags().BoolP("draft", "d", false,
|
|
||||||
"Mark pull request as a draft")
|
|
||||||
prCreateCmd.Flags().StringP("title", "t", "",
|
|
||||||
"Supply a title. Will prompt for one otherwise.")
|
|
||||||
prCreateCmd.Flags().StringP("body", "b", "",
|
|
||||||
"Supply a body. Will prompt for one otherwise.")
|
|
||||||
prCreateCmd.Flags().StringP("base", "B", "",
|
|
||||||
"The branch into which you want your code merged")
|
|
||||||
prCreateCmd.Flags().BoolP("web", "w", false, "Open the web browser to create a pull request")
|
|
||||||
prCreateCmd.Flags().BoolP("fill", "f", false, "Do not prompt for title/body and just use commit info")
|
|
||||||
|
|
||||||
prCreateCmd.Flags().StringSliceP("reviewer", "r", nil, "Request reviews from people by their `login`")
|
|
||||||
prCreateCmd.Flags().StringSliceP("assignee", "a", nil, "Assign people by their `login`")
|
|
||||||
prCreateCmd.Flags().StringSliceP("label", "l", nil, "Add labels by `name`")
|
|
||||||
prCreateCmd.Flags().StringSliceP("project", "p", nil, "Add the pull request to projects by `name`")
|
|
||||||
prCreateCmd.Flags().StringP("milestone", "m", "", "Add the pull request to a milestone by `name`")
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +1,93 @@
|
||||||
package command
|
package create
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/cli/cli/context"
|
"github.com/cli/cli/context"
|
||||||
"github.com/cli/cli/git"
|
"github.com/cli/cli/git"
|
||||||
|
"github.com/cli/cli/internal/config"
|
||||||
"github.com/cli/cli/internal/ghrepo"
|
"github.com/cli/cli/internal/ghrepo"
|
||||||
|
"github.com/cli/cli/pkg/cmdutil"
|
||||||
"github.com/cli/cli/pkg/httpmock"
|
"github.com/cli/cli/pkg/httpmock"
|
||||||
|
"github.com/cli/cli/pkg/iostreams"
|
||||||
"github.com/cli/cli/pkg/prompt"
|
"github.com/cli/cli/pkg/prompt"
|
||||||
"github.com/cli/cli/test"
|
"github.com/cli/cli/test"
|
||||||
|
"github.com/google/shlex"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func eq(t *testing.T, got interface{}, expected interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
if !reflect.DeepEqual(got, expected) {
|
||||||
|
t.Errorf("expected: %v, got: %v", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCommand(rt http.RoundTripper, remotes context.Remotes, branch string, isTTY bool, cli string) (*test.CmdOut, error) {
|
||||||
|
io, _, stdout, stderr := iostreams.Test()
|
||||||
|
io.SetStdoutTTY(isTTY)
|
||||||
|
io.SetStdinTTY(isTTY)
|
||||||
|
io.SetStderrTTY(isTTY)
|
||||||
|
|
||||||
|
factory := &cmdutil.Factory{
|
||||||
|
IOStreams: io,
|
||||||
|
HttpClient: func() (*http.Client, error) {
|
||||||
|
return &http.Client{Transport: rt}, nil
|
||||||
|
},
|
||||||
|
Config: func() (config.Config, error) {
|
||||||
|
return config.NewBlankConfig(), nil
|
||||||
|
},
|
||||||
|
Remotes: func() (context.Remotes, error) {
|
||||||
|
if remotes != nil {
|
||||||
|
return remotes, nil
|
||||||
|
}
|
||||||
|
return context.Remotes{
|
||||||
|
{
|
||||||
|
Remote: &git.Remote{Name: "origin"},
|
||||||
|
Repo: ghrepo.New("OWNER", "REPO"),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
Branch: func() (string, error) {
|
||||||
|
return branch, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := NewCmdCreate(factory, nil)
|
||||||
|
cmd.PersistentFlags().StringP("repo", "R", "", "")
|
||||||
|
|
||||||
|
argv, err := shlex.Split(cli)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.SetArgs(argv)
|
||||||
|
|
||||||
|
cmd.SetIn(&bytes.Buffer{})
|
||||||
|
cmd.SetOut(ioutil.Discard)
|
||||||
|
cmd.SetErr(ioutil.Discard)
|
||||||
|
|
||||||
|
_, err = cmd.ExecuteC()
|
||||||
|
return &test.CmdOut{
|
||||||
|
OutBuf: stdout,
|
||||||
|
ErrBuf: stderr,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func initFakeHTTP() *httpmock.Registry {
|
||||||
|
return &httpmock.Registry{}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPRCreate_nontty_web(t *testing.T) {
|
func TestPRCreate_nontty_web(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(false)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -36,8 +104,8 @@ func TestPRCreate_nontty_web(t *testing.T) {
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
cs.Stub("") // browser
|
cs.Stub("") // browser
|
||||||
|
|
||||||
output, err := RunCommand(`pr create --web`)
|
output, err := runCommand(http, nil, "feature", false, `--web`)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
eq(t, output.String(), "")
|
eq(t, output.String(), "")
|
||||||
eq(t, output.Stderr(), "")
|
eq(t, output.Stderr(), "")
|
||||||
|
|
@ -50,33 +118,23 @@ func TestPRCreate_nontty_web(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_nontty_insufficient_flags(t *testing.T) {
|
func TestPRCreate_nontty_insufficient_flags(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(false)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
defer http.Verify(t)
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
|
||||||
] } } } }
|
|
||||||
`))
|
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
|
||||||
{ "data": { "repository": { "pullRequests": { "nodes" : [
|
|
||||||
] } } } }
|
|
||||||
`))
|
|
||||||
|
|
||||||
output, err := RunCommand("pr create")
|
output, err := runCommand(http, nil, "feature", false, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "--title or --fill required when not attached to a tty", err.Error())
|
assert.Equal(t, "--title or --fill required when not attached to a terminal", err.Error())
|
||||||
|
|
||||||
assert.Equal(t, "", output.String())
|
assert.Equal(t, "", output.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_nontty(t *testing.T) {
|
func TestPRCreate_nontty(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(false)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -101,8 +159,8 @@ func TestPRCreate_nontty(t *testing.T) {
|
||||||
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
|
|
||||||
output, err := RunCommand(`pr create -t "my title" -b "my body"`)
|
output, err := runCommand(http, nil, "feature", false, `-t "my title" -b "my body"`)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
||||||
reqBody := struct {
|
reqBody := struct {
|
||||||
|
|
@ -129,9 +187,9 @@ func TestPRCreate_nontty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate(t *testing.T) {
|
func TestPRCreate(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -156,8 +214,8 @@ func TestPRCreate(t *testing.T) {
|
||||||
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
|
|
||||||
output, err := RunCommand(`pr create -t "my title" -b "my body"`)
|
output, err := runCommand(http, nil, "feature", true, `-t "my title" -b "my body"`)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
||||||
reqBody := struct {
|
reqBody := struct {
|
||||||
|
|
@ -183,8 +241,6 @@ func TestPRCreate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_metadata(t *testing.T) {
|
func TestPRCreate_metadata(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
defer http.Verify(t)
|
defer http.Verify(t)
|
||||||
|
|
||||||
|
|
@ -301,16 +357,16 @@ func TestPRCreate_metadata(t *testing.T) {
|
||||||
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
|
|
||||||
output, err := RunCommand(`pr create -t TITLE -b BODY -a monalisa -l bug -l todo -p roadmap -m 'big one.oh' -r hubot -r monalisa -r /core -r /robots`)
|
output, err := runCommand(http, nil, "feature", true, `-t TITLE -b BODY -a monalisa -l bug -l todo -p roadmap -m 'big one.oh' -r hubot -r monalisa -r /core -r /robots`)
|
||||||
eq(t, err, nil)
|
eq(t, err, nil)
|
||||||
|
|
||||||
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_withForking(t *testing.T) {
|
func TestPRCreate_withForking(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponseWithPermission("OWNER", "REPO", "READ")
|
http.StubRepoResponseWithPermission("OWNER", "REPO", "READ")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -344,17 +400,17 @@ func TestPRCreate_withForking(t *testing.T) {
|
||||||
cs.Stub("") // git remote add
|
cs.Stub("") // git remote add
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
|
|
||||||
output, err := RunCommand(`pr create -t title -b body`)
|
output, err := runCommand(http, nil, "feature", true, `-t title -b body`)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
eq(t, http.Requests[3].URL.Path, "/repos/OWNER/REPO/forks")
|
eq(t, http.Requests[3].URL.Path, "/repos/OWNER/REPO/forks")
|
||||||
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_alreadyExists(t *testing.T) {
|
func TestPRCreate_alreadyExists(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -376,7 +432,7 @@ func TestPRCreate_alreadyExists(t *testing.T) {
|
||||||
cs.Stub("") // git status
|
cs.Stub("") // git status
|
||||||
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
||||||
|
|
||||||
_, err := RunCommand(`pr create`)
|
_, err := runCommand(http, nil, "feature", true, ``)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("error expected, got nil")
|
t.Fatal("error expected, got nil")
|
||||||
}
|
}
|
||||||
|
|
@ -386,9 +442,9 @@ func TestPRCreate_alreadyExists(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_alreadyExistsDifferentBase(t *testing.T) {
|
func TestPRCreate_alreadyExistsDifferentBase(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -412,16 +468,16 @@ func TestPRCreate_alreadyExistsDifferentBase(t *testing.T) {
|
||||||
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
||||||
cs.Stub("") // git rev-parse
|
cs.Stub("") // git rev-parse
|
||||||
|
|
||||||
_, err := RunCommand(`pr create -BanotherBase -t"cool" -b"nah"`)
|
_, err := runCommand(http, nil, "feature", true, `-BanotherBase -t"cool" -b"nah"`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("got unexpected error %q", err)
|
t.Errorf("got unexpected error %q", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_web(t *testing.T) {
|
func TestPRCreate_web(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -438,8 +494,8 @@ func TestPRCreate_web(t *testing.T) {
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
cs.Stub("") // browser
|
cs.Stub("") // browser
|
||||||
|
|
||||||
output, err := RunCommand(`pr create --web`)
|
output, err := runCommand(http, nil, "feature", true, `--web`)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
eq(t, output.String(), "")
|
eq(t, output.String(), "")
|
||||||
eq(t, output.Stderr(), "Opening github.com/OWNER/REPO/compare/master...feature in your browser.\n")
|
eq(t, output.Stderr(), "Opening github.com/OWNER/REPO/compare/master...feature in your browser.\n")
|
||||||
|
|
@ -451,9 +507,8 @@ func TestPRCreate_web(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_ReportsUncommittedChanges(t *testing.T) {
|
func TestPRCreate_ReportsUncommittedChanges(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
|
|
@ -479,7 +534,7 @@ func TestPRCreate_ReportsUncommittedChanges(t *testing.T) {
|
||||||
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
|
|
||||||
output, err := RunCommand(`pr create -t "my title" -b "my body"`)
|
output, err := runCommand(http, nil, "feature", true, `-t "my title" -b "my body"`)
|
||||||
eq(t, err, nil)
|
eq(t, err, nil)
|
||||||
|
|
||||||
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
||||||
|
|
@ -487,17 +542,20 @@ func TestPRCreate_ReportsUncommittedChanges(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_cross_repo_same_branch(t *testing.T) {
|
func TestPRCreate_cross_repo_same_branch(t *testing.T) {
|
||||||
defer stubTerminal(true)()
|
remotes := context.Remotes{
|
||||||
ctx := context.NewBlank()
|
{
|
||||||
ctx.SetBranch("default")
|
Remote: &git.Remote{Name: "origin"},
|
||||||
ctx.SetRemotes(map[string]string{
|
Repo: ghrepo.New("OWNER", "REPO"),
|
||||||
"origin": "OWNER/REPO",
|
},
|
||||||
"fork": "MYSELF/REPO",
|
{
|
||||||
})
|
Remote: &git.Remote{Name: "fork"},
|
||||||
initContext = func() context.Context {
|
Repo: ghrepo.New("MYSELF", "REPO"),
|
||||||
return ctx
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repo_000": {
|
{ "data": { "repo_000": {
|
||||||
"id": "REPOID0",
|
"id": "REPOID0",
|
||||||
|
|
@ -546,8 +604,8 @@ func TestPRCreate_cross_repo_same_branch(t *testing.T) {
|
||||||
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
cs.Stub("1234567890,commit 0\n2345678901,commit 1") // git log
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
|
|
||||||
output, err := RunCommand(`pr create -t "cross repo" -b "same branch"`)
|
output, err := runCommand(http, remotes, "default", true, `-t "cross repo" -b "same branch"`)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[2].Body)
|
bodyBytes, _ := ioutil.ReadAll(http.Requests[2].Body)
|
||||||
reqBody := struct {
|
reqBody := struct {
|
||||||
|
|
@ -575,9 +633,9 @@ func TestPRCreate_cross_repo_same_branch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_survey_defaults_multicommit(t *testing.T) {
|
func TestPRCreate_survey_defaults_multicommit(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "cool_bug-fixes")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -623,8 +681,8 @@ func TestPRCreate_survey_defaults_multicommit(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
output, err := RunCommand(`pr create`)
|
output, err := runCommand(http, nil, "cool_bug-fixes", true, ``)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
||||||
reqBody := struct {
|
reqBody := struct {
|
||||||
|
|
@ -652,10 +710,9 @@ func TestPRCreate_survey_defaults_multicommit(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_survey_defaults_monocommit(t *testing.T) {
|
func TestPRCreate_survey_defaults_monocommit(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
defer http.Verify(t)
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.Register(httpmock.GraphQL(`query RepositoryNetwork\b`), httpmock.StringResponse(httpmock.RepoNetworkStubResponse("OWNER", "REPO", "master", "WRITE")))
|
http.Register(httpmock.GraphQL(`query RepositoryNetwork\b`), httpmock.StringResponse(httpmock.RepoNetworkStubResponse("OWNER", "REPO", "master", "WRITE")))
|
||||||
http.Register(httpmock.GraphQL(`query RepositoryFindFork\b`), httpmock.StringResponse(`
|
http.Register(httpmock.GraphQL(`query RepositoryFindFork\b`), httpmock.StringResponse(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -708,15 +765,15 @@ func TestPRCreate_survey_defaults_monocommit(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
output, err := RunCommand(`pr create`)
|
output, err := runCommand(http, nil, "feature", true, ``)
|
||||||
eq(t, err, nil)
|
eq(t, err, nil)
|
||||||
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
eq(t, output.String(), "https://github.com/OWNER/REPO/pull/12\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_survey_autofill_nontty(t *testing.T) {
|
func TestPRCreate_survey_autofill_nontty(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(false)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -744,8 +801,8 @@ func TestPRCreate_survey_autofill_nontty(t *testing.T) {
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
cs.Stub("") // browser open
|
cs.Stub("") // browser open
|
||||||
|
|
||||||
output, err := RunCommand(`pr create -f`)
|
output, err := runCommand(http, nil, "feature", false, `-f`)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
||||||
reqBody := struct {
|
reqBody := struct {
|
||||||
|
|
@ -775,9 +832,9 @@ func TestPRCreate_survey_autofill_nontty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_survey_autofill(t *testing.T) {
|
func TestPRCreate_survey_autofill(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -805,8 +862,8 @@ func TestPRCreate_survey_autofill(t *testing.T) {
|
||||||
cs.Stub("") // git push
|
cs.Stub("") // git push
|
||||||
cs.Stub("") // browser open
|
cs.Stub("") // browser open
|
||||||
|
|
||||||
output, err := RunCommand(`pr create -f`)
|
output, err := runCommand(http, nil, "feature", true, `-f`)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
bodyBytes, _ := ioutil.ReadAll(http.Requests[3].Body)
|
||||||
reqBody := struct {
|
reqBody := struct {
|
||||||
|
|
@ -834,9 +891,9 @@ func TestPRCreate_survey_autofill(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_defaults_error_autofill(t *testing.T) {
|
func TestPRCreate_defaults_error_autofill(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
|
|
||||||
cs, cmdTeardown := test.InitCmdStubber()
|
cs, cmdTeardown := test.InitCmdStubber()
|
||||||
|
|
@ -847,15 +904,15 @@ func TestPRCreate_defaults_error_autofill(t *testing.T) {
|
||||||
cs.Stub("") // git status
|
cs.Stub("") // git status
|
||||||
cs.Stub("") // git log
|
cs.Stub("") // git log
|
||||||
|
|
||||||
_, err := RunCommand("pr create -f")
|
_, err := runCommand(http, nil, "feature", true, "-f")
|
||||||
|
|
||||||
eq(t, err.Error(), "could not compute title or body defaults: could not find any commits between origin/master and feature")
|
eq(t, err.Error(), "could not compute title or body defaults: could not find any commits between origin/master and feature")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_defaults_error_web(t *testing.T) {
|
func TestPRCreate_defaults_error_web(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
|
|
||||||
cs, cmdTeardown := test.InitCmdStubber()
|
cs, cmdTeardown := test.InitCmdStubber()
|
||||||
|
|
@ -866,15 +923,15 @@ func TestPRCreate_defaults_error_web(t *testing.T) {
|
||||||
cs.Stub("") // git status
|
cs.Stub("") // git status
|
||||||
cs.Stub("") // git log
|
cs.Stub("") // git log
|
||||||
|
|
||||||
_, err := RunCommand("pr create -w")
|
_, err := runCommand(http, nil, "feature", true, "-w")
|
||||||
|
|
||||||
eq(t, err.Error(), "could not compute title or body defaults: could not find any commits between origin/master and feature")
|
eq(t, err.Error(), "could not compute title or body defaults: could not find any commits between origin/master and feature")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRCreate_defaults_error_interactive(t *testing.T) {
|
func TestPRCreate_defaults_error_interactive(t *testing.T) {
|
||||||
initBlankContext("", "OWNER/REPO", "feature")
|
|
||||||
defer stubTerminal(true)()
|
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
http.StubRepoResponse("OWNER", "REPO")
|
http.StubRepoResponse("OWNER", "REPO")
|
||||||
http.StubResponse(200, bytes.NewBufferString(`
|
http.StubResponse(200, bytes.NewBufferString(`
|
||||||
{ "data": { "repository": { "forks": { "nodes": [
|
{ "data": { "repository": { "forks": { "nodes": [
|
||||||
|
|
@ -917,8 +974,8 @@ func TestPRCreate_defaults_error_interactive(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
output, err := RunCommand(`pr create`)
|
output, err := runCommand(http, nil, "feature", true, ``)
|
||||||
eq(t, err, nil)
|
require.NoError(t, err)
|
||||||
|
|
||||||
stderr := string(output.Stderr())
|
stderr := string(output.Stderr())
|
||||||
eq(t, strings.Contains(stderr, "warning: could not compute title or body defaults: could not find any commits"), true)
|
eq(t, strings.Contains(stderr, "warning: could not compute title or body defaults: could not find any commits"), true)
|
||||||
114
pkg/cmd/pr/shared/params.go
Normal file
114
pkg/cmd/pr/shared/params.go
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cli/cli/api"
|
||||||
|
"github.com/cli/cli/internal/ghrepo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithPrAndIssueQueryParams(baseURL, title, body string, assignees, labels, projects []string, milestones []string) (string, error) {
|
||||||
|
u, err := url.Parse(baseURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
q := u.Query()
|
||||||
|
if title != "" {
|
||||||
|
q.Set("title", title)
|
||||||
|
}
|
||||||
|
if body != "" {
|
||||||
|
q.Set("body", body)
|
||||||
|
}
|
||||||
|
if len(assignees) > 0 {
|
||||||
|
q.Set("assignees", strings.Join(assignees, ","))
|
||||||
|
}
|
||||||
|
if len(labels) > 0 {
|
||||||
|
q.Set("labels", strings.Join(labels, ","))
|
||||||
|
}
|
||||||
|
if len(projects) > 0 {
|
||||||
|
q.Set("projects", strings.Join(projects, ","))
|
||||||
|
}
|
||||||
|
if len(milestones) > 0 {
|
||||||
|
q.Set("milestone", milestones[0])
|
||||||
|
}
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
return u.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddMetadataToIssueParams(client *api.Client, baseRepo ghrepo.Interface, params map[string]interface{}, tb *IssueMetadataState) error {
|
||||||
|
if !tb.HasMetadata() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if tb.MetadataResult == nil {
|
||||||
|
resolveInput := api.RepoResolveInput{
|
||||||
|
Reviewers: tb.Reviewers,
|
||||||
|
Assignees: tb.Assignees,
|
||||||
|
Labels: tb.Labels,
|
||||||
|
Projects: tb.Projects,
|
||||||
|
Milestones: tb.Milestones,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
tb.MetadataResult, err = api.RepoResolveMetadataIDs(client, baseRepo, resolveInput)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assigneeIDs, err := tb.MetadataResult.MembersToIDs(tb.Assignees)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not assign user: %w", err)
|
||||||
|
}
|
||||||
|
params["assigneeIds"] = assigneeIDs
|
||||||
|
|
||||||
|
labelIDs, err := tb.MetadataResult.LabelsToIDs(tb.Labels)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not add label: %w", err)
|
||||||
|
}
|
||||||
|
params["labelIds"] = labelIDs
|
||||||
|
|
||||||
|
projectIDs, err := tb.MetadataResult.ProjectsToIDs(tb.Projects)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not add to project: %w", err)
|
||||||
|
}
|
||||||
|
params["projectIds"] = projectIDs
|
||||||
|
|
||||||
|
if len(tb.Milestones) > 0 {
|
||||||
|
milestoneID, err := tb.MetadataResult.MilestoneToID(tb.Milestones[0])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not add to milestone '%s': %w", tb.Milestones[0], err)
|
||||||
|
}
|
||||||
|
params["milestoneId"] = milestoneID
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tb.Reviewers) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var userReviewers []string
|
||||||
|
var teamReviewers []string
|
||||||
|
for _, r := range tb.Reviewers {
|
||||||
|
if strings.ContainsRune(r, '/') {
|
||||||
|
teamReviewers = append(teamReviewers, r)
|
||||||
|
} else {
|
||||||
|
userReviewers = append(userReviewers, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userReviewerIDs, err := tb.MetadataResult.MembersToIDs(userReviewers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not request reviewer: %w", err)
|
||||||
|
}
|
||||||
|
params["userReviewerIds"] = userReviewerIDs
|
||||||
|
|
||||||
|
teamReviewerIDs, err := tb.MetadataResult.TeamsToIDs(teamReviewers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not request reviewer: %w", err)
|
||||||
|
}
|
||||||
|
params["teamReviewerIds"] = teamReviewerIDs
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package command
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -7,21 +7,26 @@ import (
|
||||||
"github.com/cli/cli/api"
|
"github.com/cli/cli/api"
|
||||||
"github.com/cli/cli/internal/ghrepo"
|
"github.com/cli/cli/internal/ghrepo"
|
||||||
"github.com/cli/cli/pkg/githubtemplate"
|
"github.com/cli/cli/pkg/githubtemplate"
|
||||||
|
"github.com/cli/cli/pkg/iostreams"
|
||||||
"github.com/cli/cli/pkg/prompt"
|
"github.com/cli/cli/pkg/prompt"
|
||||||
"github.com/cli/cli/pkg/surveyext"
|
"github.com/cli/cli/pkg/surveyext"
|
||||||
"github.com/cli/cli/utils"
|
"github.com/cli/cli/utils"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Defaults struct {
|
||||||
|
Title string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
type Action int
|
type Action int
|
||||||
type metadataStateType int
|
type metadataStateType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
issueMetadata metadataStateType = iota
|
IssueMetadata metadataStateType = iota
|
||||||
prMetadata
|
PRMetadata
|
||||||
)
|
)
|
||||||
|
|
||||||
type issueMetadataState struct {
|
type IssueMetadataState struct {
|
||||||
Type metadataStateType
|
Type metadataStateType
|
||||||
|
|
||||||
Body string
|
Body string
|
||||||
|
|
@ -38,7 +43,7 @@ type issueMetadataState struct {
|
||||||
MetadataResult *api.RepoMetadataResult
|
MetadataResult *api.RepoMetadataResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *issueMetadataState) HasMetadata() bool {
|
func (tb *IssueMetadataState) HasMetadata() bool {
|
||||||
return len(tb.Reviewers) > 0 ||
|
return len(tb.Reviewers) > 0 ||
|
||||||
len(tb.Assignees) > 0 ||
|
len(tb.Assignees) > 0 ||
|
||||||
len(tb.Labels) > 0 ||
|
len(tb.Labels) > 0 ||
|
||||||
|
|
@ -112,9 +117,9 @@ func selectTemplate(nonLegacyTemplatePaths []string, legacyTemplatePath *string,
|
||||||
for _, p := range nonLegacyTemplatePaths {
|
for _, p := range nonLegacyTemplatePaths {
|
||||||
templateNames = append(templateNames, githubtemplate.ExtractName(p))
|
templateNames = append(templateNames, githubtemplate.ExtractName(p))
|
||||||
}
|
}
|
||||||
if metadataType == issueMetadata {
|
if metadataType == IssueMetadata {
|
||||||
templateNames = append(templateNames, "Open a blank issue")
|
templateNames = append(templateNames, "Open a blank issue")
|
||||||
} else if metadataType == prMetadata {
|
} else if metadataType == PRMetadata {
|
||||||
templateNames = append(templateNames, "Open a blank pull request")
|
templateNames = append(templateNames, "Open a blank pull request")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,12 +148,8 @@ func selectTemplate(nonLegacyTemplatePaths []string, legacyTemplatePath *string,
|
||||||
return string(templateContents), nil
|
return string(templateContents), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClient *api.Client, repo ghrepo.Interface, providedTitle, providedBody string, defs defaults, nonLegacyTemplatePaths []string, legacyTemplatePath *string, allowReviewers, allowMetadata bool) error {
|
// FIXME: this command has too many parameters and responsibilities
|
||||||
editorCommand, err := determineEditor(cmd)
|
func TitleBodySurvey(io *iostreams.IOStreams, editorCommand string, issueState *IssueMetadataState, apiClient *api.Client, repo ghrepo.Interface, providedTitle, providedBody string, defs Defaults, nonLegacyTemplatePaths []string, legacyTemplatePath *string, allowReviewers, allowMetadata bool) error {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
issueState.Title = defs.Title
|
issueState.Title = defs.Title
|
||||||
templateContents := ""
|
templateContents := ""
|
||||||
|
|
||||||
|
|
@ -198,7 +199,7 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
||||||
qs = append(qs, bodyQuestion)
|
qs = append(qs, bodyQuestion)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = prompt.SurveyAsk(qs, issueState)
|
err := prompt.SurveyAsk(qs, issueState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not prompt: %w", err)
|
return fmt.Errorf("could not prompt: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -249,7 +250,7 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
||||||
Projects: isChosen("Projects"),
|
Projects: isChosen("Projects"),
|
||||||
Milestones: isChosen("Milestone"),
|
Milestones: isChosen("Milestone"),
|
||||||
}
|
}
|
||||||
s := utils.Spinner(cmd.OutOrStderr())
|
s := utils.Spinner(io.ErrOut)
|
||||||
utils.StartSpinner(s)
|
utils.StartSpinner(s)
|
||||||
issueState.MetadataResult, err = api.RepoMetadata(apiClient, repo, metadataInput)
|
issueState.MetadataResult, err = api.RepoMetadata(apiClient, repo, metadataInput)
|
||||||
utils.StopSpinner(s)
|
utils.StopSpinner(s)
|
||||||
|
|
@ -297,7 +298,7 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
cmd.PrintErrln("warning: no available reviewers")
|
fmt.Fprintln(io.ErrOut, "warning: no available reviewers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isChosen("Assignees") {
|
if isChosen("Assignees") {
|
||||||
|
|
@ -311,7 +312,7 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
cmd.PrintErrln("warning: no assignable users")
|
fmt.Fprintln(io.ErrOut, "warning: no assignable users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isChosen("Labels") {
|
if isChosen("Labels") {
|
||||||
|
|
@ -325,7 +326,7 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
cmd.PrintErrln("warning: no labels in the repository")
|
fmt.Fprintln(io.ErrOut, "warning: no labels in the repository")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isChosen("Projects") {
|
if isChosen("Projects") {
|
||||||
|
|
@ -339,7 +340,7 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
cmd.PrintErrln("warning: no projects to choose from")
|
fmt.Fprintln(io.ErrOut, "warning: no projects to choose from")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isChosen("Milestone") {
|
if isChosen("Milestone") {
|
||||||
|
|
@ -357,7 +358,7 @@ func titleBodySurvey(cmd *cobra.Command, issueState *issueMetadataState, apiClie
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
cmd.PrintErrln("warning: no milestones in the repository")
|
fmt.Fprintln(io.ErrOut, "warning: no milestones in the repository")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
values := metadataValues{}
|
values := metadataValues{}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue