зеркало из https://github.com/mislav/hub.git
Merge pull request #1294
This commit is contained in:
Коммит
95be446696
|
@ -2,9 +2,11 @@ package commands
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/github/hub/git"
|
||||
"github.com/github/hub/github"
|
||||
|
@ -45,6 +47,9 @@ pull-request -i <ISSUE>
|
|||
-c, --copy
|
||||
Put the URL of the new pull request to clipboard instead of printing it.
|
||||
|
||||
-p, --push
|
||||
Push the current branch to <HEAD> before creating the pull request.
|
||||
|
||||
-b, --base <BASE>
|
||||
The base branch in "[OWNER:]BRANCH" format. Defaults to the default branch
|
||||
(usually "master").
|
||||
|
@ -61,6 +66,11 @@ pull-request -i <ISSUE>
|
|||
-l, --labels <LABELS>
|
||||
Add a comma-separated list of labels to this pull request.
|
||||
|
||||
## Configuration:
|
||||
|
||||
HUB_RETRY_TIMEOUT=<SECONDS>
|
||||
The maximum time to keep retrying after HTTP 422 on '--push' (default: 9).
|
||||
|
||||
## See also:
|
||||
|
||||
hub(1), hub-merge(1), hub-checkout(1)
|
||||
|
@ -77,6 +87,7 @@ var (
|
|||
flagPullRequestBrowse,
|
||||
flagPullRequestCopy,
|
||||
flagPullRequestEdit,
|
||||
flagPullRequestPush,
|
||||
flagPullRequestForce bool
|
||||
|
||||
flagPullRequestMilestone uint64
|
||||
|
@ -93,6 +104,7 @@ func init() {
|
|||
cmdPullRequest.Flag.BoolVarP(&flagPullRequestCopy, "copy", "c", false, "COPY")
|
||||
cmdPullRequest.Flag.StringVarP(&flagPullRequestMessage, "message", "m", "", "MESSAGE")
|
||||
cmdPullRequest.Flag.BoolVarP(&flagPullRequestEdit, "edit", "e", false, "EDIT")
|
||||
cmdPullRequest.Flag.BoolVarP(&flagPullRequestPush, "push", "p", false, "PUSH")
|
||||
cmdPullRequest.Flag.BoolVarP(&flagPullRequestForce, "force", "f", false, "FORCE")
|
||||
cmdPullRequest.Flag.StringVarP(&flagPullRequestFile, "file", "F", "", "FILE")
|
||||
cmdPullRequest.Flag.VarP(&flagPullRequestAssignees, "assign", "a", "USERS")
|
||||
|
@ -188,27 +200,36 @@ func pullRequest(cmd *Command, args *Args) {
|
|||
var editor *github.Editor
|
||||
var title, body string
|
||||
|
||||
baseTracking := base
|
||||
headTracking := head
|
||||
|
||||
remote := gitRemoteForProject(baseProject)
|
||||
if remote != nil {
|
||||
baseTracking = fmt.Sprintf("%s/%s", remote.Name, base)
|
||||
}
|
||||
if remote == nil || !baseProject.SameAs(headProject) {
|
||||
remote = gitRemoteForProject(headProject)
|
||||
}
|
||||
if remote != nil {
|
||||
headTracking = fmt.Sprintf("%s/%s", remote.Name, head)
|
||||
}
|
||||
|
||||
if flagPullRequestPush && remote == nil {
|
||||
utils.Check(fmt.Errorf("Can't find remote for %s", head))
|
||||
}
|
||||
|
||||
if cmd.FlagPassed("message") {
|
||||
title, body = readMsg(flagPullRequestMessage)
|
||||
} else if cmd.FlagPassed("file") {
|
||||
title, body, editor, err = readMsgFromFile(flagPullRequestFile, flagPullRequestEdit, "PULLREQ", "pull request")
|
||||
utils.Check(err)
|
||||
} else if flagPullRequestIssue == "" {
|
||||
baseTracking := base
|
||||
headTracking := head
|
||||
|
||||
remote := gitRemoteForProject(baseProject)
|
||||
if remote != nil {
|
||||
baseTracking = fmt.Sprintf("%s/%s", remote.Name, base)
|
||||
}
|
||||
if remote == nil || !baseProject.SameAs(headProject) {
|
||||
remote = gitRemoteForProject(headProject)
|
||||
}
|
||||
if remote != nil {
|
||||
headTracking = fmt.Sprintf("%s/%s", remote.Name, head)
|
||||
headForMessage := headTracking
|
||||
if flagPullRequestPush {
|
||||
headForMessage = head
|
||||
}
|
||||
|
||||
message, err := createPullRequestMessage(baseTracking, headTracking, fullBase, fullHead)
|
||||
message, err := createPullRequestMessage(baseTracking, headForMessage, fullBase, fullHead)
|
||||
utils.Check(err)
|
||||
|
||||
editor, err = github.NewEditor("PULLREQ", "pull request", message)
|
||||
|
@ -222,6 +243,15 @@ func pullRequest(cmd *Command, args *Args) {
|
|||
utils.Check(fmt.Errorf("Aborting due to empty pull request title"))
|
||||
}
|
||||
|
||||
if flagPullRequestPush {
|
||||
if args.Noop {
|
||||
args.Before(fmt.Sprintf("Would push to %s/%s", remote.Name, head), "")
|
||||
} else {
|
||||
err = git.Spawn("push", remote.Name, fmt.Sprintf("HEAD:%s", head))
|
||||
utils.Check(err)
|
||||
}
|
||||
}
|
||||
|
||||
var pullRequestURL string
|
||||
if args.Noop {
|
||||
args.Before(fmt.Sprintf("Would request a pull request to %s from %s", fullBase, fullHead), "")
|
||||
|
@ -241,7 +271,40 @@ func pullRequest(cmd *Command, args *Args) {
|
|||
issueNum, _ := strconv.Atoi(flagPullRequestIssue)
|
||||
params["issue"] = issueNum
|
||||
}
|
||||
pr, err := client.CreatePullRequest(baseProject, params)
|
||||
|
||||
startedAt := time.Now()
|
||||
numRetries := 0
|
||||
retryDelay := 2
|
||||
retryAllowance := 0
|
||||
if flagPullRequestPush {
|
||||
if allowanceFromEnv := os.Getenv("HUB_RETRY_TIMEOUT"); allowanceFromEnv != "" {
|
||||
retryAllowance, err = strconv.Atoi(allowanceFromEnv)
|
||||
utils.Check(err)
|
||||
} else {
|
||||
retryAllowance = 9
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && editor != nil {
|
||||
defer editor.DeleteFile()
|
||||
|
|
|
@ -500,17 +500,29 @@ BODY
|
|||
Given I am on the "feature" branch with upstream "origin/feature"
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
tries = 0
|
||||
post('/repos/mislav/coral/pulls') {
|
||||
status 422
|
||||
json(:message => "I haz fail!")
|
||||
tries += 1
|
||||
if tries == 1
|
||||
status 422
|
||||
json :message => 'Validation Failed',
|
||||
:errors => [{
|
||||
:resource => 'PullRequest',
|
||||
:code => 'invalid',
|
||||
:field => 'head'
|
||||
}]
|
||||
else
|
||||
status 400
|
||||
end
|
||||
}
|
||||
"""
|
||||
When I run `hub pull-request -m message`
|
||||
Then the stderr should contain exactly:
|
||||
"""
|
||||
Error creating pull request: Unprocessable Entity (HTTP 422)
|
||||
I haz fail!\n
|
||||
Invalid value for "head"\n
|
||||
"""
|
||||
And the exit status should be 1
|
||||
|
||||
Scenario: Convert issue to pull request
|
||||
Given I am on the "feature" branch with upstream "origin/feature"
|
||||
|
@ -812,3 +824,97 @@ BODY
|
|||
Error creating pull request: Temporary Redirect (HTTP 307)
|
||||
Refused to follow redirect to https://disney.com/mouse\n
|
||||
"""
|
||||
|
||||
Scenario: Default message with --push
|
||||
Given the git commit editor is "true"
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
post('/repos/mislav/coral/pulls') {
|
||||
assert :title => 'The commit I never pushed',
|
||||
:body => nil
|
||||
status 201
|
||||
json :html_url => "the://url"
|
||||
}
|
||||
"""
|
||||
Given I am on the "master" branch pushed to "origin/master"
|
||||
When I successfully run `git checkout --quiet -b topic`
|
||||
Given I make a commit with message "The commit I never pushed"
|
||||
When I successfully run `hub pull-request -p`
|
||||
Then the output should contain exactly "the://url\n"
|
||||
And "git push origin HEAD:topic" should be run
|
||||
|
||||
Scenario: Text editor fails with --push
|
||||
Given the text editor exits with error status
|
||||
And I am on the "master" branch pushed to "origin/master"
|
||||
And an empty file named ".git/PULLREQ_EDITMSG"
|
||||
When I successfully run `git checkout --quiet -b topic`
|
||||
Given I make a commit
|
||||
When I run `hub pull-request -p`
|
||||
Then the stderr should contain "error using text editor for pull request message"
|
||||
And the exit status should be 1
|
||||
And the file ".git/PULLREQ_EDITMSG" should not exist
|
||||
And "git push origin HEAD:topic" should not be run
|
||||
|
||||
Scenario: Automatically retry when --push resulted in 422
|
||||
Given The default aruba timeout is 7 seconds
|
||||
And the text editor adds:
|
||||
"""
|
||||
hello!
|
||||
"""
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
first_try_at = nil
|
||||
tries = 0
|
||||
|
||||
post('/repos/mislav/coral/pulls') {
|
||||
tries += 1
|
||||
assert :title => 'hello!', :head => 'mislav:topic'
|
||||
|
||||
if !first_try_at || (Time.now - first_try_at) < 5
|
||||
first_try_at ||= Time.now
|
||||
status 422
|
||||
json :message => 'Validation Failed',
|
||||
:errors => [{
|
||||
:resource => 'PullRequest',
|
||||
:code => 'invalid',
|
||||
:field => 'head'
|
||||
}]
|
||||
else
|
||||
status 201
|
||||
json :html_url => "the://url?tries=#{tries}"
|
||||
end
|
||||
}
|
||||
"""
|
||||
Given I am on the "topic" branch
|
||||
When I successfully run `hub pull-request -p`
|
||||
Then the output should contain exactly "the://url?tries=3\n"
|
||||
And the file ".git/PULLREQ_EDITMSG" should not exist
|
||||
|
||||
Scenario: Eventually give up on retries for --push
|
||||
Given The default aruba timeout is 7 seconds
|
||||
And the text editor adds:
|
||||
"""
|
||||
hello!
|
||||
"""
|
||||
And $HUB_RETRY_TIMEOUT is "5"
|
||||
Given the GitHub API server:
|
||||
"""
|
||||
post('/repos/mislav/coral/pulls') {
|
||||
status 422
|
||||
json :message => 'Validation Failed',
|
||||
:errors => [{
|
||||
:resource => 'PullRequest',
|
||||
:code => 'invalid',
|
||||
:field => 'head'
|
||||
}]
|
||||
}
|
||||
"""
|
||||
Given I am on the "topic" branch
|
||||
When I run `hub pull-request -p`
|
||||
Then the stderr should contain:
|
||||
"""
|
||||
Error creating pull request: Unprocessable Entity (HTTP 422)
|
||||
Invalid value for "head"\n
|
||||
"""
|
||||
And the output should match /Given up after retrying for 5\.\d seconds\./
|
||||
And a file named ".git/PULLREQ_EDITMSG" should exist
|
||||
|
|
Загрузка…
Ссылка в новой задаче