提交 7bfcfa44 编写于 作者: J Jingwen Owen Ou

Merge pull request #546 from github/master-to-gh

Pull cukes from master into gh branch
Contributing to hub Contributing to hub
=================== ===================
<i>**Warning:** in the near future, hub might be implemented
[entirely in Go instead of Ruby](https://github.com/github/hub/issues/475).
Keep that in mind, and don't contribute big features/refactorings to
the Ruby codebase, as such pull requests will be unlikely to get accepted.</i>
You will need: You will need:
1. Ruby 1.8.7+ 1. Ruby 1.8.7+
......
...@@ -235,7 +235,7 @@ superpowers: ...@@ -235,7 +235,7 @@ superpowers:
### git checkout ### git checkout
$ git checkout https://github.com/defunkt/hub/pull/73 $ git checkout https://github.com/defunkt/hub/pull/73
> git remote add -f -t feature git://github:com/mislav/hub.git > git remote add -f -t feature mislav git://github.com/mislav/hub.git
> git checkout --track -B mislav-feature mislav/feature > git checkout --track -B mislav-feature mislav/feature
$ git checkout https://github.com/defunkt/hub/pull/73 custom-branch-name $ git checkout https://github.com/defunkt/hub/pull/73 custom-branch-name
......
...@@ -157,6 +157,7 @@ end ...@@ -157,6 +157,7 @@ end
desc "Publish to Homebrew" desc "Publish to Homebrew"
task :homebrew do task :homebrew do
require File.expand_path('../lib/hub/version', __FILE__) require File.expand_path('../lib/hub/version', __FILE__)
ENV['RUBYOPT'] = ''
Dir.chdir `brew --prefix`.chomp do Dir.chdir `brew --prefix`.chomp do
sh 'git checkout -q master' sh 'git checkout -q master'
sh 'git pull -q origin master' sh 'git pull -q origin master'
...@@ -172,9 +173,9 @@ task :homebrew do ...@@ -172,9 +173,9 @@ task :homebrew do
branch = "hub-v#{Hub::VERSION}" branch = "hub-v#{Hub::VERSION}"
sh "git checkout -q -B #{branch}" sh "git checkout -q -B #{branch}"
sh "git commit -m 'hub v#{Hub::VERSION}' -- #{formula_file}" sh "git commit -m 'hub #{Hub::VERSION}' -- #{formula_file}"
sh "git push -u mislav #{branch}" sh "git push -u mislav #{branch}"
sh "hub pull-request -m 'upgrade hub to v#{Hub::VERSION}'" sh "hub pull-request -m 'hub #{Hub::VERSION}'"
sh "git checkout -q master" sh "git checkout -q master"
end end
......
package commands package commands
import ( import (
"github.com/bmizerany/assert"
"os" "os"
"testing" "testing"
"github.com/bmizerany/assert"
"github.com/github/hub/fixtures"
) )
func TestParseCherryPickProjectAndSha(t *testing.T) { func TestParseCherryPickProjectAndSha(t *testing.T) {
testConfigs := fixtures.SetupTestConfigs()
defer testConfigs.TearDown()
ref := "https://github.com/jingweno/gh/commit/a319d88#comments" ref := "https://github.com/jingweno/gh/commit/a319d88#comments"
project, sha := parseCherryPickProjectAndSha(ref) project, sha := parseCherryPickProjectAndSha(ref)
assert.Equal(t, "jingweno", project.Owner) assert.Equal(t, "jingweno", project.Owner)
assert.Equal(t, "gh", project.Name) assert.Equal(t, "gh", project.Name)
assert.Equal(t, "github.com", project.Host)
assert.Equal(t, "https", project.Protocol)
assert.Equal(t, "a319d88", sha) assert.Equal(t, "a319d88", sha)
ref = "https://github.com/jingweno/gh/commit/a319d88#comments" ref = "https://github.com/jingweno/gh/commit/a319d88#comments"
...@@ -23,6 +30,9 @@ func TestParseCherryPickProjectAndSha(t *testing.T) { ...@@ -23,6 +30,9 @@ func TestParseCherryPickProjectAndSha(t *testing.T) {
} }
func TestTransformCherryPickArgs(t *testing.T) { func TestTransformCherryPickArgs(t *testing.T) {
testConfigs := fixtures.SetupTestConfigs()
defer testConfigs.TearDown()
os.Setenv("HUB_PROTOCOL", "git") os.Setenv("HUB_PROTOCOL", "git")
args := NewArgs([]string{"cherry-pick", "https://github.com/jingweno/gh/commit/a319d88#comments"}) args := NewArgs([]string{"cherry-pick", "https://github.com/jingweno/gh/commit/a319d88#comments"})
transformCherryPickArgs(args) transformCherryPickArgs(args)
......
...@@ -3,8 +3,9 @@ package commands ...@@ -3,8 +3,9 @@ package commands
import ( import (
"bytes" "bytes"
"fmt" "fmt"
flag "github.com/ogier/pflag"
"strings" "strings"
flag "github.com/ogier/pflag"
) )
var ( var (
...@@ -49,11 +50,8 @@ func (c *Command) Call(args *Args) (err error) { ...@@ -49,11 +50,8 @@ func (c *Command) Call(args *Args) (err error) {
func (c *Command) parseArguments(args *Args) (err error) { func (c *Command) parseArguments(args *Args) (err error) {
c.Flag.SetInterspersed(true) c.Flag.SetInterspersed(true)
c.Flag.Init(c.Name(), flag.ContinueOnError)
if !c.GitExtension { c.Flag.Usage = c.PrintUsage
c.Flag.Usage = c.PrintUsage
}
if err = c.Flag.Parse(args.Params); err == nil { if err = c.Flag.Parse(args.Params); err == nil {
args.Params = c.Flag.Args() args.Params = c.Flag.Args()
} }
......
...@@ -64,7 +64,7 @@ func transformMergeArgs(args *Args) error { ...@@ -64,7 +64,7 @@ func transformMergeArgs(args *Args) error {
return fmt.Errorf("Error: %s's fork is not available anymore", user) return fmt.Errorf("Error: %s's fork is not available anymore", user)
} }
u := url.GitURL("", user, pullRequest.Head.Repo.Private) u := url.GitURL(pullRequest.Head.Repo.Name, user, pullRequest.Head.Repo.Private)
mergeHead := fmt.Sprintf("%s/%s", user, branch) mergeHead := fmt.Sprintf("%s/%s", user, branch)
ref := fmt.Sprintf("+refs/heads/%s:refs/remotes/%s", branch, mergeHead) ref := fmt.Sprintf("+refs/heads/%s:refs/remotes/%s", branch, mergeHead)
args.Before("git", "fetch", u, ref) args.Before("git", "fetch", u, ref)
......
...@@ -14,7 +14,7 @@ import ( ...@@ -14,7 +14,7 @@ import (
var cmdPullRequest = &Command{ var cmdPullRequest = &Command{
Run: pullRequest, Run: pullRequest,
Usage: "pull-request [-f] [-m <MESSAGE>|-F <FILE>|-i <ISSUE>|<ISSUE-URL>] [-b <BASE>] [-h <HEAD>] ", Usage: "pull-request [-f] [-m <MESSAGE>|-F <FILE>|-i <ISSUE>|<ISSUE-URL>] [-o] [-b <BASE>] [-h <HEAD>] ",
Short: "Open a pull request on GitHub", Short: "Open a pull request on GitHub",
Long: `Opens a pull request on GitHub for the project that the "origin" remote Long: `Opens a pull request on GitHub for the project that the "origin" remote
points to. The default head of the pull request is the current branch. points to. The default head of the pull request is the current branch.
...@@ -40,6 +40,7 @@ var ( ...@@ -40,6 +40,7 @@ var (
flagPullRequestIssue, flagPullRequestIssue,
flagPullRequestMessage, flagPullRequestMessage,
flagPullRequestFile string flagPullRequestFile string
flagPullRequestBrowse,
flagPullRequestForce bool flagPullRequestForce bool
) )
...@@ -47,6 +48,7 @@ func init() { ...@@ -47,6 +48,7 @@ func init() {
cmdPullRequest.Flag.StringVarP(&flagPullRequestBase, "base", "b", "", "BASE") cmdPullRequest.Flag.StringVarP(&flagPullRequestBase, "base", "b", "", "BASE")
cmdPullRequest.Flag.StringVarP(&flagPullRequestHead, "head", "h", "", "HEAD") cmdPullRequest.Flag.StringVarP(&flagPullRequestHead, "head", "h", "", "HEAD")
cmdPullRequest.Flag.StringVarP(&flagPullRequestIssue, "issue", "i", "", "ISSUE") cmdPullRequest.Flag.StringVarP(&flagPullRequestIssue, "issue", "i", "", "ISSUE")
cmdPullRequest.Flag.BoolVarP(&flagPullRequestBrowse, "browse", "o", false, "BROWSE")
cmdPullRequest.Flag.StringVarP(&flagPullRequestMessage, "message", "m", "", "MESSAGE") cmdPullRequest.Flag.StringVarP(&flagPullRequestMessage, "message", "m", "", "MESSAGE")
cmdPullRequest.Flag.BoolVarP(&flagPullRequestForce, "force", "f", false, "FORCE") cmdPullRequest.Flag.BoolVarP(&flagPullRequestForce, "force", "f", false, "FORCE")
cmdPullRequest.Flag.StringVarP(&flagPullRequestFile, "file", "F", "", "FILE") cmdPullRequest.Flag.StringVarP(&flagPullRequestFile, "file", "F", "", "FILE")
...@@ -197,7 +199,15 @@ func pullRequest(cmd *Command, args *Args) { ...@@ -197,7 +199,15 @@ func pullRequest(cmd *Command, args *Args) {
pullRequestURL = pr.HTMLURL pullRequestURL = pr.HTMLURL
} }
args.Replace("echo", "", pullRequestURL) if flagPullRequestBrowse {
launcher, err := utils.BrowserLauncher()
utils.Check(err)
args.Replace(launcher[0], "", launcher[1:]...)
args.AppendParams(pullRequestURL)
} else {
args.Replace("echo", "", pullRequestURL)
}
if flagPullRequestIssue != "" { if flagPullRequestIssue != "" {
args.After("echo", "Warning: Issue to pull request conversion is deprecated and might not work in the future.") args.After("echo", "Warning: Issue to pull request conversion is deprecated and might not work in the future.")
} }
......
...@@ -40,8 +40,8 @@ Feature: OAuth authentication ...@@ -40,8 +40,8 @@ Feature: OAuth authentication
auth = Rack::Auth::Basic::Request.new(env) auth = Rack::Auth::Basic::Request.new(env)
halt 401 unless auth.credentials == %w[mislav kitty] halt 401 unless auth.credentials == %w[mislav kitty]
json [ json [
{:token => 'SKIPPD', :app => {:url => 'http://example.com'}}, {:token => 'SKIPPD', :note_url => 'http://example.com'},
{:token => 'OTOKEN', :app => {:url => 'http://hub.github.com/'}} {:token => 'OTOKEN', :note_url => 'http://hub.github.com/'}
] ]
} }
get('/user') { get('/user') {
...@@ -58,6 +58,36 @@ Feature: OAuth authentication ...@@ -58,6 +58,36 @@ Feature: OAuth authentication
And the exit status should be 0 And the exit status should be 0
And the file "../home/.config/hub" should contain 'access_token = "OTOKEN"' And the file "../home/.config/hub" should contain 'access_token = "OTOKEN"'
Scenario: Re-use existing authorization with an old URL
Given the GitHub API server:
"""
require 'rack/auth/basic'
get('/authorizations') {
auth = Rack::Auth::Basic::Request.new(env)
halt 401 unless auth.credentials == %w[mislav kitty]
json [
{:token => 'OTOKEN', :note => 'hub', :note_url => 'http://defunkt.io/hub/'}
]
}
post('/authorizations') {
status 422
json :message => "Validation Failed",
:errors => [{:resource => "OauthAccess", :code => "already_exists", :field => "description"}]
}
get('/user') {
json :login => 'mislav'
}
post('/user/repos') {
json :full_name => 'mislav/dotfiles'
}
"""
When I run `hub create` interactively
When I type "mislav"
And I type "kitty"
Then the output should contain "github.com password for mislav (never stored):"
And the exit status should be 0
And the file "../home/.config/hub" should contain 'access_token = "OTOKEN"'
Scenario: Credentials from GITHUB_USER & GITHUB_PASSWORD Scenario: Credentials from GITHUB_USER & GITHUB_PASSWORD
Given the GitHub API server: Given the GitHub API server:
""" """
...@@ -66,7 +96,7 @@ Feature: OAuth authentication ...@@ -66,7 +96,7 @@ Feature: OAuth authentication
auth = Rack::Auth::Basic::Request.new(env) auth = Rack::Auth::Basic::Request.new(env)
halt 401 unless auth.credentials == %w[mislav kitty] halt 401 unless auth.credentials == %w[mislav kitty]
json [ json [
{:token => 'OTOKEN', :app => {:url => 'http://hub.github.com/'}} {:token => 'OTOKEN', :note_url => 'http://hub.github.com/'}
] ]
} }
get('/user') { get('/user') {
...@@ -154,7 +184,7 @@ Feature: OAuth authentication ...@@ -154,7 +184,7 @@ Feature: OAuth authentication
end end
json [ { json [ {
:token => token, :token => token,
:app => {:url => 'http://hub.github.com/'} :note_url => 'http://hub.github.com/'
} ] } ]
} }
get('/user') { get('/user') {
......
...@@ -134,3 +134,17 @@ Feature: hub browse ...@@ -134,3 +134,17 @@ Feature: hub browse
And the "upstream" remote has url "../path/to/another/repo.git" And the "upstream" remote has url "../path/to/another/repo.git"
When I successfully run `hub browse` When I successfully run `hub browse`
Then "open https://github.com/mislav/dotfiles" should be run Then "open https://github.com/mislav/dotfiles" should be run
Scenario: Enterprise repo
Given I am in "git://git.my.org/mislav/dotfiles.git" git repo
And I am "mislav" on git.my.org with OAuth token "FITOKEN"
And "git.my.org" is a whitelisted Enterprise host
When I successfully run `hub browse`
Then "open https://git.my.org/mislav/dotfiles" should be run
Scenario: Enterprise repo over HTTP
Given I am in "git://git.my.org/mislav/dotfiles.git" git repo
And I am "mislav" on http://git.my.org with OAuth token "FITOKEN"
And "git.my.org" is a whitelisted Enterprise host
When I successfully run `hub browse`
Then "open http://git.my.org/mislav/dotfiles" should be run
...@@ -81,3 +81,11 @@ Feature: hub compare ...@@ -81,3 +81,11 @@ Feature: hub compare
When I successfully run `hub compare anotheruser feature` When I successfully run `hub compare anotheruser feature`
Then there should be no output Then there should be no output
And "open https://github.com/anotheruser/dotfiles/compare/feature" should be run And "open https://github.com/anotheruser/dotfiles/compare/feature" should be run
Scenario: Enterprise repo over HTTP
Given the "origin" remote has url "git://git.my.org/mislav/dotfiles.git"
And I am "mislav" on http://git.my.org with OAuth token "FITOKEN"
And "git.my.org" is a whitelisted Enterprise host
When I successfully run `hub compare refactor`
Then there should be no output
And "open http://git.my.org/mislav/dotfiles/compare/refactor" should be run
...@@ -129,3 +129,32 @@ Feature: hub create ...@@ -129,3 +129,32 @@ Feature: hub create
""" """
When I successfully run `hub create` When I successfully run `hub create`
Then the url for "origin" should be "git@github.com:mislav/my-dot-files.git" Then the url for "origin" should be "git@github.com:mislav/my-dot-files.git"
Scenario: Verbose API output
Given the GitHub API server:
"""
get('/repos/mislav/dotfiles') { status 404 }
post('/user/repos') {
response['location'] = 'http://disney.com'
json :full_name => 'mislav/dotfiles'
}
"""
And $HUB_VERBOSE is "on"
When I successfully run `hub create`
Then the stderr should contain:
"""
> GET https://api.github.com/repos/mislav/dotfiles
> Authorization: token [REDACTED]
< HTTP 404
"""
And the stderr should contain:
"""
> POST https://api.github.com/user/repos
> Authorization: token [REDACTED]
"""
And the stderr should contain:
"""
< HTTP 200
< Location: http://disney.com
{"full_name":"mislav/dotfiles"}\n
"""
...@@ -7,7 +7,10 @@ Feature: hub fork ...@@ -7,7 +7,10 @@ Feature: hub fork
Scenario: Fork the repository Scenario: Fork the repository
Given the GitHub API server: Given the GitHub API server:
""" """
before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN' } before {
halt 400 unless request.env['HTTP_X_ORIGINAL_SCHEME'] == 'https'
halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token OTOKEN'
}
get('/repos/mislav/dotfiles', :host_name => 'api.github.com') { 404 } get('/repos/mislav/dotfiles', :host_name => 'api.github.com') { 404 }
post('/repos/evilchelu/dotfiles/forks', :host_name => 'api.github.com') { '' } post('/repos/evilchelu/dotfiles/forks', :host_name => 'api.github.com') { '' }
""" """
...@@ -121,7 +124,10 @@ Scenario: Related fork already exists ...@@ -121,7 +124,10 @@ Scenario: Related fork already exists
Scenario: Enterprise fork Scenario: Enterprise fork
Given the GitHub API server: Given the GitHub API server:
""" """
before { halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN' } before {
halt 400 unless request.env['HTTP_X_ORIGINAL_SCHEME'] == 'https'
halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN'
}
post('/api/v3/repos/evilchelu/dotfiles/forks', :host_name => 'git.my.org') { '' } post('/api/v3/repos/evilchelu/dotfiles/forks', :host_name => 'git.my.org') { '' }
""" """
And the "origin" remote has url "git@git.my.org:evilchelu/dotfiles.git" And the "origin" remote has url "git@git.my.org:evilchelu/dotfiles.git"
...@@ -129,3 +135,18 @@ Scenario: Related fork already exists ...@@ -129,3 +135,18 @@ Scenario: Related fork already exists
And "git.my.org" is a whitelisted Enterprise host And "git.my.org" is a whitelisted Enterprise host
When I successfully run `hub fork` When I successfully run `hub fork`
Then the url for "mislav" should be "git@git.my.org:mislav/dotfiles.git" Then the url for "mislav" should be "git@git.my.org:mislav/dotfiles.git"
Scenario: Enterprise fork using regular HTTP
Given the GitHub API server:
"""
before {
halt 400 unless request.env['HTTP_X_ORIGINAL_SCHEME'] == 'http'
halt 401 unless request.env['HTTP_AUTHORIZATION'] == 'token FITOKEN'
}
post('/api/v3/repos/evilchelu/dotfiles/forks', :host_name => 'git.my.org') { '' }
"""
And the "origin" remote has url "git@git.my.org:evilchelu/dotfiles.git"
And I am "mislav" on http://git.my.org with OAuth token "FITOKEN"
And "git.my.org" is a whitelisted Enterprise host
When I successfully run `hub fork`
Then the url for "mislav" should be "git@git.my.org:mislav/dotfiles.git"
...@@ -11,7 +11,7 @@ Feature: hub merge ...@@ -11,7 +11,7 @@ Feature: hub merge
get('/repos/defunkt/hub/pulls/164') { json \ get('/repos/defunkt/hub/pulls/164') { json \
:head => { :head => {
:label => 'jfirebaugh:hub_merge', :label => 'jfirebaugh:hub_merge',
:repo => {:private => false} :repo => {:private => false, :name=>"hub"}
}, },
:title => "Add `hub merge` command" :title => "Add `hub merge` command"
} }
...@@ -34,7 +34,7 @@ Feature: hub merge ...@@ -34,7 +34,7 @@ Feature: hub merge
get('/repos/defunkt/hub/pulls/164') { json \ get('/repos/defunkt/hub/pulls/164') { json \
:head => { :head => {
:label => 'jfirebaugh:hub_merge', :label => 'jfirebaugh:hub_merge',
:repo => {:private => false} :repo => {:private => false, :name=>"hub"}
}, },
:title => "Add `hub merge` command" :title => "Add `hub merge` command"
} }
...@@ -55,7 +55,7 @@ Feature: hub merge ...@@ -55,7 +55,7 @@ Feature: hub merge
get('/repos/defunkt/hub/pulls/164') { json \ get('/repos/defunkt/hub/pulls/164') { json \
:head => { :head => {
:label => 'jfirebaugh:hub_merge', :label => 'jfirebaugh:hub_merge',
:repo => {:private => true} :repo => {:private => true, :name=>"hub"}
}, },
:title => "Add `hub merge` command" :title => "Add `hub merge` command"
} }
...@@ -82,6 +82,21 @@ Feature: hub merge ...@@ -82,6 +82,21 @@ Feature: hub merge
Error: jfirebaugh's fork is not available anymore\n Error: jfirebaugh's fork is not available anymore\n
""" """
Scenario: Renamed repo
Given the GitHub API server:
"""
require 'json'
get('/repos/defunkt/hub/pulls/164') { json \
:head => {
:label => 'jfirebaugh:hub_merge',
:repo => {:private => false, :name=>"hub-1"}
}
}
"""
And there is a commit named "jfirebaugh/hub_merge"
When I successfully run `hub merge https://github.com/defunkt/hub/pull/164`
Then "git fetch git://github.com/jfirebaugh/hub-1.git +refs/heads/hub_merge:refs/remotes/jfirebaugh/hub_merge" should be run
Scenario: Unchanged merge Scenario: Unchanged merge
When I run `hub merge master` When I run `hub merge master`
Then "git merge master" should be run Then "git merge master" should be run
...@@ -43,6 +43,11 @@ Feature: hub pull-request ...@@ -43,6 +43,11 @@ Feature: hub pull-request
When I successfully run `hub pull-request -m ăéñøü` When I successfully run `hub pull-request -m ăéñøü`
Then the output should contain exactly "the://url\n" Then the output should contain exactly "the://url\n"
Scenario: Invalid flag
When I run `hub pull-request -yelp`
Then the stderr should contain "unknown shorthand flag: 'y' in -yelp\n"
And the exit status should be 1
Scenario: Non-existing base Scenario: Non-existing base
Given the GitHub API server: Given the GitHub API server:
""" """
...@@ -415,3 +420,13 @@ Feature: hub pull-request ...@@ -415,3 +420,13 @@ Feature: hub pull-request
""" """
When I successfully run `hub pull-request -m hereyougo` When I successfully run `hub pull-request -m hereyougo`
Then the output should contain exactly "the://url\n" Then the output should contain exactly "the://url\n"
Scenario: Open pull request in web browser
Given the GitHub API server:
"""
post('/repos/mislav/coral/pulls') {
json :html_url => "the://url"
}
"""
When I successfully run `hub pull-request -o -m hereyougo`
Then "open the://url" should be run
...@@ -25,9 +25,12 @@ Given(/^the "([^"]*)" remote has url "([^"]*)"$/) do |remote_name, url| ...@@ -25,9 +25,12 @@ Given(/^the "([^"]*)" remote has url "([^"]*)"$/) do |remote_name, url|
end end
end end
Given(/^I am "([^"]*)" on ([\w.-]+)(?: with OAuth token "([^"]*)")?$/) do |name, host, token| Given(/^I am "([^"]*)" on ([\S]+)(?: with OAuth token "([^"]*)")?$/) do |name, host, token|
edit_hub_config do |cfg| edit_hub_config do |cfg|
entry = { 'user' => name, 'host' => host } entry = {'user' => name}
host = host.sub(%r{^([\w-]+)://}, '')
entry['host'] = host
entry['protocol'] = $1 if $1
entry['access_token'] = token if token entry['access_token'] = token if token
cfg << entry cfg << entry
end end
......
package fixtures
import (
"io/ioutil"
"os"
)
type TestConfigs struct {
Path string
}
func (c *TestConfigs) TearDown() {
os.Setenv("GH_CONFIG", "")
os.RemoveAll(c.Path)
}
func SetupTestConfigs() *TestConfigs {
file, _ := ioutil.TempFile("", "test-gh-config-")
content := `[[hosts]]
host = "github.com"
user = "jingweno"
access_token = "123"
protocol = "http"`
ioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)
os.Setenv("GH_CONFIG", file.Name())
return &TestConfigs{file.Name()}
}
...@@ -2,7 +2,6 @@ package github ...@@ -2,7 +2,6 @@ package github
import ( import (
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"os" "os"
"strings" "strings"
...@@ -400,7 +399,7 @@ func (client *Client) FindOrCreateToken(user, password, twoFactorCode string) (t ...@@ -400,7 +399,7 @@ func (client *Client) FindOrCreateToken(user, password, twoFactorCode string) (t
} }
for _, auth := range auths { for _, auth := range auths {
if auth.App.URL == OAuthAppURL { if auth.Note == OAuthAppName || auth.NoteURL == OAuthAppURL {
token = auth.Token token = auth.Token
break break
} }
...@@ -424,27 +423,6 @@ func (client *Client) FindOrCreateToken(user, password, twoFactorCode string) (t ...@@ -424,27 +423,6 @@ func (client *Client) FindOrCreateToken(user, password, twoFactorCode string) (t
return return
} }
// An implementation of http.ProxyFromEnvironment that isn't broken
func proxyFromEnvironment(req *http.Request) (*url.URL, error) {
proxy := os.Getenv("http_proxy")
if proxy == "" {
proxy = os.Getenv("HTTP_PROXY")
}
if proxy == "" {
return nil, nil
}
proxyURL, err := url.Parse(proxy)
if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
if proxyURL, err := url.Parse("http://" + proxy); err == nil {
return proxyURL, nil
}
}
if err != nil {
return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
}
return proxyURL, nil
}
func (client *Client) api() (c *octokit.Client, err error) { func (client *Client) api() (c *octokit.Client, err error) {
if client.Host.AccessToken == "" { if client.Host.AccessToken == "" {
host, e := CurrentConfigs().PromptForHost(client.Host.Host) host, e := CurrentConfigs().PromptForHost(client.Host.Host)
...@@ -466,14 +444,7 @@ func (client *Client) newOctokitClient(auth octokit.AuthMethod) *octokit.Client ...@@ -466,14 +444,7 @@ func (client *Client) newOctokitClient(auth octokit.AuthMethod) *octokit.Client
if client.Host != nil { if client.Host != nil {
host = client.Host.Host host = client.Host.Host
} }
host = normalizeHost(host)
if host == "" {
host = GitHubHost
}
if host == GitHubHost {
host = GitHubApiHost
}
apiHost := host apiHost := host
hubTestHost := os.Getenv("HUB_TEST_HOST") hubTestHost := os.Getenv("HUB_TEST_HOST")
...@@ -481,19 +452,32 @@ func (client *Client) newOctokitClient(auth octokit.AuthMethod) *octokit.Client ...@@ -481,19 +452,32 @@ func (client *Client) newOctokitClient(auth octokit.AuthMethod) *octokit.Client
apiHost = hubTestHost apiHost = hubTestHost
} }
apiHost = absolute(apiHost) hostURL := client.absolute(host)
apiHostURL := client.absolute(apiHost)
tr := &http.Transport{Proxy: proxyFromEnvironment} httpClient := newHttpClient(os.Getenv("HUB_VERBOSE") != "")
httpClient := &http.Client{Transport: tr} c := octokit.NewClientWith(apiHostURL.String(), UserAgent, auth, httpClient)
c := octokit.NewClientWith(apiHost, UserAgent, auth, httpClient)
if hubTestHost != "" { if hubTestHost != "" {
// if it's in test, make sure host name is in the header // if it's in test, make sure host name is in the header
c.Header.Set("Host", host) c.Header.Set("Host", host)
c.Header.Set("X-Original-Scheme", hostURL.Scheme)
} }
return c return c
} }
func (client *Client) absolute(endpoint string) *url.URL {
u, _ := url.Parse(endpoint)
if u.Scheme == "" && client.Host != nil {
u.Scheme = client.Host.Protocol
}
if u.Scheme == "" {
u.Scheme = "https"
}
return u
}
func (client *Client) requestURL(u *url.URL) (uu *url.URL) { func (client *Client) requestURL(u *url.URL) (uu *url.URL) {
uu = u uu = u
if client.Host != nil && client.Host.Host != GitHubHost { if client.Host != nil && client.Host.Host != GitHubHost {
...@@ -503,6 +487,19 @@ func (client *Client) requestURL(u *url.URL) (uu *url.URL) { ...@@ -503,6 +487,19 @@ func (client *Client) requestURL(u *url.URL) (uu *url.URL) {
return return
} }
func normalizeHost(host string) string {
host = strings.ToLower(host)
if host == "" {
host = GitHubHost
}
if host == GitHubHost {
host = GitHubApiHost
}
return host
}
func FormatError(action string, err error) (ee error) { func FormatError(action string, err error) (ee error) {
switch e := err.(type) { switch e := err.(type) {
default: default:
...@@ -555,12 +552,3 @@ func warnExistenceOfRepo(project *Project, ee error) (err error) { ...@@ -555,12 +552,3 @@ func warnExistenceOfRepo(project *Project, ee error) (err error) {
return return
} }
func absolute(endpoint string) string {
u, _ := url.Parse(endpoint)
if u.Scheme == "" {
u.Scheme = "https"
}
return u.String()
}
...@@ -21,6 +21,7 @@ type Host struct { ...@@ -21,6 +21,7 @@ type Host struct {
Host string `toml:"host"` Host string `toml:"host"`
User string `toml:"user"` User string `toml:"user"`
AccessToken string `toml:"access_token"` AccessToken string `toml:"access_token"`
Protocol string `toml:"protocol"`
} }
type Configs struct { type Configs struct {
...@@ -28,7 +29,7 @@ type Configs struct { ...@@ -28,7 +29,7 @@ type Configs struct {
} }
func (c *Configs) PromptForHost(host string) (h *Host, err error) { func (c *Configs) PromptForHost(host string) (h *Host, err error) {
h = c.find(host) h = c.Find(host)
if h != nil { if h != nil {
return return
} }
...@@ -53,12 +54,16 @@ func (c *Configs) PromptForHost(host string) (h *Host, err error) { ...@@ -53,12 +54,16 @@ func (c *Configs) PromptForHost(host string) (h *Host, err error) {
client.Host.AccessToken = token client.Host.AccessToken = token
currentUser, err := client.CurrentUser() currentUser, err := client.CurrentUser()
if err != nil { if err != nil {
return return
} }
h = &Host{Host: host, User: currentUser.Login, AccessToken: token} h = &Host{
Host: host,
User: currentUser.Login,
AccessToken: token,
Protocol: "https",
}
c.Hosts = append(c.Hosts, *h) c.Hosts = append(c.Hosts, *h)
err = saveTo(configsFile(), c) err = saveTo(configsFile(), c)
...@@ -109,7 +114,7 @@ func (c *Configs) scanLine() string { ...@@ -109,7 +114,7 @@ func (c *Configs) scanLine() string {
return line return line
} }
func (c *Configs) find(host string) *Host { func (c *Configs) Find(host string) *Host {
for _, h := range c.Hosts { for _, h := range c.Hosts {
if h.Host == host { if h.Host == host {
return &h return &h
......
...@@ -6,34 +6,35 @@ import ( ...@@ -6,34 +6,35 @@ import (
"testing" "testing"
"github.com/bmizerany/assert" "github.com/bmizerany/assert"
"github.com/github/hub/fixtures"
) )
func TestConfigs_loadFrom(t *testing.T) { func TestConfigs_loadFrom(t *testing.T) {
file, _ := ioutil.TempFile("", "test-gh-config-") testConfigs := fixtures.SetupTestConfigs()
defer os.RemoveAll(file.Name()) defer testConfigs.TearDown()
content := `[[hosts]]
host = "https://github.com"
user = "jingweno"
access_token = "123"`
ioutil.WriteFile(file.Name(), []byte(content), os.ModePerm)
cc := &Configs{} cc := &Configs{}
err := loadFrom(file.Name(), cc) err := loadFrom(testConfigs.Path, cc)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, 1, len(cc.Hosts)) assert.Equal(t, 1, len(cc.Hosts))
host := cc.Hosts[0] host := cc.Hosts[0]
assert.Equal(t, "https://github.com", host.Host) assert.Equal(t, "github.com", host.Host)
assert.Equal(t, "jingweno", host.User) assert.Equal(t, "jingweno", host.User)
assert.Equal(t, "123", host.AccessToken) assert.Equal(t, "123", host.AccessToken)
assert.Equal(t, "http", host.Protocol)
} }
func TestConfigs_saveTo(t *testing.T) { func TestConfigs_saveTo(t *testing.T) {
file, _ := ioutil.TempFile("", "test-gh-config-") file, _ := ioutil.TempFile("", "test-gh-config-")
defer os.RemoveAll(file.Name()) defer os.RemoveAll(file.Name())
host := Host{Host: "https://github.com", User: "jingweno", AccessToken: "123"} host := Host{
Host: "github.com",
User: "jingweno",
AccessToken: "123",
Protocol: "https",
}
c := Configs{Hosts: []Host{host}} c := Configs{Hosts: []Host{host}}
err := saveTo(file.Name(), &c) err := saveTo(file.Name(), &c)
...@@ -41,8 +42,9 @@ func TestConfigs_saveTo(t *testing.T) { ...@@ -41,8 +42,9 @@ func TestConfigs_saveTo(t *testing.T) {
b, _ := ioutil.ReadFile(file.Name()) b, _ := ioutil.ReadFile(file.Name())
content := `[[hosts]] content := `[[hosts]]
host = "https://github.com" host = "github.com"
user = "jingweno" user = "jingweno"
access_token = "123"` access_token = "123"
protocol = "https"`
assert.Equal(t, content, string(b)) assert.Equal(t, content, string(b))
} }
package github
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"github.com/github/hub/utils"
)
type verboseTransport struct {
Transport *http.Transport
Verbose bool
}
func (t *verboseTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
if t.Verbose {
t.dumpRequest(req)
}
resp, err = t.Transport.RoundTrip(req)
if err == nil && t.Verbose {
t.dumpResponse(resp)
}
return
}
func (t *verboseTransport) dumpRequest(req *http.Request) {
info := fmt.Sprintf("> %s %s://%s%s", req.Method, req.Header.Get("X-Original-Scheme"), req.Host, req.URL.Path)
t.verbosePrintln(info)
t.dumpHeaders(req.Header, ">")
body := t.dumpBody(req.Body)
if body != nil {
// reset body since it's been read
req.Body = body
}
}
func (t *verboseTransport) dumpResponse(resp *http.Response) {
info := fmt.Sprintf("< HTTP %d", resp.StatusCode)
location, err := resp.Location()
if err == nil {
info = fmt.Sprintf("%s\n< Location: %s", info, location.String())
}
t.verbosePrintln(info)
t.dumpHeaders(resp.Header, "<")
body := t.dumpBody(resp.Body)
if body != nil {
// reset body since it's been read
resp.Body = body
}
}
func (t *verboseTransport) dumpHeaders(header http.Header, indent string) {
dumpHeaders := []string{"Authorization", "X-GitHub-OTP", "Localtion"}
for _, h := range dumpHeaders {
v := header.Get(h)
if v != "" {
r := regexp.MustCompile("(?i)^(basic|token) (.+)")
if r.MatchString(v) {
v = r.ReplaceAllString(v, "$1 [REDACTED]")
}
info := fmt.Sprintf("%s %s: %s", indent, h, v)
t.verbosePrintln(info)
}
}
}
func (t *verboseTransport) dumpBody(body io.ReadCloser) io.ReadCloser {
if body == nil {
return nil
}
defer body.Close()
buf := new(bytes.Buffer)
_, err := io.Copy(buf, body)
utils.Check(err)
if buf.Len() > 0 {
t.verbosePrintln(buf.String())
}
return ioutil.NopCloser(buf)
}
func (t *verboseTransport) verbosePrintln(msg string) {
if isTerminal(os.Stderr.Fd()) {
msg = fmt.Sprintf("\\e[36m%s\\e[m", msg)
}
fmt.Fprintln(os.Stderr, msg)
}
func newHttpClient(verbose bool) *http.Client {
tr := &verboseTransport{
Transport: &http.Transport{Proxy: proxyFromEnvironment},
Verbose: verbose,
}
return &http.Client{Transport: tr}
}
// An implementation of http.ProxyFromEnvironment that isn't broken
func proxyFromEnvironment(req *http.Request) (*url.URL, error) {
proxy := os.Getenv("http_proxy")
if proxy == "" {
proxy = os.Getenv("HTTP_PROXY")
}
if proxy == "" {
return nil, nil
}
proxyURL, err := url.Parse(proxy)
if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
if proxyURL, err := url.Parse("http://" + proxy); err == nil {
return proxyURL, nil
}
}
if err != nil {
return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
}
return proxyURL, nil
}
...@@ -13,9 +13,10 @@ import ( ...@@ -13,9 +13,10 @@ import (
) )
type Project struct { type Project struct {
Name string Name string
Owner string Owner string
Host string Host string
Protocol string
} }
func (p Project) String() string { func (p Project) String() string {
...@@ -48,7 +49,7 @@ func (p *Project) WebURL(name, owner, path string) string { ...@@ -48,7 +49,7 @@ func (p *Project) WebURL(name, owner, path string) string {
} }
} }
url := fmt.Sprintf("https://%s", utils.ConcatPaths(p.Host, ownerWithName)) url := fmt.Sprintf("%s://%s", p.Protocol, utils.ConcatPaths(p.Host, ownerWithName))
if path != "" { if path != "" {
url = utils.ConcatPaths(url, path) url = utils.ConcatPaths(url, path)
} }
...@@ -111,12 +112,16 @@ func NewProjectFromURL(url *url.URL) (p *Project, err error) { ...@@ -111,12 +112,16 @@ func NewProjectFromURL(url *url.URL) (p *Project, err error) {
} }
name := strings.TrimSuffix(parts[2], ".git") name := strings.TrimSuffix(parts[2], ".git")
p = NewProject(parts[1], name, url.Host) p = newProject(parts[1], name, url.Host, url.Scheme)
return return
} }
func NewProject(owner, name, host string) *Project { func NewProject(owner, name, host string) *Project {
return newProject(owner, name, host, "")
}
func newProject(owner, name, host, protocol string) *Project {
if strings.Contains(owner, "/") { if strings.Contains(owner, "/") {
result := strings.SplitN(owner, "/", 2) result := strings.SplitN(owner, "/", 2)
owner = result[0] owner = result[0]
...@@ -135,17 +140,36 @@ func NewProject(owner, name, host string) *Project { ...@@ -135,17 +140,36 @@ func NewProject(owner, name, host string) *Project {
host = DefaultGitHubHost() host = DefaultGitHubHost()
} }
if protocol != "http" && protocol != "https" {
protocol = ""
}
if protocol == "" {
h := CurrentConfigs().Find(host)
if h != nil {
protocol = h.Protocol
}
}
if protocol == "" {
protocol = "https"
}
if owner == "" { if owner == "" {
h, e := CurrentConfigs().PromptForHost(host) h := CurrentConfigs().Find(host)
utils.Check(e) if h != nil {
owner = h.User owner = h.User
}
} }
if name == "" { if name == "" {
name, _ = utils.DirName() name, _ = utils.DirName()
} }
return &Project{Name: name, Owner: owner, Host: host} return &Project{
Name: name,
Owner: owner,
Host: host,
Protocol: protocol,
}
} }
func parseOwnerAndName(remote string) (owner string, name string) { func parseOwnerAndName(remote string) (owner string, name string) {
......
package github package github
import ( import (
"github.com/bmizerany/assert"
"net/url" "net/url"
"os" "os"
"testing" "testing"
"github.com/bmizerany/assert"
"github.com/github/hub/fixtures"
) )
func TestWebURL(t *testing.T) { func TestWebURL(t *testing.T) {
project := Project{Name: "foo", Owner: "bar", Host: "github.com"} project := Project{Name: "foo", Owner: "bar", Host: "github.com", Protocol: "https"}
url := project.WebURL("", "", "baz") url := project.WebURL("", "", "baz")
assert.Equal(t, "https://github.com/bar/foo/baz", url) assert.Equal(t, "https://github.com/bar/foo/baz", url)
...@@ -99,12 +101,17 @@ func TestMustMatchGitHubURL(t *testing.T) { ...@@ -99,12 +101,17 @@ func TestMustMatchGitHubURL(t *testing.T) {
} }
func TestNewProjectFromURL(t *testing.T) { func TestNewProjectFromURL(t *testing.T) {
testConfigs := fixtures.SetupTestConfigs()
defer testConfigs.TearDown()
u, _ := url.Parse("ssh://git@github.com/octokit/go-octokit.git") u, _ := url.Parse("ssh://git@github.com/octokit/go-octokit.git")
p, err := NewProjectFromURL(u) p, err := NewProjectFromURL(u)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "go-octokit", p.Name) assert.Equal(t, "go-octokit", p.Name)
assert.Equal(t, "octokit", p.Owner) assert.Equal(t, "octokit", p.Owner)
assert.Equal(t, "github.com", p.Host)
assert.Equal(t, "http", p.Protocol)
u, _ = url.Parse("git://github.com/octokit/go-octokit.git") u, _ = url.Parse("git://github.com/octokit/go-octokit.git")
p, err = NewProjectFromURL(u) p, err = NewProjectFromURL(u)
...@@ -112,6 +119,8 @@ func TestNewProjectFromURL(t *testing.T) { ...@@ -112,6 +119,8 @@ func TestNewProjectFromURL(t *testing.T) {
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "go-octokit", p.Name) assert.Equal(t, "go-octokit", p.Name)
assert.Equal(t, "octokit", p.Owner) assert.Equal(t, "octokit", p.Owner)
assert.Equal(t, "github.com", p.Host)
assert.Equal(t, "http", p.Protocol)
u, _ = url.Parse("https://github.com/octokit/go-octokit") u, _ = url.Parse("https://github.com/octokit/go-octokit")
p, err = NewProjectFromURL(u) p, err = NewProjectFromURL(u)
...@@ -119,6 +128,8 @@ func TestNewProjectFromURL(t *testing.T) { ...@@ -119,6 +128,8 @@ func TestNewProjectFromURL(t *testing.T) {
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, "go-octokit", p.Name) assert.Equal(t, "go-octokit", p.Name)
assert.Equal(t, "octokit", p.Owner) assert.Equal(t, "octokit", p.Owner)
assert.Equal(t, "github.com", p.Host)
assert.Equal(t, "https", p.Protocol)
u, _ = url.Parse("origin/master") u, _ = url.Parse("origin/master")
_, err = NewProjectFromURL(u) _, err = NewProjectFromURL(u)
......
package github package github
import ( import (
"github.com/bmizerany/assert"
"testing" "testing"
"github.com/bmizerany/assert"
"github.com/github/hub/fixtures"
) )
func TestParseURL(t *testing.T) { func TestParseURL(t *testing.T) {
testConfigs := fixtures.SetupTestConfigs()
defer testConfigs.TearDown()
url, err := url, err :=
ParseURL("https://github.com/jingweno/gh/pulls/21") ParseURL("https://github.com/jingweno/gh/pulls/21")
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
......
...@@ -111,6 +111,10 @@ module Hub ...@@ -111,6 +111,10 @@ module Hub
$stdout.puts ref_state $stdout.puts ref_state
end end
exit exit_code exit exit_code
rescue GitHubAPI::Exceptions
response = $!.response
display_api_exception("fetching CI status", response)
exit 1
end end
# $ hub pull-request # $ hub pull-request
...@@ -158,11 +162,13 @@ module Hub ...@@ -158,11 +162,13 @@ module Hub
head_project, options[:head] = from_github_ref.call(head, head_project) head_project, options[:head] = from_github_ref.call(head, head_project)
when '-i' when '-i'
options[:issue] = args.shift options[:issue] = args.shift
when '-o', '--browse'
open_with_browser = true
else else
if url = resolve_github_url(arg) and url.project_path =~ /^issues\/(\d+)/ if url = resolve_github_url(arg) and url.project_path =~ /^issues\/(\d+)/
options[:issue] = $1 options[:issue] = $1
base_project = url.project base_project = url.project
elsif !options[:title] elsif !options[:title] && arg.index('-') != 0
options[:title] = arg options[:title] = arg
warn "hub: Specifying pull request title without a flag is deprecated." warn "hub: Specifying pull request title without a flag is deprecated."
warn "Please use one of `-m' or `-F' options." warn "Please use one of `-m' or `-F' options."
...@@ -237,8 +243,10 @@ module Hub ...@@ -237,8 +243,10 @@ module Hub
pull = api_client.create_pullrequest(options) pull = api_client.create_pullrequest(options)
args.executable = 'echo' args.push('-u') unless open_with_browser
args.replace [pull['html_url']] browse_command(args) do
pull['html_url']
end
rescue GitHubAPI::Exceptions rescue GitHubAPI::Exceptions
response = $!.response response = $!.response
display_api_exception("creating pull request", response) display_api_exception("creating pull request", response)
...@@ -435,8 +443,9 @@ module Hub ...@@ -435,8 +443,9 @@ module Hub
user, branch = pull_data['head']['label'].split(':', 2) user, branch = pull_data['head']['label'].split(':', 2)
abort "Error: #{user}'s fork is not available anymore" unless pull_data['head']['repo'] abort "Error: #{user}'s fork is not available anymore" unless pull_data['head']['repo']
url = github_project(url.project_name, user).git_url(:private => pull_data['head']['repo']['private'], repo_name = pull_data['head']['repo']['name']
:https => https_protocol?) url = github_project(repo_name, user).git_url(:private => pull_data['head']['repo']['private'],
:https => https_protocol?)
merge_head = "#{user}/#{branch}" merge_head = "#{user}/#{branch}"
args.before ['fetch', url, "+refs/heads/#{branch}:refs/remotes/#{merge_head}"] args.before ['fetch', url, "+refs/heads/#{branch}:refs/remotes/#{merge_head}"]
...@@ -693,7 +702,7 @@ module Hub ...@@ -693,7 +702,7 @@ module Hub
"/#{subpage}" "/#{subpage}"
end end
project.web_url(path) project.web_url(path, api_client.config.method(:protocol))
end end
end end
...@@ -724,7 +733,8 @@ module Hub ...@@ -724,7 +733,8 @@ module Hub
end end
end end
project.web_url "/compare/#{range.tr('/', ';')}" path = '/compare/%s' % range.tr('/', ';')
project.web_url(path, api_client.config.method(:protocol))
end end
end end
...@@ -829,7 +839,9 @@ module Hub ...@@ -829,7 +839,9 @@ module Hub
config_file = ENV['HUB_CONFIG'] || '~/.config/hub' config_file = ENV['HUB_CONFIG'] || '~/.config/hub'
file_store = GitHubAPI::FileStore.new File.expand_path(config_file) file_store = GitHubAPI::FileStore.new File.expand_path(config_file)
file_config = GitHubAPI::Configuration.new file_store file_config = GitHubAPI::Configuration.new file_store
GitHubAPI.new file_config, :app_url => 'http://hub.github.com/' GitHubAPI.new file_config,
:app_url => 'http://hub.github.com/',
:verbose => !ENV['HUB_VERBOSE'].to_s.empty?
end end
end end
...@@ -1032,7 +1044,7 @@ help ...@@ -1032,7 +1044,7 @@ help
write.close write.close
# Don't page if the input is short enough # Don't page if the input is short enough
ENV['LESS'] = 'FSRX' ENV['LESS'] = 'FSR'
# Wait until we have input before we start the pager # Wait until we have input before we start the pager
Kernel.select [STDIN] Kernel.select [STDIN]
......
...@@ -288,7 +288,7 @@ module Hub ...@@ -288,7 +288,7 @@ module Hub
local_repo.remotes.find { |r| r.project == self } local_repo.remotes.find { |r| r.project == self }
end end
def web_url(path = nil) def web_url(path = nil, protocol_config = nil)
project_name = name_with_owner project_name = name_with_owner
if project_name.sub!(/\.wiki$/, '') if project_name.sub!(/\.wiki$/, '')
unless '/wiki' == path unless '/wiki' == path
...@@ -298,7 +298,11 @@ module Hub ...@@ -298,7 +298,11 @@ module Hub
path = '/wiki' + path path = '/wiki' + path
end end
end end
"https://#{host}/" + project_name + path.to_s '%s://%s/%s' % [
protocol_config ? protocol_config.call(host) : 'https',
host,
project_name + path.to_s
]
end end
def git_url(options = {}) def git_url(options = {})
......
...@@ -27,8 +27,11 @@ module Hub ...@@ -27,8 +27,11 @@ module Hub
def initialize config, options def initialize config, options
@config = config @config = config
@oauth_app_url = options.fetch(:app_url) @oauth_app_url = options.fetch(:app_url)
@verbose = options.fetch(:verbose, false)
end end
def verbose?() @verbose end
# Fake exception type for net/http exception handling. # Fake exception type for net/http exception handling.
# Necessary because net/http may or may not be loaded at the time. # Necessary because net/http may or may not be loaded at the time.
module Exceptions module Exceptions
...@@ -180,6 +183,8 @@ module Hub ...@@ -180,6 +183,8 @@ module Hub
when 'custom' then err['message'] when 'custom' then err['message']
when 'missing_field' when 'missing_field'
%(Missing field: "%s") % err['field'] %(Missing field: "%s") % err['field']
when 'already_exists'
%(Duplicate value for "%s") % err['field']
when 'invalid' when 'invalid'
%(Invalid value for "%s": "%s") % [ err['field'], err['value'] ] %(Invalid value for "%s": "%s") % [ err['field'], err['value'] ]
when 'unauthorized' when 'unauthorized'
...@@ -246,8 +251,10 @@ module Hub ...@@ -246,8 +251,10 @@ module Hub
end end
def configure_connection req, url def configure_connection req, url
url.scheme = config.protocol(url.host)
if ENV['HUB_TEST_HOST'] if ENV['HUB_TEST_HOST']
req['Host'] = url.host req['Host'] = url.host
req['X-Original-Scheme'] = url.scheme
url = url.dup url = url.dup
url.scheme = 'http' url.scheme = 'http'
url.host, test_port = ENV['HUB_TEST_HOST'].split(':') url.host, test_port = ENV['HUB_TEST_HOST'].split(':')
...@@ -322,7 +329,7 @@ module Hub ...@@ -322,7 +329,7 @@ module Hub
end end
end end
if found = res.data.find {|auth| auth['app']['url'] == oauth_app_url } if found = res.data.find {|auth| auth['note'] == 'hub' || auth['note_url'] == oauth_app_url }
found['token'] found['token']
else else
# create a new authorization # create a new authorization
...@@ -342,9 +349,63 @@ module Hub ...@@ -342,9 +349,63 @@ module Hub
end end
end end
module Verbose
def finalize_request(req, url)
super
dump_request_info(req, url) if verbose?
end
def perform_request(*)
res = super
dump_response_info(res) if verbose?
res
end
def verbose_puts(msg)
msg = "\e[36m%s\e[m" % msg if $stderr.tty?
$stderr.puts msg
end
def dump_request_info(req, url)
verbose_puts "> %s %s://%s%s" % [
req.method.to_s.upcase,
url.scheme,
url.host,
req.path,
]
dump_headers(req, '> ')
dump_body(req)
end
def dump_response_info(res)
verbose_puts "< HTTP %s" % res.status
dump_headers(res, '< ')
dump_body(res)
end
def dump_body(obj)
verbose_puts obj.body if obj.body
end
DUMP_HEADERS = %w[ Authorization X-GitHub-OTP Location ]
def dump_headers(obj, indent)
DUMP_HEADERS.each do |header|
if value = obj[header]
verbose_puts '%s%s: %s' % [
indent,
header,
value.sub(/^(basic|token) (.+)/i, '\1 [REDACTED]'),
]
end
end
end
end
include HttpMethods include HttpMethods
include OAuth include OAuth
include GistAuth include GistAuth
include Verbose
# Filesystem store suitable for Configuration # Filesystem store suitable for Configuration
class FileStore class FileStore
...@@ -472,6 +533,11 @@ module Hub ...@@ -472,6 +533,11 @@ module Hub
end end
end end
def protocol host
host = normalize_host host
@data.fetch_value(host, nil, :protocol) { 'https' }
end
def value_to_persist(value = nil) def value_to_persist(value = nil)
@data.persist_next_change! @data.persist_next_change!
value value
...@@ -481,6 +547,7 @@ module Hub ...@@ -481,6 +547,7 @@ module Hub
print "#{what}: " print "#{what}: "
$stdin.gets.chomp $stdin.gets.chomp
rescue Interrupt rescue Interrupt
puts
abort abort
end end
...@@ -496,6 +563,7 @@ module Hub ...@@ -496,6 +563,7 @@ module Hub
$stdin.gets.chomp $stdin.gets.chomp
end end
rescue Interrupt rescue Interrupt
puts
abort abort
end end
...@@ -503,6 +571,7 @@ module Hub ...@@ -503,6 +571,7 @@ module Hub
print "two-factor authentication code: " print "two-factor authentication code: "
$stdin.gets.chomp $stdin.gets.chomp
rescue Interrupt rescue Interrupt
puts
abort abort
end end
......
module Hub module Hub
Version = VERSION = '1.11.2' Version = VERSION = '1.12.0'
end end
.\" generated with Ronn/v0.7.3 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3
. .
.TH "HUB" "1" "December 2013" "GITHUB" "Hub Manual" .TH "HUB" "1" "February 2014" "GITHUB" "Hub Manual"
. .
.SH "NAME" .SH "NAME"
\fBhub\fR \- git + hub = github \fBhub\fR \- git + hub = github
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
\fBgit fork\fR [\fB\-\-no\-remote\fR] \fBgit fork\fR [\fB\-\-no\-remote\fR]
. .
.br .br
\fBgit pull\-request\fR [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR] \fBgit pull\-request\fR [\fB\-o\fR|\fB\-\-browse\fR] [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR]
. .
.br .br
\fBgit ci\-status\fR [\fB\-v\fR] [\fICOMMIT\fR] \fBgit ci\-status\fR [\fB\-v\fR] [\fICOMMIT\fR]
...@@ -148,13 +148,16 @@ Open a GitHub compare view page in the system\'s default web browser\. \fISTART\ ...@@ -148,13 +148,16 @@ Open a GitHub compare view page in the system\'s default web browser\. \fISTART\
Forks the original project (referenced by "origin" remote) on GitHub and adds a new remote for it under your username\. Forks the original project (referenced by "origin" remote) on GitHub and adds a new remote for it under your username\.
. .
.TP .TP
\fBgit pull\-request\fR [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR] \fBgit pull\-request\fR [\fB\-o\fR|\fB\-\-browse\fR] [\fB\-f\fR] [\fB\-m\fR \fIMESSAGE\fR|\fB\-F\fR \fIFILE\fR|\fB\-i\fR \fIISSUE\fR|\fIISSUE\-URL\fR] [\fB\-b\fR \fIBASE\fR] [\fB\-h\fR \fIHEAD\fR]
Opens a pull request on GitHub for the project that the "origin" remote points to\. The default head of the pull request is the current branch\. Both base and head of the pull request can be explicitly given in one of the following formats: "branch", "owner:branch", "owner/repo:branch"\. This command will abort operation if it detects that the current topic branch has local commits that are not yet pushed to its upstream branch on the remote\. To skip this check, use \fB\-f\fR\. Opens a pull request on GitHub for the project that the "origin" remote points to\. The default head of the pull request is the current branch\. Both base and head of the pull request can be explicitly given in one of the following formats: "branch", "owner:branch", "owner/repo:branch"\. This command will abort operation if it detects that the current topic branch has local commits that are not yet pushed to its upstream branch on the remote\. To skip this check, use \fB\-f\fR\.
. .
.IP .IP
Without \fIMESSAGE\fR or \fIFILE\fR, a text editor will open in which title and body of the pull request can be entered in the same manner as git commit message\. Pull request message can also be passed via stdin with \fB\-F \-\fR\. Without \fIMESSAGE\fR or \fIFILE\fR, a text editor will open in which title and body of the pull request can be entered in the same manner as git commit message\. Pull request message can also be passed via stdin with \fB\-F \-\fR\.
. .
.IP .IP
With \fB\-o\fR or \fB\-\-browse\fR, the new pull request will open in the web browser\.
.
.IP
Issue to pull request conversion via \fB\-i <ISSUE>\fR or \fIISSUE\-URL\fR arguments is deprecated and will likely be removed from the future versions of both hub and GitHub API\. Issue to pull request conversion via \fB\-i <ISSUE>\fR or \fIISSUE\-URL\fR arguments is deprecated and will likely be removed from the future versions of both hub and GitHub API\.
. .
.TP .TP
......
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
<code>git browse</code> [<code>-u</code>] [[<var>USER</var><code>/</code>]<var>REPOSITORY</var>] [SUBPAGE]<br /> <code>git browse</code> [<code>-u</code>] [[<var>USER</var><code>/</code>]<var>REPOSITORY</var>] [SUBPAGE]<br />
<code>git compare</code> [<code>-u</code>] [<var>USER</var>] [[<var>START</var>...]<var>END</var>]<br /> <code>git compare</code> [<code>-u</code>] [<var>USER</var>] [[<var>START</var>...]<var>END</var>]<br />
<code>git fork</code> [<code>--no-remote</code>]<br /> <code>git fork</code> [<code>--no-remote</code>]<br />
<code>git pull-request</code> [<code>-f</code>] [<code>-m</code> <var>MESSAGE</var>|<code>-F</code> <var>FILE</var>|<code>-i</code> <var>ISSUE</var>|<var>ISSUE-URL</var>] [<code>-b</code> <var>BASE</var>] [<code>-h</code> <var>HEAD</var>]<br /> <code>git pull-request</code> [<code>-o</code>|<code>--browse</code>] [<code>-f</code>] [<code>-m</code> <var>MESSAGE</var>|<code>-F</code> <var>FILE</var>|<code>-i</code> <var>ISSUE</var>|<var>ISSUE-URL</var>] [<code>-b</code> <var>BASE</var>] [<code>-h</code> <var>HEAD</var>]<br />
<code>git ci-status</code> [<code>-v</code>] [<var>COMMIT</var>]</p> <code>git ci-status</code> [<code>-v</code>] [<var>COMMIT</var>]</p>
<h2 id="DESCRIPTION">DESCRIPTION</h2> <h2 id="DESCRIPTION">DESCRIPTION</h2>
...@@ -184,7 +184,7 @@ If <var>END</var> is omitted, GitHub compare view is opened for the current bran ...@@ -184,7 +184,7 @@ If <var>END</var> is omitted, GitHub compare view is opened for the current bran
With <code>-u</code>, outputs the URL rather than opening the browser.</p></dd> With <code>-u</code>, outputs the URL rather than opening the browser.</p></dd>
<dt><code>git fork</code> [<code>--no-remote</code>]</dt><dd><p>Forks the original project (referenced by "origin" remote) on GitHub and <dt><code>git fork</code> [<code>--no-remote</code>]</dt><dd><p>Forks the original project (referenced by "origin" remote) on GitHub and
adds a new remote for it under your username.</p></dd> adds a new remote for it under your username.</p></dd>
<dt><code>git pull-request</code> [<code>-f</code>] [<code>-m</code> <var>MESSAGE</var>|<code>-F</code> <var>FILE</var>|<code>-i</code> <var>ISSUE</var>|<var>ISSUE-URL</var>] [<code>-b</code> <var>BASE</var>] [<code>-h</code> <var>HEAD</var>]</dt><dd><p>Opens a pull request on GitHub for the project that the "origin" remote <dt><code>git pull-request</code> [<code>-o</code>|<code>--browse</code>] [<code>-f</code>] [<code>-m</code> <var>MESSAGE</var>|<code>-F</code> <var>FILE</var>|<code>-i</code> <var>ISSUE</var>|<var>ISSUE-URL</var>] [<code>-b</code> <var>BASE</var>] [<code>-h</code> <var>HEAD</var>]</dt><dd><p>Opens a pull request on GitHub for the project that the "origin" remote
points to. The default head of the pull request is the current branch. points to. The default head of the pull request is the current branch.
Both base and head of the pull request can be explicitly given in one of Both base and head of the pull request can be explicitly given in one of
the following formats: "branch", "owner:branch", "owner/repo:branch". the following formats: "branch", "owner:branch", "owner/repo:branch".
...@@ -196,6 +196,8 @@ on the remote. To skip this check, use <code>-f</code>.</p> ...@@ -196,6 +196,8 @@ on the remote. To skip this check, use <code>-f</code>.</p>
of the pull request can be entered in the same manner as git commit message. of the pull request can be entered in the same manner as git commit message.
Pull request message can also be passed via stdin with <code>-F -</code>.</p> Pull request message can also be passed via stdin with <code>-F -</code>.</p>
<p>With <code>-o</code> or <code>--browse</code>, the new pull request will open in the web browser.</p>
<p>Issue to pull request conversion via <code>-i &lt;ISSUE></code> or <var>ISSUE-URL</var> <p>Issue to pull request conversion via <code>-i &lt;ISSUE></code> or <var>ISSUE-URL</var>
arguments is deprecated and will likely be removed from the future versions arguments is deprecated and will likely be removed from the future versions
of both hub and GitHub API.</p></dd> of both hub and GitHub API.</p></dd>
...@@ -457,7 +459,7 @@ $ git help hub ...@@ -457,7 +459,7 @@ $ git help hub
<ol class='man-decor man-foot man foot'> <ol class='man-decor man-foot man foot'>
<li class='tl'>GITHUB</li> <li class='tl'>GITHUB</li>
<li class='tc'>December 2013</li> <li class='tc'>February 2014</li>
<li class='tr'>hub(1)</li> <li class='tr'>hub(1)</li>
</ol> </ol>
......
...@@ -27,7 +27,7 @@ hub(1) -- git + hub = github ...@@ -27,7 +27,7 @@ hub(1) -- git + hub = github
`git browse` [`-u`] [[<USER>`/`]<REPOSITORY>] [SUBPAGE] `git browse` [`-u`] [[<USER>`/`]<REPOSITORY>] [SUBPAGE]
`git compare` [`-u`] [<USER>] [[<START>...]<END>] `git compare` [`-u`] [<USER>] [[<START>...]<END>]
`git fork` [`--no-remote`] `git fork` [`--no-remote`]
`git pull-request` [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>] `git pull-request` [`-o`|`--browse`] [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>]
`git ci-status` [`-v`] [<COMMIT>] `git ci-status` [`-v`] [<COMMIT>]
## DESCRIPTION ## DESCRIPTION
...@@ -142,7 +142,7 @@ hub also adds some custom commands that are otherwise not present in git: ...@@ -142,7 +142,7 @@ hub also adds some custom commands that are otherwise not present in git:
Forks the original project (referenced by "origin" remote) on GitHub and Forks the original project (referenced by "origin" remote) on GitHub and
adds a new remote for it under your username. adds a new remote for it under your username.
* `git pull-request` [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>]: * `git pull-request` [`-o`|`--browse`] [`-f`] [`-m` <MESSAGE>|`-F` <FILE>|`-i` <ISSUE>|<ISSUE-URL>] [`-b` <BASE>] [`-h` <HEAD>]:
Opens a pull request on GitHub for the project that the "origin" remote Opens a pull request on GitHub for the project that the "origin" remote
points to. The default head of the pull request is the current branch. points to. The default head of the pull request is the current branch.
Both base and head of the pull request can be explicitly given in one of Both base and head of the pull request can be explicitly given in one of
...@@ -155,6 +155,8 @@ hub also adds some custom commands that are otherwise not present in git: ...@@ -155,6 +155,8 @@ hub also adds some custom commands that are otherwise not present in git:
of the pull request can be entered in the same manner as git commit message. of the pull request can be entered in the same manner as git commit message.
Pull request message can also be passed via stdin with `-F -`. Pull request message can also be passed via stdin with `-F -`.
With `-o` or `--browse`, the new pull request will open in the web browser.
Issue to pull request conversion via `-i <ISSUE>` or <ISSUE-URL> Issue to pull request conversion via `-i <ISSUE>` or <ISSUE-URL>
arguments is deprecated and will likely be removed from the future versions arguments is deprecated and will likely be removed from the future versions
of both hub and GitHub API. of both hub and GitHub API.
......
...@@ -39,7 +39,7 @@ fi ...@@ -39,7 +39,7 @@ fi
bundle "$@" bundle "$@"
if [ ! -f "$cache_name" ]; then if [ ! -f "$cache_name" ] && [ -n "$AMAZON_SECRET_ACCESS_KEY" ]; then
echo "Caching \`${bundle_path}' to S3" echo "Caching \`${bundle_path}' to S3"
tar czf "$cache_name" "$bundle_path" tar czf "$cache_name" "$bundle_path"
script/s3-put "$cache_name" "${AMAZON_S3_BUCKET}:${TRAVIS_REPO_SLUG}/${cache_name}" script/s3-put "$cache_name" "${AMAZON_S3_BUCKET}:${TRAVIS_REPO_SLUG}/${cache_name}"
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
set -e set -e
TMP_GOPATH="${TMPDIR:-/tmp}/go" TMP_GOPATH="${TMPDIR:-/tmp}/go"
TMP_SELF="${TMP_GOPATH}/src/github.com/jingweno/gh" TMP_SELF="${TMP_GOPATH}/src/github.com/github/hub"
export GOPATH="${TMP_GOPATH}:${PWD}/Godeps/_workspace:$GOPATH" export GOPATH="${TMP_GOPATH}:${PWD}/Godeps/_workspace:$GOPATH"
......
...@@ -410,7 +410,7 @@ class HubTest < Minitest::Test ...@@ -410,7 +410,7 @@ class HubTest < Minitest::Test
assert_equal expected, usage_help assert_equal expected, usage_help
usage_help = hub("pull-request -h") usage_help = hub("pull-request -h")
expected = "Usage: git pull-request [-f] [-m MESSAGE|-F FILE|-i ISSUE|ISSUE-URL] [-b BASE] [-h HEAD]\n" expected = "Usage: git pull-request [-o|--browse] [-f] [-m MESSAGE|-F FILE|-i ISSUE|ISSUE-URL] [-b BASE] [-h HEAD]\n"
assert_equal expected, usage_help assert_equal expected, usage_help
end end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册