This commit is contained in:
Mislav Marohnić 2016-10-22 12:45:37 +02:00
Родитель d67ca0ee88 4a147e8c46
Коммит 95be446696
2 изменённых файлов: 186 добавлений и 17 удалений

Просмотреть файл

@ -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