2013-05-29 22:58:46 +04:00
|
|
|
package commands
|
2013-04-11 19:33:43 +04:00
|
|
|
|
|
|
|
import (
|
2013-04-29 00:41:45 +04:00
|
|
|
"fmt"
|
2016-10-22 13:28:32 +03:00
|
|
|
"os"
|
2013-04-24 14:00:45 +04:00
|
|
|
"regexp"
|
2016-08-08 01:43:48 +03:00
|
|
|
"strconv"
|
2013-04-28 21:45:00 +04:00
|
|
|
"strings"
|
2016-10-22 13:10:00 +03:00
|
|
|
"time"
|
2014-02-24 01:34:10 +04:00
|
|
|
|
|
|
|
"github.com/github/hub/git"
|
|
|
|
"github.com/github/hub/github"
|
2016-09-11 00:49:58 +03:00
|
|
|
"github.com/github/hub/ui"
|
2014-02-24 01:34:10 +04:00
|
|
|
"github.com/github/hub/utils"
|
2013-04-11 19:33:43 +04:00
|
|
|
)
|
|
|
|
|
2013-06-30 00:54:40 +04:00
|
|
|
var cmdPullRequest = &Command{
|
2016-01-24 11:56:18 +03:00
|
|
|
Run: pullRequest,
|
|
|
|
Usage: `
|
2018-01-22 14:53:13 +03:00
|
|
|
pull-request [-focp] [-b <BASE>] [-h <HEAD>] [-r <REVIEWERS> ] [-a <ASSIGNEES>] [-M <MILESTONE>] [-l <LABELS>]
|
2018-05-29 18:17:44 +03:00
|
|
|
pull-request -m <MESSAGE> [--edit]
|
2016-08-21 13:26:42 +03:00
|
|
|
pull-request -F <FILE> [--edit]
|
2016-01-24 11:56:18 +03:00
|
|
|
pull-request -i <ISSUE>
|
|
|
|
`,
|
|
|
|
Long: `Create a GitHub pull request.
|
|
|
|
|
|
|
|
## Options:
|
|
|
|
-f, --force
|
|
|
|
Skip the check for unpushed commits.
|
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-m, --message=<MESSAGE>
|
2018-11-28 04:25:52 +03:00
|
|
|
The text up to the first blank line in <MESSAGE> is treated as the pull
|
|
|
|
request title, and the rest is used as pull request description in Markdown
|
|
|
|
format.
|
2016-01-24 11:56:18 +03:00
|
|
|
|
2018-11-28 05:05:15 +03:00
|
|
|
If multiple <MESSAGE> options are given, their values are concatenated as
|
|
|
|
separate paragraphs.
|
|
|
|
|
2018-05-03 02:02:52 +03:00
|
|
|
--no-edit
|
|
|
|
Use the message from the first commit on the branch as pull request title
|
|
|
|
and description without opening a text editor.
|
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-F, --file=<FILE>
|
2016-01-24 11:56:18 +03:00
|
|
|
Read the pull request title and description from <FILE>.
|
|
|
|
|
2016-08-21 13:26:42 +03:00
|
|
|
-e, --edit
|
|
|
|
Further edit the contents of <FILE> in a text editor before submitting.
|
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-i, --issue=<ISSUE>
|
|
|
|
(Deprecated) Convert an issue to a pull request. <ISSUE> may be an issue
|
|
|
|
number or a URL.
|
2016-01-24 11:56:18 +03:00
|
|
|
|
|
|
|
-o, --browse
|
|
|
|
Open the new pull request in a web browser.
|
|
|
|
|
2016-09-11 19:21:23 +03:00
|
|
|
-c, --copy
|
|
|
|
Put the URL of the new pull request to clipboard instead of printing it.
|
|
|
|
|
2016-10-03 23:40:20 +03:00
|
|
|
-p, --push
|
2016-10-20 23:53:27 +03:00
|
|
|
Push the current branch to <HEAD> before creating the pull request.
|
2016-10-03 23:40:20 +03:00
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-b, --base=<BASE>
|
2018-12-28 09:21:03 +03:00
|
|
|
The base branch in the "[<OWNER>:]<BRANCH>" format. Defaults to the default
|
|
|
|
branch of the upstream repository (usually "master").
|
|
|
|
|
|
|
|
See the "CONVENTIONS" section of hub(1) for more information on how hub
|
|
|
|
selects the defaults in case of multiple git remotes.
|
2016-01-24 11:56:18 +03:00
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-h, --head=<HEAD>
|
2018-12-28 09:21:03 +03:00
|
|
|
The head branch in "[<OWNER>:]<BRANCH>" format. Defaults to the currently
|
|
|
|
checked out branch.
|
2016-01-24 11:56:18 +03:00
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-r, --reviewer=<USERS>
|
2017-04-17 18:13:23 +03:00
|
|
|
A comma-separated list of GitHub handles to request a review from.
|
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-a, --assign=<USERS>
|
2016-08-10 12:44:38 +03:00
|
|
|
A comma-separated list of GitHub handles to assign to this pull request.
|
2016-01-24 11:56:18 +03:00
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-M, --milestone=<NAME>
|
2018-02-12 02:26:50 +03:00
|
|
|
The milestone name to add to this pull request. Passing the milestone number
|
|
|
|
is deprecated.
|
2016-01-24 11:56:18 +03:00
|
|
|
|
2018-12-18 19:49:09 +03:00
|
|
|
-l, --labels=<LABELS>
|
2018-10-19 17:32:18 +03:00
|
|
|
Add a comma-separated list of labels to this pull request. Labels will be
|
|
|
|
created if they don't already exist.
|
2016-01-24 18:50:01 +03:00
|
|
|
|
2018-09-06 13:55:39 +03:00
|
|
|
## Examples:
|
|
|
|
$ hub pull-request
|
|
|
|
[ opens a text editor for writing title and message ]
|
|
|
|
[ creates a pull request for the current branch ]
|
|
|
|
|
|
|
|
$ hub pull-request --base OWNER:master --head MYUSER:my-branch
|
|
|
|
[ creates a pull request with explicit base and head branches ]
|
|
|
|
|
|
|
|
$ hub pull-request --browse -m "My title"
|
|
|
|
[ creates a pull request with the given title and opens it in a browser ]
|
|
|
|
|
2018-09-06 13:57:45 +03:00
|
|
|
$ hub pull-request -F - --edit < path/to/message-template.md
|
2018-09-06 13:55:39 +03:00
|
|
|
[ further edit the title and message received on standard input ]
|
|
|
|
|
2016-10-22 13:28:32 +03:00
|
|
|
## Configuration:
|
|
|
|
|
|
|
|
HUB_RETRY_TIMEOUT=<SECONDS>
|
|
|
|
The maximum time to keep retrying after HTTP 422 on '--push' (default: 9).
|
|
|
|
|
2016-01-24 18:50:01 +03:00
|
|
|
## See also:
|
|
|
|
|
|
|
|
hub(1), hub-merge(1), hub-checkout(1)
|
2013-04-11 19:33:43 +04:00
|
|
|
`,
|
|
|
|
}
|
|
|
|
|
2013-12-07 19:43:55 +04:00
|
|
|
var (
|
|
|
|
flagPullRequestBase,
|
|
|
|
flagPullRequestHead,
|
|
|
|
flagPullRequestIssue,
|
2018-02-12 02:26:50 +03:00
|
|
|
flagPullRequestMilestone,
|
2013-12-07 19:43:55 +04:00
|
|
|
flagPullRequestFile string
|
2016-08-15 15:02:43 +03:00
|
|
|
|
2018-11-28 05:05:15 +03:00
|
|
|
flagPullRequestMessage messageBlocks
|
|
|
|
|
2014-04-11 08:16:50 +04:00
|
|
|
flagPullRequestBrowse,
|
2016-09-11 19:21:23 +03:00
|
|
|
flagPullRequestCopy,
|
2016-08-21 12:33:39 +03:00
|
|
|
flagPullRequestEdit,
|
2016-10-03 23:40:20 +03:00
|
|
|
flagPullRequestPush,
|
2018-05-03 02:02:52 +03:00
|
|
|
flagPullRequestForce,
|
|
|
|
flagPullRequestNoEdit bool
|
2016-08-15 15:02:43 +03:00
|
|
|
|
|
|
|
flagPullRequestAssignees,
|
2017-04-17 18:13:23 +03:00
|
|
|
flagPullRequestReviewers,
|
2016-08-15 15:02:43 +03:00
|
|
|
flagPullRequestLabels listFlag
|
2013-12-07 19:43:55 +04:00
|
|
|
)
|
2013-04-11 19:33:43 +04:00
|
|
|
|
|
|
|
func init() {
|
2013-12-30 03:07:58 +04:00
|
|
|
cmdPullRequest.Flag.StringVarP(&flagPullRequestBase, "base", "b", "", "BASE")
|
|
|
|
cmdPullRequest.Flag.StringVarP(&flagPullRequestHead, "head", "h", "", "HEAD")
|
|
|
|
cmdPullRequest.Flag.StringVarP(&flagPullRequestIssue, "issue", "i", "", "ISSUE")
|
2014-04-11 08:16:50 +04:00
|
|
|
cmdPullRequest.Flag.BoolVarP(&flagPullRequestBrowse, "browse", "o", false, "BROWSE")
|
2016-09-11 19:21:23 +03:00
|
|
|
cmdPullRequest.Flag.BoolVarP(&flagPullRequestCopy, "copy", "c", false, "COPY")
|
2018-11-28 05:05:15 +03:00
|
|
|
cmdPullRequest.Flag.VarP(&flagPullRequestMessage, "message", "m", "MESSAGE")
|
2016-08-21 12:33:39 +03:00
|
|
|
cmdPullRequest.Flag.BoolVarP(&flagPullRequestEdit, "edit", "e", false, "EDIT")
|
2016-10-03 23:40:20 +03:00
|
|
|
cmdPullRequest.Flag.BoolVarP(&flagPullRequestPush, "push", "p", false, "PUSH")
|
2013-12-30 03:07:58 +04:00
|
|
|
cmdPullRequest.Flag.BoolVarP(&flagPullRequestForce, "force", "f", false, "FORCE")
|
2018-05-03 02:02:52 +03:00
|
|
|
cmdPullRequest.Flag.BoolVarP(&flagPullRequestNoEdit, "no-edit", "", false, "NO-EDIT")
|
2013-12-30 03:07:58 +04:00
|
|
|
cmdPullRequest.Flag.StringVarP(&flagPullRequestFile, "file", "F", "", "FILE")
|
2016-08-15 15:02:43 +03:00
|
|
|
cmdPullRequest.Flag.VarP(&flagPullRequestAssignees, "assign", "a", "USERS")
|
2017-04-17 18:13:23 +03:00
|
|
|
cmdPullRequest.Flag.VarP(&flagPullRequestReviewers, "reviewer", "r", "USERS")
|
2018-02-12 02:26:50 +03:00
|
|
|
cmdPullRequest.Flag.StringVarP(&flagPullRequestMilestone, "milestone", "M", "", "MILESTONE")
|
2016-08-15 15:02:43 +03:00
|
|
|
cmdPullRequest.Flag.VarP(&flagPullRequestLabels, "labels", "l", "LABELS")
|
2013-12-30 02:18:14 +04:00
|
|
|
|
|
|
|
CmdRunner.Use(cmdPullRequest)
|
2013-04-11 19:33:43 +04:00
|
|
|
}
|
|
|
|
|
2013-06-30 00:54:40 +04:00
|
|
|
func pullRequest(cmd *Command, args *Args) {
|
2014-04-02 00:40:02 +04:00
|
|
|
localRepo, err := github.LocalRepo()
|
|
|
|
utils.Check(err)
|
2013-12-06 22:50:19 +04:00
|
|
|
|
|
|
|
currentBranch, err := localRepo.CurrentBranch()
|
2013-12-06 22:54:11 +04:00
|
|
|
utils.Check(err)
|
2013-12-06 22:50:19 +04:00
|
|
|
|
|
|
|
baseProject, err := localRepo.MainProject()
|
2013-12-06 22:54:11 +04:00
|
|
|
utils.Check(err)
|
2013-12-06 22:50:19 +04:00
|
|
|
|
2014-09-15 04:46:13 +04:00
|
|
|
host, err := github.CurrentConfig().PromptForHost(baseProject.Host)
|
2014-03-28 03:42:14 +04:00
|
|
|
if err != nil {
|
|
|
|
utils.Check(github.FormatError("creating pull request", err))
|
|
|
|
}
|
2016-08-09 22:40:16 +03:00
|
|
|
client := github.NewClientWithHost(host)
|
2013-12-28 13:14:07 +04:00
|
|
|
|
2014-04-09 20:22:20 +04:00
|
|
|
trackedBranch, headProject, err := localRepo.RemoteBranchAndProject(host.User, false)
|
2013-12-06 22:50:19 +04:00
|
|
|
utils.Check(err)
|
|
|
|
|
2013-12-07 04:51:11 +04:00
|
|
|
var (
|
2013-12-28 13:14:07 +04:00
|
|
|
base, head string
|
|
|
|
force bool
|
2013-12-07 04:51:11 +04:00
|
|
|
)
|
2013-12-08 12:33:48 +04:00
|
|
|
|
2013-12-07 19:43:55 +04:00
|
|
|
force = flagPullRequestForce
|
2013-12-08 12:33:48 +04:00
|
|
|
|
2013-12-06 22:50:19 +04:00
|
|
|
if flagPullRequestBase != "" {
|
2013-12-08 12:33:48 +04:00
|
|
|
baseProject, base = parsePullRequestProject(baseProject, flagPullRequestBase)
|
2013-12-06 22:50:19 +04:00
|
|
|
}
|
2013-12-07 04:51:11 +04:00
|
|
|
|
2013-12-06 22:50:19 +04:00
|
|
|
if flagPullRequestHead != "" {
|
2013-12-08 12:33:48 +04:00
|
|
|
headProject, head = parsePullRequestProject(headProject, flagPullRequestHead)
|
2013-12-06 22:50:19 +04:00
|
|
|
}
|
2013-12-07 04:51:11 +04:00
|
|
|
|
2013-07-02 22:56:45 +04:00
|
|
|
if args.ParamsSize() == 1 {
|
2013-12-06 22:50:19 +04:00
|
|
|
arg := args.RemoveParam(0)
|
2013-12-08 12:33:48 +04:00
|
|
|
flagPullRequestIssue = parsePullRequestIssueNumber(arg)
|
2013-06-02 05:16:18 +04:00
|
|
|
}
|
|
|
|
|
2018-12-28 05:01:30 +03:00
|
|
|
baseRemote, _ := localRepo.RemoteForProject(baseProject)
|
|
|
|
if base == "" && baseRemote != nil {
|
|
|
|
base = localRepo.DefaultBranch(baseRemote).ShortName()
|
2013-12-06 22:50:19 +04:00
|
|
|
}
|
|
|
|
|
2014-03-05 20:37:17 +04:00
|
|
|
if head == "" && trackedBranch != nil {
|
2013-12-28 13:14:07 +04:00
|
|
|
if !trackedBranch.IsRemote() {
|
|
|
|
// the current branch tracking another branch
|
|
|
|
// pretend there's no upstream at all
|
|
|
|
trackedBranch = nil
|
|
|
|
} else {
|
2014-07-30 09:25:23 +04:00
|
|
|
if baseProject.SameAs(headProject) && base == trackedBranch.ShortName() {
|
2013-12-08 12:33:48 +04:00
|
|
|
e := fmt.Errorf(`Aborted: head branch is the same as base ("%s")`, base)
|
|
|
|
e = fmt.Errorf("%s\n(use `-h <branch>` to specify an explicit pull request head)", e)
|
|
|
|
utils.Check(e)
|
2013-12-06 22:50:19 +04:00
|
|
|
}
|
|
|
|
}
|
2014-03-05 20:37:17 +04:00
|
|
|
}
|
|
|
|
|
2014-03-05 20:39:23 +04:00
|
|
|
if head == "" {
|
|
|
|
if trackedBranch == nil {
|
|
|
|
head = currentBranch.ShortName()
|
|
|
|
} else {
|
|
|
|
head = trackedBranch.ShortName()
|
|
|
|
}
|
2013-12-06 22:50:19 +04:00
|
|
|
}
|
|
|
|
|
2016-08-09 22:40:16 +03:00
|
|
|
if headRepo, err := client.Repository(headProject); err == nil {
|
|
|
|
headProject.Owner = headRepo.Owner.Login
|
|
|
|
headProject.Name = headRepo.Name
|
|
|
|
}
|
|
|
|
|
2013-12-06 22:50:19 +04:00
|
|
|
fullBase := fmt.Sprintf("%s:%s", baseProject.Owner, base)
|
|
|
|
fullHead := fmt.Sprintf("%s:%s", headProject.Owner, head)
|
|
|
|
|
2013-12-19 22:19:38 +04:00
|
|
|
if !force && trackedBranch != nil {
|
|
|
|
remoteCommits, _ := git.RefList(trackedBranch.LongName(), "")
|
|
|
|
if len(remoteCommits) > 0 {
|
|
|
|
err = fmt.Errorf("Aborted: %d commits are not yet pushed to %s", len(remoteCommits), trackedBranch.LongName())
|
|
|
|
err = fmt.Errorf("%s\n(use `-f` to force submit a pull request anyway)", err)
|
|
|
|
utils.Check(err)
|
|
|
|
}
|
2013-12-07 19:43:55 +04:00
|
|
|
}
|
|
|
|
|
2018-01-23 17:25:43 +03:00
|
|
|
messageBuilder := &github.MessageBuilder{
|
|
|
|
Filename: "PULLREQ_EDITMSG",
|
|
|
|
Title: "pull request",
|
|
|
|
}
|
2016-08-21 13:26:42 +03:00
|
|
|
|
2016-10-03 23:40:20 +03:00
|
|
|
baseTracking := base
|
|
|
|
headTracking := head
|
|
|
|
|
2018-12-28 05:01:30 +03:00
|
|
|
remote := baseRemote
|
2016-10-03 23:40:20 +03:00
|
|
|
if remote != nil {
|
|
|
|
baseTracking = fmt.Sprintf("%s/%s", remote.Name, base)
|
|
|
|
}
|
|
|
|
if remote == nil || !baseProject.SameAs(headProject) {
|
2018-12-28 05:01:30 +03:00
|
|
|
remote, _ = localRepo.RemoteForProject(headProject)
|
2016-10-03 23:40:20 +03:00
|
|
|
}
|
|
|
|
if remote != nil {
|
|
|
|
headTracking = fmt.Sprintf("%s/%s", remote.Name, head)
|
|
|
|
}
|
|
|
|
|
2016-10-20 23:53:27 +03:00
|
|
|
if flagPullRequestPush && remote == nil {
|
|
|
|
utils.Check(fmt.Errorf("Can't find remote for %s", head))
|
|
|
|
}
|
|
|
|
|
2018-01-23 20:08:23 +03:00
|
|
|
messageBuilder.AddCommentedSection(fmt.Sprintf(`Requesting a pull to %s from %s
|
|
|
|
|
|
|
|
Write a message for this pull request. The first block
|
|
|
|
of text is the title and the rest is the description.`, fullBase, fullHead))
|
|
|
|
|
2018-11-28 05:05:15 +03:00
|
|
|
if len(flagPullRequestMessage) > 0 {
|
|
|
|
messageBuilder.Message = flagPullRequestMessage.String()
|
2018-01-23 17:25:43 +03:00
|
|
|
messageBuilder.Edit = flagPullRequestEdit
|
2016-08-21 13:26:42 +03:00
|
|
|
} else if cmd.FlagPassed("file") {
|
2018-01-23 17:25:43 +03:00
|
|
|
messageBuilder.Message, err = msgFromFile(flagPullRequestFile)
|
2016-08-21 13:26:42 +03:00
|
|
|
utils.Check(err)
|
2018-01-23 17:25:43 +03:00
|
|
|
messageBuilder.Edit = flagPullRequestEdit
|
2018-05-03 02:02:52 +03:00
|
|
|
} else if flagPullRequestNoEdit {
|
|
|
|
commits, _ := git.RefList(baseTracking, head)
|
|
|
|
if len(commits) == 0 {
|
2018-06-06 16:53:43 +03:00
|
|
|
utils.Check(fmt.Errorf("Aborted: no commits detected between %s and %s", baseTracking, head))
|
2018-05-03 02:02:52 +03:00
|
|
|
}
|
|
|
|
message, err := git.Show(commits[len(commits)-1])
|
|
|
|
utils.Check(err)
|
|
|
|
messageBuilder.Message = message
|
2016-08-21 13:26:42 +03:00
|
|
|
} else if flagPullRequestIssue == "" {
|
2018-01-23 17:25:43 +03:00
|
|
|
messageBuilder.Edit = true
|
|
|
|
|
2016-10-03 23:40:20 +03:00
|
|
|
headForMessage := headTracking
|
|
|
|
if flagPullRequestPush {
|
|
|
|
headForMessage = head
|
2014-10-20 04:49:05 +04:00
|
|
|
}
|
|
|
|
|
2017-07-31 18:06:56 +03:00
|
|
|
message := ""
|
|
|
|
commitLogs := ""
|
|
|
|
|
|
|
|
commits, _ := git.RefList(baseTracking, headForMessage)
|
|
|
|
if len(commits) == 1 {
|
|
|
|
message, err = git.Show(commits[0])
|
|
|
|
utils.Check(err)
|
2018-05-28 21:00:49 +03:00
|
|
|
|
2018-05-29 13:23:39 +03:00
|
|
|
re := regexp.MustCompile(`\nSigned-off-by:\s.*$`)
|
2018-05-28 21:00:49 +03:00
|
|
|
message = re.ReplaceAllString(message, "")
|
2017-07-31 18:06:56 +03:00
|
|
|
} else if len(commits) > 1 {
|
|
|
|
commitLogs, err = git.Log(baseTracking, headForMessage)
|
|
|
|
utils.Check(err)
|
|
|
|
}
|
|
|
|
|
2018-01-23 20:08:23 +03:00
|
|
|
if commitLogs != "" {
|
|
|
|
messageBuilder.AddCommentedSection("\nChanges:\n\n" + strings.TrimSpace(commitLogs))
|
|
|
|
}
|
|
|
|
|
2017-07-31 18:06:56 +03:00
|
|
|
workdir, _ := git.WorkdirName()
|
|
|
|
if workdir != "" {
|
|
|
|
template, _ := github.ReadTemplate(github.PullRequestTemplate, workdir)
|
|
|
|
if template != "" {
|
2018-05-29 16:06:05 +03:00
|
|
|
message = message + "\n\n\n" + template
|
2017-07-31 18:06:56 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-23 17:25:43 +03:00
|
|
|
messageBuilder.Message = message
|
2015-06-08 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2018-01-23 17:25:43 +03:00
|
|
|
title, body, err := messageBuilder.Extract()
|
|
|
|
utils.Check(err)
|
|
|
|
|
2013-07-05 22:10:24 +04:00
|
|
|
if title == "" && flagPullRequestIssue == "" {
|
|
|
|
utils.Check(fmt.Errorf("Aborting due to empty pull request title"))
|
|
|
|
}
|
2013-05-28 21:13:55 +04:00
|
|
|
|
2016-10-03 23:40:20 +03:00
|
|
|
if flagPullRequestPush {
|
|
|
|
if args.Noop {
|
|
|
|
args.Before(fmt.Sprintf("Would push to %s/%s", remote.Name, head), "")
|
|
|
|
} else {
|
2016-12-08 00:47:46 +03:00
|
|
|
err = git.Spawn("push", "--set-upstream", remote.Name, fmt.Sprintf("HEAD:%s", head))
|
2016-10-20 23:53:27 +03:00
|
|
|
utils.Check(err)
|
2016-10-03 23:40:20 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 02:26:50 +03:00
|
|
|
milestoneNumber := 0
|
|
|
|
if flagPullRequestMilestone != "" {
|
|
|
|
// BC: Don't try to resolve milestone name if it's an integer
|
|
|
|
milestoneNumber, err = strconv.Atoi(flagPullRequestMilestone)
|
|
|
|
if err != nil {
|
|
|
|
milestones, err := client.FetchMilestones(baseProject)
|
|
|
|
utils.Check(err)
|
|
|
|
milestoneNumber, err = findMilestoneNumber(milestones, flagPullRequestMilestone)
|
|
|
|
utils.Check(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-06 00:45:22 +04:00
|
|
|
var pullRequestURL string
|
2013-07-05 22:10:24 +04:00
|
|
|
if args.Noop {
|
2013-12-06 22:50:19 +04:00
|
|
|
args.Before(fmt.Sprintf("Would request a pull request to %s from %s", fullBase, fullHead), "")
|
2013-07-06 00:45:22 +04:00
|
|
|
pullRequestURL = "PULL_REQUEST_URL"
|
2013-07-05 22:10:24 +04:00
|
|
|
} else {
|
2016-08-08 01:43:48 +03:00
|
|
|
params := map[string]interface{}{
|
|
|
|
"base": base,
|
|
|
|
"head": fullHead,
|
|
|
|
}
|
2014-02-24 02:24:37 +04:00
|
|
|
|
2013-07-05 22:10:24 +04:00
|
|
|
if title != "" {
|
2016-08-08 01:43:48 +03:00
|
|
|
params["title"] = title
|
|
|
|
if body != "" {
|
|
|
|
params["body"] = body
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
issueNum, _ := strconv.Atoi(flagPullRequestIssue)
|
|
|
|
params["issue"] = issueNum
|
2013-07-05 22:10:24 +04:00
|
|
|
}
|
2016-10-22 13:10:00 +03:00
|
|
|
|
|
|
|
startedAt := time.Now()
|
|
|
|
numRetries := 0
|
|
|
|
retryDelay := 2
|
|
|
|
retryAllowance := 0
|
|
|
|
if flagPullRequestPush {
|
2016-10-22 13:28:32 +03:00
|
|
|
if allowanceFromEnv := os.Getenv("HUB_RETRY_TIMEOUT"); allowanceFromEnv != "" {
|
|
|
|
retryAllowance, err = strconv.Atoi(allowanceFromEnv)
|
|
|
|
utils.Check(err)
|
|
|
|
} else {
|
|
|
|
retryAllowance = 9
|
|
|
|
}
|
2016-10-22 13:10:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var pr *github.PullRequest
|
|
|
|
for {
|
|
|
|
pr, err = client.CreatePullRequest(baseProject, params)
|
|
|
|
if err != nil && strings.Contains(err.Error(), `Invalid value for "head"`) {
|
|
|
|
if retryAllowance > 0 {
|
|
|
|
retryAllowance -= retryDelay
|
|
|
|
time.Sleep(time.Duration(retryDelay) * time.Second)
|
|
|
|
retryDelay += 1
|
|
|
|
numRetries += 1
|
|
|
|
} else {
|
|
|
|
if numRetries > 0 {
|
|
|
|
duration := time.Now().Sub(startedAt)
|
|
|
|
err = fmt.Errorf("%s\nGiven up after retrying for %.1f seconds.", err, duration.Seconds())
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2013-04-28 21:45:00 +04:00
|
|
|
|
2018-01-23 17:25:43 +03:00
|
|
|
if err == nil {
|
|
|
|
defer messageBuilder.Cleanup()
|
2014-03-02 23:43:43 +04:00
|
|
|
}
|
|
|
|
|
2014-02-24 02:24:37 +04:00
|
|
|
utils.Check(err)
|
2015-04-02 16:53:10 +03:00
|
|
|
|
2016-08-14 20:54:31 +03:00
|
|
|
pullRequestURL = pr.HtmlUrl
|
2015-04-02 16:53:10 +03:00
|
|
|
|
2016-09-09 00:27:32 +03:00
|
|
|
params = map[string]interface{}{}
|
|
|
|
if len(flagPullRequestLabels) > 0 {
|
|
|
|
params["labels"] = flagPullRequestLabels
|
|
|
|
}
|
|
|
|
if len(flagPullRequestAssignees) > 0 {
|
|
|
|
params["assignees"] = flagPullRequestAssignees
|
|
|
|
}
|
2018-02-12 02:26:50 +03:00
|
|
|
if milestoneNumber > 0 {
|
|
|
|
params["milestone"] = milestoneNumber
|
2016-09-09 00:27:32 +03:00
|
|
|
}
|
2015-08-06 00:29:51 +03:00
|
|
|
|
2016-09-09 00:27:32 +03:00
|
|
|
if len(params) > 0 {
|
2015-08-06 00:29:51 +03:00
|
|
|
err = client.UpdateIssue(baseProject, pr.Number, params)
|
2015-04-02 16:53:10 +03:00
|
|
|
utils.Check(err)
|
|
|
|
}
|
2017-04-17 18:13:23 +03:00
|
|
|
|
|
|
|
if len(flagPullRequestReviewers) > 0 {
|
2018-01-19 16:47:06 +03:00
|
|
|
userReviewers := []string{}
|
|
|
|
teamReviewers := []string{}
|
|
|
|
for _, reviewer := range flagPullRequestReviewers {
|
|
|
|
if strings.Contains(reviewer, "/") {
|
2018-08-23 14:26:24 +03:00
|
|
|
teamName := strings.SplitN(reviewer, "/", 2)[1]
|
|
|
|
if !pr.HasRequestedTeam(teamName) {
|
|
|
|
teamReviewers = append(teamReviewers, teamName)
|
|
|
|
}
|
|
|
|
} else if !pr.HasRequestedReviewer(reviewer) {
|
2018-01-19 16:47:06 +03:00
|
|
|
userReviewers = append(userReviewers, reviewer)
|
|
|
|
}
|
|
|
|
}
|
2018-08-23 14:26:24 +03:00
|
|
|
if len(userReviewers) > 0 || len(teamReviewers) > 0 {
|
|
|
|
err = client.RequestReview(baseProject, pr.Number, map[string]interface{}{
|
|
|
|
"reviewers": userReviewers,
|
|
|
|
"team_reviewers": teamReviewers,
|
|
|
|
})
|
|
|
|
utils.Check(err)
|
|
|
|
}
|
2017-04-17 18:13:23 +03:00
|
|
|
}
|
2013-07-05 22:10:24 +04:00
|
|
|
}
|
2013-07-06 00:45:22 +04:00
|
|
|
|
2016-09-11 00:49:58 +03:00
|
|
|
if flagPullRequestIssue != "" {
|
|
|
|
ui.Errorln("Warning: Issue to pull request conversion is deprecated and might not work in the future.")
|
|
|
|
}
|
|
|
|
|
2016-09-11 05:38:13 +03:00
|
|
|
args.NoForward()
|
2016-09-11 19:21:23 +03:00
|
|
|
printBrowseOrCopy(args, pullRequestURL, flagPullRequestBrowse, flagPullRequestCopy)
|
2013-07-05 22:10:24 +04:00
|
|
|
}
|
2013-06-30 20:11:25 +04:00
|
|
|
|
2013-12-08 12:33:48 +04:00
|
|
|
func parsePullRequestProject(context *github.Project, s string) (p *github.Project, ref string) {
|
|
|
|
p = context
|
|
|
|
ref = s
|
|
|
|
|
|
|
|
if strings.Contains(s, ":") {
|
|
|
|
split := strings.SplitN(s, ":", 2)
|
|
|
|
ref = split[1]
|
|
|
|
var name string
|
|
|
|
if !strings.Contains(split[0], "/") {
|
|
|
|
name = context.Name
|
|
|
|
}
|
|
|
|
p = github.NewProject(split[0], name, context.Host)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func parsePullRequestIssueNumber(url string) string {
|
|
|
|
u, e := github.ParseURL(url)
|
|
|
|
if e != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
r := regexp.MustCompile(`^issues\/(\d+)`)
|
|
|
|
p := u.ProjectPath()
|
|
|
|
if r.MatchString(p) {
|
|
|
|
return r.FindStringSubmatch(p)[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
2018-02-12 02:26:50 +03:00
|
|
|
|
|
|
|
func findMilestoneNumber(milestones []github.Milestone, name string) (int, error) {
|
|
|
|
for _, milestone := range milestones {
|
|
|
|
if strings.EqualFold(milestone.Title, name) {
|
|
|
|
return milestone.Number, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0, fmt.Errorf("error: no milestone found with name '%s'", name)
|
|
|
|
}
|