bump deps

Signed-off-by: Alex Suraci <asuraci@pivotal.io>
This commit is contained in:
Chris Brown
2015-02-17 18:08:25 -08:00
committed by Alex Suraci
parent efbfb91683
commit 8b3dc6edbe
57 changed files with 1827 additions and 255 deletions

14
Godeps/Godeps.json generated
View File

@@ -7,8 +7,8 @@
"Deps": [
{
"ImportPath": "code.google.com/p/goauth2/oauth",
"Comment": "weekly-43",
"Rev": "d4571a95014ff1fdd0055dc0f6b7e977a42b1e5a"
"Comment": "weekly-56",
"Rev": "afe77d958c701557ec5dc56f6936fcc194d15520"
},
{
"ImportPath": "github.com/blang/semver",
@@ -17,20 +17,20 @@
},
{
"ImportPath": "github.com/google/go-github/github",
"Rev": "32eadd61120c7a84b8eeac3ac3795cb453a33054"
"Rev": "7ea4ee6d222607c11ea86e99a6f6723beeae785d"
},
{
"ImportPath": "github.com/google/go-querystring/query",
"Rev": "ec0a78e0f4db229b7897be36596a8944230b857a"
"Rev": "d8840cbb2baa915f4836edda4750050a2c0b7aea"
},
{
"ImportPath": "github.com/mitchellh/colorstring",
"Rev": "9797cb57e314a5a2c4d735ff060d5c6dcba57749"
"Rev": "15fc698eaae194ff160846daf77922a45b9290a7"
},
{
"ImportPath": "github.com/onsi/ginkgo",
"Comment": "v1.1.0-33-g17ea479",
"Rev": "17ea479729ee427265ac1e913443018350946ddf"
"Comment": "v1.1.0-38-g5ed93e4",
"Rev": "5ed93e443a4b7dfe9f5e95ca87e6082e503021d2"
},
{
"ImportPath": "github.com/onsi/gomega",

View File

@@ -67,7 +67,7 @@ func main() {
// Get an authorization code from the data provider.
// ("Please ask the user if I can access this resource.")
url := config.AuthCodeURL("")
fmt.Println("Visit this URL to get a code, then run again with -code=YOUR_CODE\n")
fmt.Print("Visit this URL to get a code, then run again with -code=YOUR_CODE\n\n")
fmt.Println(url)
return
}

View File

@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The oauth package provides support for making
// OAuth2-authenticated HTTP requests.
// Package oauth supports making OAuth2-authenticated HTTP requests.
//
// Example usage:
//
@@ -39,14 +38,23 @@ package oauth
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
)
// OAuthError is the error type returned by many operations.
//
// In retrospect it should not exist. Don't depend on it.
type OAuthError struct {
prefix string
msg string
@@ -122,7 +130,16 @@ type Config struct {
// TokenCache allows tokens to be cached for subsequent requests.
TokenCache Cache
AccessType string // Optional, "online" (default) or "offline", no refresh token if "online"
// AccessType is an OAuth extension that gets sent as the
// "access_type" field in the URL from AuthCodeURL.
// See https://developers.google.com/accounts/docs/OAuth2WebServer.
// It may be "online" (the default) or "offline".
// If your application needs to refresh access tokens when the
// user is not present at the browser, then use offline. This
// will result in your application obtaining a refresh token
// the first time your application exchanges an authorization
// code for a user.
AccessType string
// ApprovalPrompt indicates whether the user should be
// re-prompted for consent. If set to "auto" (default) the
@@ -140,10 +157,19 @@ type Token struct {
AccessToken string
RefreshToken string
Expiry time.Time // If zero the token has no (known) expiry time.
Extra map[string]string // May be nil.
// Extra optionally contains extra metadata from the server
// when updating a token. The only current key that may be
// populated is "id_token". It may be nil and will be
// initialized as needed.
Extra map[string]string
}
// Expired reports whether the token has expired or is invalid.
func (t *Token) Expired() bool {
if t.AccessToken == "" {
return true
}
if t.Expiry.IsZero() {
return false
}
@@ -164,6 +190,9 @@ type Transport struct {
*Config
*Token
// mu guards modifying the token.
mu sync.Mutex
// Transport is the HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
// (It should never be an oauth.Transport.)
@@ -192,11 +221,11 @@ func (c *Config) AuthCodeURL(state string) string {
q := url.Values{
"response_type": {"code"},
"client_id": {c.ClientId},
"redirect_uri": {c.RedirectURL},
"scope": {c.Scope},
"state": {state},
"access_type": {c.AccessType},
"approval_prompt": {c.ApprovalPrompt},
"state": condVal(state),
"scope": condVal(c.Scope),
"redirect_uri": condVal(c.RedirectURL),
"access_type": condVal(c.AccessType),
"approval_prompt": condVal(c.ApprovalPrompt),
}.Encode()
if url_.RawQuery == "" {
url_.RawQuery = q
@@ -206,6 +235,13 @@ func (c *Config) AuthCodeURL(state string) string {
return url_.String()
}
func condVal(v string) []string {
if v == "" {
return nil
}
return []string{v}
}
// Exchange takes a code and gets access Token from the remote server.
func (t *Transport) Exchange(code string) (*Token, error) {
if t.Config == nil {
@@ -246,35 +282,48 @@ func (t *Transport) Exchange(code string) (*Token, error) {
// If the Token is invalid callers should expect HTTP-level errors,
// as indicated by the Response's StatusCode.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
accessToken, err := t.getAccessToken()
if err != nil {
return nil, err
}
// To set the Authorization header, we must make a copy of the Request
// so that we don't modify the Request we were given.
// This is required by the specification of http.RoundTripper.
req = cloneRequest(req)
req.Header.Set("Authorization", "Bearer "+accessToken)
// Make the HTTP request.
return t.transport().RoundTrip(req)
}
func (t *Transport) getAccessToken() (string, error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.Token == nil {
if t.Config == nil {
return nil, OAuthError{"RoundTrip", "no Config supplied"}
return "", OAuthError{"RoundTrip", "no Config supplied"}
}
if t.TokenCache == nil {
return nil, OAuthError{"RoundTrip", "no Token supplied"}
return "", OAuthError{"RoundTrip", "no Token supplied"}
}
var err error
t.Token, err = t.TokenCache.Token()
if err != nil {
return nil, err
return "", err
}
}
// Refresh the Token if it has expired.
if t.Expired() {
if err := t.Refresh(); err != nil {
return nil, err
return "", err
}
}
// To set the Authorization header, we must make a copy of the Request
// so that we don't modify the Request we were given.
// This is required by the specification of http.RoundTripper.
req = cloneRequest(req)
req.Header.Set("Authorization", "Bearer "+t.AccessToken)
// Make the HTTP request.
return t.transport().RoundTrip(req)
if t.AccessToken == "" {
return "", errors.New("no access token obtained from refresh")
}
return t.AccessToken, nil
}
// cloneRequest returns a clone of the provided *http.Request.
@@ -316,31 +365,80 @@ func (t *Transport) Refresh() error {
return nil
}
// AuthenticateClient gets an access Token using the client_credentials grant
// type.
func (t *Transport) AuthenticateClient() error {
if t.Config == nil {
return OAuthError{"Exchange", "no Config supplied"}
}
if t.Token == nil {
t.Token = &Token{}
}
return t.updateToken(t.Token, url.Values{"grant_type": {"client_credentials"}})
}
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
// implements the OAuth2 spec correctly
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
// In summary:
// - Reddit only accepts client secret in the Authorization header
// - Dropbox accepts either it in URL param or Auth header, but not both.
// - Google only accepts URL param (not spec compliant?), not Auth header
func providerAuthHeaderWorks(tokenURL string) bool {
if strings.HasPrefix(tokenURL, "https://accounts.google.com/") ||
strings.HasPrefix(tokenURL, "https://github.com/") ||
strings.HasPrefix(tokenURL, "https://api.instagram.com/") ||
strings.HasPrefix(tokenURL, "https://www.douban.com/") {
// Some sites fail to implement the OAuth2 spec fully.
return false
}
// Assume the provider implements the spec properly
// otherwise. We can add more exceptions as they're
// discovered. We will _not_ be adding configurable hooks
// to this package to let users select server bugs.
return true
}
// updateToken mutates both tok and v.
func (t *Transport) updateToken(tok *Token, v url.Values) error {
v.Set("client_id", t.ClientId)
bustedAuth := !providerAuthHeaderWorks(t.TokenURL)
if bustedAuth {
v.Set("client_secret", t.ClientSecret)
r, err := (&http.Client{Transport: t.transport()}).PostForm(t.TokenURL, v)
}
client := &http.Client{Transport: t.transport()}
req, err := http.NewRequest("POST", t.TokenURL, strings.NewReader(v.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if !bustedAuth {
req.SetBasicAuth(t.ClientId, t.ClientSecret)
}
r, err := client.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != 200 {
return OAuthError{"updateToken", r.Status}
return OAuthError{"updateToken", "Unexpected HTTP status " + r.Status}
}
var b struct {
Access string `json:"access_token"`
Refresh string `json:"refresh_token"`
ExpiresIn time.Duration `json:"expires_in"`
ExpiresIn int64 `json:"expires_in"` // seconds
Id string `json:"id_token"`
}
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
return err
}
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
switch content {
case "application/x-www-form-urlencoded", "text/plain":
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
vals, err := url.ParseQuery(string(body))
if err != nil {
return err
@@ -348,25 +446,25 @@ func (t *Transport) updateToken(tok *Token, v url.Values) error {
b.Access = vals.Get("access_token")
b.Refresh = vals.Get("refresh_token")
b.ExpiresIn, _ = time.ParseDuration(vals.Get("expires_in") + "s")
b.ExpiresIn, _ = strconv.ParseInt(vals.Get("expires_in"), 10, 64)
b.Id = vals.Get("id_token")
default:
if err = json.NewDecoder(r.Body).Decode(&b); err != nil {
return err
if err = json.Unmarshal(body, &b); err != nil {
return fmt.Errorf("got bad response from server: %q", body)
}
// The JSON parser treats the unitless ExpiresIn like 'ns' instead of 's' as above,
// so compensate here.
b.ExpiresIn *= time.Second
}
if b.Access == "" {
return errors.New("received empty access token from authorization server")
}
tok.AccessToken = b.Access
// Don't overwrite `RefreshToken` with an empty value
if len(b.Refresh) > 0 {
if b.Refresh != "" {
tok.RefreshToken = b.Refresh
}
if b.ExpiresIn == 0 {
tok.Expiry = time.Time{}
} else {
tok.Expiry = time.Now().Add(b.ExpiresIn)
tok.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second)
}
if b.Id != "" {
if tok.Extra == nil {

View File

@@ -25,6 +25,7 @@ var requests = []struct {
path: "/token",
query: "grant_type=authorization_code&code=c0d3&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token1",
@@ -39,6 +40,7 @@ var requests = []struct {
path: "/token",
query: "grant_type=refresh_token&refresh_token=refreshtoken1&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token2",
@@ -54,8 +56,22 @@ var requests = []struct {
query: "grant_type=refresh_token&refresh_token=refreshtoken2&client_id=cl13nt1d",
contenttype: "application/x-www-form-urlencoded",
body: "access_token=token3&refresh_token=refreshtoken3&id_token=idtoken3&expires_in=3600",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
},
{path: "/secure", auth: "Bearer token3", body: "third payload"},
{
path: "/token",
query: "grant_type=client_credentials&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token4",
"expires_in":3600
}
`,
},
{path: "/secure", auth: "Bearer token4", body: "fourth payload"},
}
func TestOAuth(t *testing.T) {
@@ -131,6 +147,18 @@ func TestOAuth(t *testing.T) {
}
checkBody(t, resp, "third payload")
checkToken(t, transport.Token, "token3", "refreshtoken3", "idtoken3")
transport.Token = &Token{}
err = transport.AuthenticateClient()
if err != nil {
t.Fatalf("AuthenticateClient: %v", err)
}
checkToken(t, transport.Token, "token4", "", "")
resp, err = c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "fourth payload")
}
func checkToken(t *testing.T, tok *Token, access, refresh, id string) {
@@ -143,16 +171,21 @@ func checkToken(t *testing.T, tok *Token, access, refresh, id string) {
if g, w := tok.Extra["id_token"], id; g != w {
t.Errorf("Extra['id_token'] = %q, want %q", g, w)
}
if tok.Expiry.IsZero() {
t.Errorf("Expiry is zero; want ~1 hour")
} else {
exp := tok.Expiry.Sub(time.Now())
if (time.Hour-time.Second) > exp || exp > time.Hour {
const slop = 3 * time.Second // time moving during test
if (time.Hour-slop) > exp || exp > time.Hour {
t.Errorf("Expiry = %v, want ~1 hour", exp)
}
}
}
func checkBody(t *testing.T, r *http.Response, body string) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Error("reading reponse body: %v, want %q", err, body)
t.Errorf("reading reponse body: %v, want %q", err, body)
}
if g, w := string(b), body; g != w {
t.Errorf("request body mismatch: got %q, want %q", g, w)
@@ -184,3 +217,20 @@ func TestCachePermissions(t *testing.T) {
t.Errorf("Created cache file has mode %#o, want non-accessible to group+other", fi.Mode())
}
}
func TestTokenExpired(t *testing.T) {
tests := []struct {
token Token
expired bool
}{
{Token{AccessToken: "foo"}, false},
{Token{AccessToken: ""}, true},
{Token{AccessToken: "foo", Expiry: time.Now().Add(-1 * time.Hour)}, true},
{Token{AccessToken: "foo", Expiry: time.Now().Add(1 * time.Hour)}, false},
}
for _, tt := range tests {
if got := tt.token.Expired(); got != tt.expired {
t.Errorf("token %+v Expired = %v; want %v", tt.token, got, !got)
}
}
}

View File

@@ -47,8 +47,9 @@ type PushEvent struct {
PushID *int `json:"push_id,omitempty"`
Head *string `json:"head,omitempty"`
Ref *string `json:"ref,omitempty"`
Size *int `json:"ref,omitempty"`
Size *int `json:"size,omitempty"`
Commits []PushEventCommit `json:"commits,omitempty"`
Repo *Repository `json:"repository,omitempty"`
}
func (p PushEvent) String() string {
@@ -61,13 +62,44 @@ type PushEventCommit struct {
Message *string `json:"message,omitempty"`
Author *CommitAuthor `json:"author,omitempty"`
URL *string `json:"url,omitempty"`
Distinct *bool `json:"distinct"`
Distinct *bool `json:"distinct,omitempty"`
Added []string `json:"added,omitempty"`
Removed []string `json:"removed,omitempty"`
Modified []string `json:"modified,omitempty"`
}
func (p PushEventCommit) String() string {
return Stringify(p)
}
//PullRequestEvent represents the payload delivered by PullRequestEvent webhook
type PullRequestEvent struct {
Action *string `json:"action,omitempty"`
Number *int `json:"number,omitempty"`
PullRequest *PullRequest `json:"pull_request,omitempty"`
Repo *Repository `json:"repository,omitempty"`
Sender *User `json:"sender,omitempty"`
}
// IssueActivityEvent represents the payload delivered by Issue webhook
type IssueActivityEvent struct {
Action *string `json:"action,omitempty"`
Issue *Issue `json:"issue,omitempty"`
Repo *Repository `json:"repository,omitempty"`
Sender *User `json:"sender,omitempty"`
}
// IssueCommentEvent represents the payload delivered by IssueComment webhook
//
// This webhook also gets fired for comments on pull requests
type IssueCommentEvent struct {
Action *string `json:"action,omitempty"`
Issue *Issue `json:"issue,omitempty"`
Comment *IssueComment `json:"comment,omitempty"`
Repo *Repository `json:"repository,omitempty"`
Sender *User `json:"sender,omitempty"`
}
// ListEvents drinks from the firehose of all public events across GitHub.
//
// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events

View File

@@ -95,5 +95,31 @@ bool, and int values. For example:
client.Repositories.Create("", repo)
Users who have worked with protocol buffers should find this pattern familiar.
Pagination
All requests for resource collections (repos, pull requests, issues, etc)
support pagination. Pagination options are described in the
ListOptions struct and passed to the list methods directly or as an
embedded type of a more specific list options struct (for example
PullRequestListOptions). Pages information is available via Response struct.
opt := &github.RepositoryListByOrgOptions{
ListOptions: github.ListOptions{PerPage: 10},
}
// get all pages of results
var allRepos []github.Repository
for {
repos, resp, err := client.Repositories.ListByOrg("github", opt)
if err != nil {
return err
}
allRepos = append(allRepos, repos...)
if resp.NextPage == 0 {
break
}
opt.ListOptions.Page = resp.NextPage
}
*/
package github

View File

@@ -30,6 +30,7 @@ type Gist struct {
GitPullURL *string `json:"git_pull_url,omitempty"`
GitPushURL *string `json:"git_push_url,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
func (g Gist) String() string {

View File

@@ -19,7 +19,12 @@ type Commit struct {
Tree *Tree `json:"tree,omitempty"`
Parents []Commit `json:"parents,omitempty"`
Stats *CommitStats `json:"stats,omitempty"`
URL *string `json:url,omitempty"`
URL *string `json:"url,omitempty"`
// CommentCount is the number of GitHub comments on the commit. This
// is only populated for requests that fetch GitHub data like
// Pulls.ListCommits, Repositories.ListCommits, etc.
CommentCount *int `json:"comment_count,omitempty"`
}
func (c Commit) String() string {
@@ -57,6 +62,15 @@ func (s *GitService) GetCommit(owner string, repo string, sha string) (*Commit,
return c, resp, err
}
// createCommit represents the body of a CreateCommit request.
type createCommit struct {
Author *CommitAuthor `json:"author,omitempty"`
Committer *CommitAuthor `json:"committer,omitempty"`
Message *string `json:"message,omitempty"`
Tree *string `json:"tree,omitempty"`
Parents []string `json:"parents,omitempty"`
}
// CreateCommit creates a new commit in a repository.
//
// The commit.Committer is optional and will be filled with the commit.Author
@@ -66,7 +80,24 @@ func (s *GitService) GetCommit(owner string, repo string, sha string) (*Commit,
// GitHub API docs: http://developer.github.com/v3/git/commits/#create-a-commit
func (s *GitService) CreateCommit(owner string, repo string, commit *Commit) (*Commit, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/git/commits", owner, repo)
req, err := s.client.NewRequest("POST", u, commit)
body := &createCommit{}
if commit != nil {
parents := make([]string, len(commit.Parents))
for i, parent := range commit.Parents {
parents[i] = *parent.SHA
}
body = &createCommit{
Author: commit.Author,
Committer: commit.Committer,
Message: commit.Message,
Tree: commit.Tree.SHA,
Parents: parents,
}
}
req, err := s.client.NewRequest("POST", u, body)
if err != nil {
return nil, nil, err
}

View File

@@ -42,15 +42,25 @@ func TestGitService_CreateCommit(t *testing.T) {
setup()
defer teardown()
input := &Commit{Message: String("m"), Tree: &Tree{SHA: String("t")}}
input := &Commit{
Message: String("m"),
Tree: &Tree{SHA: String("t")},
Parents: []Commit{{SHA: String("p")}},
}
mux.HandleFunc("/repos/o/r/git/commits", func(w http.ResponseWriter, r *http.Request) {
v := new(Commit)
v := new(createCommit)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
want := &createCommit{
Message: input.Message,
Tree: String("t"),
Parents: []string{"p"},
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Request body = %+v, want %+v", v, want)
}
fmt.Fprint(w, `{"sha":"s"}`)
})

View File

@@ -7,6 +7,7 @@ package github
import (
"fmt"
"strings"
)
// Reference represents a GitHub reference.
@@ -47,6 +48,7 @@ type updateRefRequest struct {
//
// GitHub API docs: http://developer.github.com/v3/git/refs/#get-a-reference
func (s *GitService) GetRef(owner string, repo string, ref string) (*Reference, *Response, error) {
ref = strings.TrimPrefix(ref, "refs/")
u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
@@ -105,7 +107,8 @@ func (s *GitService) ListRefs(owner, repo string, opt *ReferenceListOptions) ([]
func (s *GitService) CreateRef(owner string, repo string, ref *Reference) (*Reference, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/git/refs", owner, repo)
req, err := s.client.NewRequest("POST", u, &createRefRequest{
Ref: String("refs/" + *ref.Ref),
// back-compat with previous behavior that didn't require 'refs/' prefix
Ref: String("refs/" + strings.TrimPrefix(*ref.Ref, "refs/")),
SHA: ref.Object.SHA,
})
if err != nil {
@@ -125,7 +128,8 @@ func (s *GitService) CreateRef(owner string, repo string, ref *Reference) (*Refe
//
// GitHub API docs: http://developer.github.com/v3/git/refs/#update-a-reference
func (s *GitService) UpdateRef(owner string, repo string, ref *Reference, force bool) (*Reference, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, *ref.Ref)
refPath := strings.TrimPrefix(*ref.Ref, "refs/")
u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, refPath)
req, err := s.client.NewRequest("PATCH", u, &updateRefRequest{
SHA: ref.Object.SHA,
Force: &force,
@@ -147,6 +151,7 @@ func (s *GitService) UpdateRef(owner string, repo string, ref *Reference, force
//
// GitHub API docs: http://developer.github.com/v3/git/refs/#delete-a-reference
func (s *GitService) DeleteRef(owner string, repo string, ref string) (*Response, error) {
ref = strings.TrimPrefix(ref, "refs/")
u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {

View File

@@ -31,7 +31,7 @@ func TestGitService_GetRef(t *testing.T) {
}`)
})
ref, _, err := client.Git.GetRef("o", "r", "heads/b")
ref, _, err := client.Git.GetRef("o", "r", "refs/heads/b")
if err != nil {
t.Errorf("Git.GetRef returned error: %v", err)
}
@@ -48,6 +48,11 @@ func TestGitService_GetRef(t *testing.T) {
if !reflect.DeepEqual(ref, want) {
t.Errorf("Git.GetRef returned %+v, want %+v", ref, want)
}
// without 'refs/' prefix
if _, _, err := client.Git.GetRef("o", "r", "heads/b"); err != nil {
t.Errorf("Git.GetRef returned error: %v", err)
}
}
func TestGitService_ListRefs(t *testing.T) {
@@ -161,7 +166,7 @@ func TestGitService_CreateRef(t *testing.T) {
})
ref, _, err := client.Git.CreateRef("o", "r", &Reference{
Ref: String("heads/b"),
Ref: String("refs/heads/b"),
Object: &GitObject{
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
},
@@ -182,6 +187,17 @@ func TestGitService_CreateRef(t *testing.T) {
if !reflect.DeepEqual(ref, want) {
t.Errorf("Git.CreateRef returned %+v, want %+v", ref, want)
}
// without 'refs/' prefix
_, _, err = client.Git.CreateRef("o", "r", &Reference{
Ref: String("heads/b"),
Object: &GitObject{
SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"),
},
})
if err != nil {
t.Errorf("Git.CreateRef returned error: %v", err)
}
}
func TestGitService_UpdateRef(t *testing.T) {
@@ -214,7 +230,7 @@ func TestGitService_UpdateRef(t *testing.T) {
})
ref, _, err := client.Git.UpdateRef("o", "r", &Reference{
Ref: String("heads/b"),
Ref: String("refs/heads/b"),
Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")},
}, true)
if err != nil {
@@ -233,6 +249,15 @@ func TestGitService_UpdateRef(t *testing.T) {
if !reflect.DeepEqual(ref, want) {
t.Errorf("Git.UpdateRef returned %+v, want %+v", ref, want)
}
// without 'refs/' prefix
_, _, err = client.Git.UpdateRef("o", "r", &Reference{
Ref: String("heads/b"),
Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")},
}, true)
if err != nil {
t.Errorf("Git.UpdateRef returned error: %v", err)
}
}
func TestGitService_DeleteRef(t *testing.T) {
@@ -243,8 +268,13 @@ func TestGitService_DeleteRef(t *testing.T) {
testMethod(t, r, "DELETE")
})
_, err := client.Git.DeleteRef("o", "r", "heads/b")
_, err := client.Git.DeleteRef("o", "r", "refs/heads/b")
if err != nil {
t.Errorf("Git.DeleteRef returned error: %v", err)
}
// without 'refs/' prefix
if _, err := client.Git.DeleteRef("o", "r", "heads/b"); err != nil {
t.Errorf("Git.DeleteRef returned error: %v", err)
}
}

View File

@@ -26,6 +26,7 @@ type TreeEntry struct {
Mode *string `json:"mode,omitempty"`
Type *string `json:"type,omitempty"`
Size *int `json:"size,omitempty"`
Content *string `json:"content,omitempty"`
}
func (t TreeEntry) String() string {
@@ -57,7 +58,7 @@ func (s *GitService) GetTree(owner string, repo string, sha string, recursive bo
// createTree represents the body of a CreateTree request.
type createTree struct {
BaseTree string `json:"base_tree"`
BaseTree string `json:"base_tree,omitempty"`
Entries []TreeEntry `json:"tree"`
}

View File

@@ -116,6 +116,73 @@ func TestGitService_CreateTree(t *testing.T) {
}
}
func TestGitService_CreateTree_Content(t *testing.T) {
setup()
defer teardown()
input := []TreeEntry{
{
Path: String("content.md"),
Mode: String("100644"),
Content: String("file content"),
},
}
mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) {
v := new(createTree)
json.NewDecoder(r.Body).Decode(v)
if m := "POST"; m != r.Method {
t.Errorf("Request method = %v, want %v", r.Method, m)
}
want := &createTree{
BaseTree: "b",
Entries: input,
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want)
}
fmt.Fprint(w, `{
"sha": "5c6780ad2c68743383b740fd1dab6f6a33202b11",
"url": "https://api.github.com/repos/o/r/git/trees/5c6780ad2c68743383b740fd1dab6f6a33202b11",
"tree": [
{
"mode": "100644",
"type": "blob",
"sha": "aad8feacf6f8063150476a7b2bd9770f2794c08b",
"path": "content.md",
"size": 12,
"url": "https://api.github.com/repos/o/r/git/blobs/aad8feacf6f8063150476a7b2bd9770f2794c08b"
}
]
}`)
})
tree, _, err := client.Git.CreateTree("o", "r", "b", input)
if err != nil {
t.Errorf("Git.CreateTree returned error: %v", err)
}
want := Tree{
String("5c6780ad2c68743383b740fd1dab6f6a33202b11"),
[]TreeEntry{
{
Path: String("content.md"),
Mode: String("100644"),
Type: String("blob"),
Size: Int(12),
SHA: String("aad8feacf6f8063150476a7b2bd9770f2794c08b"),
},
},
}
if !reflect.DeepEqual(*tree, want) {
t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want)
}
}
func TestGitService_CreateTree_invalidOwner(t *testing.T) {
_, _, err := client.Git.CreateTree("%", "%", "", nil)
testURLParseError(t, err)

View File

@@ -34,6 +34,14 @@ const (
mediaTypeV3 = "application/vnd.github.v3+json"
defaultMediaType = "application/octet-stream"
// Media Type values to access preview APIs
// https://developer.github.com/changes/2014-08-05-team-memberships-api/
mediaTypeMembershipPreview = "application/vnd.github.the-wasp-preview+json"
// https://developer.github.com/changes/2014-01-09-preview-the-new-deployments-api/
mediaTypeDeploymentPreview = "application/vnd.github.cannonball-preview+json"
)
// A Client manages communication with the GitHub API.
@@ -146,8 +154,9 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
u := c.BaseURL.ResolveReference(rel)
buf := new(bytes.Buffer)
var buf io.ReadWriter
if body != nil {
buf = new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
@@ -160,7 +169,9 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
}
req.Header.Add("Accept", mediaTypeV3)
if c.UserAgent != "" {
req.Header.Add("User-Agent", c.UserAgent)
}
return req, nil
}
@@ -485,10 +496,10 @@ type UnauthenticatedRateLimitedTransport struct {
// RoundTrip implements the RoundTripper interface.
func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.ClientID == "" {
return nil, errors.New("ClientID is empty")
return nil, errors.New("t.ClientID is empty")
}
if t.ClientSecret == "" {
return nil, errors.New("ClientSecret is empty")
return nil, errors.New("t.ClientSecret is empty")
}
// To set extra querystring params, we must make a copy of the Request so

View File

@@ -80,8 +80,8 @@ func openTestFile(name, content string) (file *os.File, dir string, err error) {
}
func testMethod(t *testing.T, r *http.Request, want string) {
if want != r.Method {
t.Errorf("Request method = %v, want %v", r.Method, want)
if got := r.Method; got != want {
t.Errorf("Request method: %v, want %v", got, want)
}
}
@@ -94,14 +94,14 @@ func testFormValues(t *testing.T, r *http.Request, values values) {
}
r.ParseForm()
if !reflect.DeepEqual(want, r.Form) {
t.Errorf("Request parameters = %v, want %v", r.Form, want)
if got := r.Form; !reflect.DeepEqual(got, want) {
t.Errorf("Request parameters: %v, want %v", got, want)
}
}
func testHeader(t *testing.T, r *http.Request, header string, want string) {
if value := r.Header.Get(header); want != value {
t.Errorf("Header %s = %s, want: %s", header, value, want)
if got := r.Header.Get(header); got != want {
t.Errorf("Header.Get(%q) returned %s, want %s", header, got, want)
}
}
@@ -117,11 +117,10 @@ func testURLParseError(t *testing.T, err error) {
func testBody(t *testing.T, r *http.Request, want string) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("Unable to read body")
t.Errorf("Error reading request body: %v", err)
}
str := string(b)
if want != str {
t.Errorf("Body = %s, want: %s", str, want)
if got := string(b); got != want {
t.Errorf("request Body is %s, want %s", got, want)
}
}
@@ -156,11 +155,11 @@ func testJSONMarshal(t *testing.T, v interface{}, want string) {
func TestNewClient(t *testing.T) {
c := NewClient(nil)
if c.BaseURL.String() != defaultBaseURL {
t.Errorf("NewClient BaseURL = %v, want %v", c.BaseURL.String(), defaultBaseURL)
if got, want := c.BaseURL.String(), defaultBaseURL; got != want {
t.Errorf("NewClient BaseURL is %v, want %v", got, want)
}
if c.UserAgent != userAgent {
t.Errorf("NewClient UserAgent = %v, want %v", c.UserAgent, userAgent)
if got, want := c.UserAgent, userAgent; got != want {
t.Errorf("NewClient UserAgent is %v, want %v", got, want)
}
}
@@ -172,20 +171,19 @@ func TestNewRequest(t *testing.T) {
req, _ := c.NewRequest("GET", inURL, inBody)
// test that relative URL was expanded
if req.URL.String() != outURL {
t.Errorf("NewRequest(%v) URL = %v, want %v", inURL, req.URL, outURL)
if got, want := req.URL.String(), outURL; got != want {
t.Errorf("NewRequest(%q) URL is %v, want %v", inURL, got, want)
}
// test that body was JSON encoded
body, _ := ioutil.ReadAll(req.Body)
if string(body) != outBody {
t.Errorf("NewRequest(%v) Body = %v, want %v", inBody, string(body), outBody)
if got, want := string(body), outBody; got != want {
t.Errorf("NewRequest(%q) Body is %v, want %v", inBody, got, want)
}
// test that default user-agent is attached to the request
userAgent := req.Header.Get("User-Agent")
if c.UserAgent != userAgent {
t.Errorf("NewRequest() User-Agent = %v, want %v", userAgent, c.UserAgent)
if got, want := req.Header.Get("User-Agent"), c.UserAgent; got != want {
t.Errorf("NewRequest() User-Agent is %v, want %v", got, want)
}
}
@@ -211,6 +209,37 @@ func TestNewRequest_badURL(t *testing.T) {
testURLParseError(t, err)
}
// ensure that no User-Agent header is set if the client's UserAgent is empty.
// This caused a problem with Google's internal http client.
func TestNewRequest_emptyUserAgent(t *testing.T) {
c := NewClient(nil)
c.UserAgent = ""
req, err := c.NewRequest("GET", "/", nil)
if err != nil {
t.Fatalf("NewRequest returned unexpected error: %v", err)
}
if _, ok := req.Header["User-Agent"]; ok {
t.Fatal("constructed request contains unexpected User-Agent header")
}
}
// If a nil body is passed to github.NewRequest, make sure that nil is also
// passed to http.NewRequest. In most cases, passing an io.Reader that returns
// no content is fine, since there is no difference between an HTTP request
// body that is an empty string versus one that is not set at all. However in
// certain cases, intermediate systems may treat these differently resulting in
// subtle errors.
func TestNewRequest_emptyBody(t *testing.T) {
c := NewClient(nil)
req, err := c.NewRequest("GET", "/", nil)
if err != nil {
t.Fatalf("NewRequest returned unexpected error: %v", err)
}
if req.Body != nil {
t.Fatalf("constructed request contains a non-nil Body")
}
}
func TestResponse_populatePageValues(t *testing.T) {
r := http.Response{
Header: http.Header{
@@ -223,17 +252,17 @@ func TestResponse_populatePageValues(t *testing.T) {
}
response := newResponse(&r)
if want, got := 1, response.FirstPage; want != got {
t.Errorf("response.FirstPage: %v, want %v", want, got)
if got, want := response.FirstPage, 1; got != want {
t.Errorf("response.FirstPage: %v, want %v", got, want)
}
if want, got := 2, response.PrevPage; want != got {
t.Errorf("response.PrevPage: %v, want %v", want, got)
if got, want := response.PrevPage, 2; want != got {
t.Errorf("response.PrevPage: %v, want %v", got, want)
}
if want, got := 4, response.NextPage; want != got {
t.Errorf("response.NextPage: %v, want %v", want, got)
if got, want := response.NextPage, 4; want != got {
t.Errorf("response.NextPage: %v, want %v", got, want)
}
if want, got := 5, response.LastPage; want != got {
t.Errorf("response.LastPage: %v, want %v", want, got)
if got, want := response.LastPage, 5; want != got {
t.Errorf("response.LastPage: %v, want %v", got, want)
}
}
@@ -250,17 +279,17 @@ func TestResponse_populatePageValues_invalid(t *testing.T) {
}
response := newResponse(&r)
if want, got := 0, response.FirstPage; want != got {
t.Errorf("response.FirstPage: %v, want %v", want, got)
if got, want := response.FirstPage, 0; got != want {
t.Errorf("response.FirstPage: %v, want %v", got, want)
}
if want, got := 0, response.PrevPage; want != got {
t.Errorf("response.PrevPage: %v, want %v", want, got)
if got, want := response.PrevPage, 0; got != want {
t.Errorf("response.PrevPage: %v, want %v", got, want)
}
if want, got := 0, response.NextPage; want != got {
t.Errorf("response.NextPage: %v, want %v", want, got)
if got, want := response.NextPage, 0; got != want {
t.Errorf("response.NextPage: %v, want %v", got, want)
}
if want, got := 0, response.LastPage; want != got {
t.Errorf("response.LastPage: %v, want %v", want, got)
if got, want := response.LastPage, 0; got != want {
t.Errorf("response.LastPage: %v, want %v", got, want)
}
// more invalid URLs
@@ -271,8 +300,8 @@ func TestResponse_populatePageValues_invalid(t *testing.T) {
}
response = newResponse(&r)
if want, got := 0, response.FirstPage; want != got {
t.Errorf("response.FirstPage: %v, want %v", want, got)
if got, want := response.FirstPage, 0; got != want {
t.Errorf("response.FirstPage: %v, want %v", got, want)
}
}
@@ -349,13 +378,11 @@ func TestDo_rateLimit(t *testing.T) {
w.Header().Add(headerRateReset, "1372700873")
})
var want int
if want = 0; client.Rate.Limit != want {
t.Errorf("Client rate limit = %v, want %v", client.Rate.Limit, want)
if got, want := client.Rate.Limit, 0; got != want {
t.Errorf("Client rate limit = %v, want %v", got, want)
}
if want = 0; client.Rate.Limit != want {
t.Errorf("Client rate remaining = %v, got %v", client.Rate.Remaining, want)
if got, want := client.Rate.Limit, 0; got != want {
t.Errorf("Client rate remaining = %v, got %v", got, want)
}
if !client.Rate.Reset.IsZero() {
t.Errorf("Client rate reset not initialized to zero value")
@@ -364,11 +391,11 @@ func TestDo_rateLimit(t *testing.T) {
req, _ := client.NewRequest("GET", "/", nil)
client.Do(req, nil)
if want = 60; client.Rate.Limit != want {
t.Errorf("Client rate limit = %v, want %v", client.Rate.Limit, want)
if got, want := client.Rate.Limit, 60; got != want {
t.Errorf("Client rate limit = %v, want %v", got, want)
}
if want = 59; client.Rate.Remaining != want {
t.Errorf("Client rate remaining = %v, want %v", client.Rate.Remaining, want)
if got, want := client.Rate.Remaining, 59; got != want {
t.Errorf("Client rate remaining = %v, want %v", got, want)
}
reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC)
if client.Rate.Reset.UTC() != reset {
@@ -376,6 +403,7 @@ func TestDo_rateLimit(t *testing.T) {
}
}
// ensure rate limit is still parsed, even for error responses
func TestDo_rateLimit_errorResponse(t *testing.T) {
setup()
defer teardown()
@@ -387,16 +415,14 @@ func TestDo_rateLimit_errorResponse(t *testing.T) {
http.Error(w, "Bad Request", 400)
})
var want int
req, _ := client.NewRequest("GET", "/", nil)
client.Do(req, nil)
if want = 60; client.Rate.Limit != want {
t.Errorf("Client rate limit = %v, want %v", client.Rate.Limit, want)
if got, want := client.Rate.Limit, 60; got != want {
t.Errorf("Client rate limit = %v, want %v", got, want)
}
if want = 59; client.Rate.Remaining != want {
t.Errorf("Client rate remaining = %v, want %v", client.Rate.Remaining, want)
if got, want := client.Rate.Remaining, 59; got != want {
t.Errorf("Client rate remaining = %v, want %v", got, want)
}
reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC)
if client.Rate.Reset.UTC() != reset {
@@ -427,8 +453,7 @@ func TestCheckResponse(t *testing.T) {
}
}
// ensure that we properly handle API errors that do not contain a response
// body
// ensure that we properly handle API errors that do not contain a response body
func TestCheckResponse_noBody(t *testing.T) {
res := &http.Response{
Request: &http.Request{},

View File

@@ -15,7 +15,7 @@ type GitignoresService struct {
client *Client
}
// Represents a .gitignore file as returned by the GitHub API.
// Gitignore represents a .gitignore file as returned by the GitHub API.
type Gitignore struct {
Name *string `json:"name,omitempty"`
Source *string `json:"source,omitempty"`
@@ -25,7 +25,7 @@ func (g Gitignore) String() string {
return Stringify(g)
}
// Fetches a list of all available Gitignore templates.
// List all available Gitignore templates.
//
// http://developer.github.com/v3/gitignore/#listing-available-templates
func (s GitignoresService) List() ([]string, *Response, error) {
@@ -43,7 +43,7 @@ func (s GitignoresService) List() ([]string, *Response, error) {
return *availableTemplates, resp, err
}
// Fetches a Gitignore by name.
// Get a Gitignore by name.
//
// http://developer.github.com/v3/gitignore/#get-a-single-template
func (s GitignoresService) Get(name string) (*Gitignore, *Response, error) {

View File

@@ -34,6 +34,11 @@ type Issue struct {
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
Milestone *Milestone `json:"milestone,omitempty"`
PullRequestLinks *PullRequestLinks `json:"pull_request,omitempty"`
// TextMatches is only populated from search results that request text matches
// See: search.go and https://developer.github.com/v3/search/#text-match-metadata
TextMatches []TextMatch `json:"text_matches,omitempty"`
}
func (i Issue) String() string {
@@ -80,6 +85,15 @@ type IssueListOptions struct {
ListOptions
}
// PullRequestLinks object is added to the Issue object when it's an issue included
// in the IssueCommentEvent webhook payload, if the webhooks is fired by a comment on a PR
type PullRequestLinks struct {
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
DiffURL *string `json:"diff_url,omitempty"`
PatchURL *string `json:"patch_url,omitempty"`
}
// List the issues for the authenticated user. If all is true, list issues
// across all the user's visible repositories including owned, member, and
// organization repositories; if false, list only owned and member

View File

@@ -17,6 +17,9 @@ type IssueComment struct {
User *User `json:"user,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
IssueURL *string `json:"issue_url,omitempty"`
}
func (i IssueComment) String() string {
@@ -34,6 +37,8 @@ type IssueListCommentsOptions struct {
// Since filters comments by time.
Since time.Time `url:"since,omitempty"`
ListOptions
}
// ListComments lists all comments on the specified issue. Specifying an issue

View File

@@ -24,12 +24,16 @@ func TestIssuesService_ListComments_allIssues(t *testing.T) {
"sort": "updated",
"direction": "desc",
"since": "2002-02-10T15:30:00Z",
"page": "2",
})
fmt.Fprint(w, `[{"id":1}]`)
})
opt := &IssueListCommentsOptions{"updated", "desc",
time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
opt := &IssueListCommentsOptions{
Sort: "updated",
Direction: "desc",
Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
ListOptions: ListOptions{Page: 2},
}
comments, _, err := client.Issues.ListComments("o", "r", 0, opt)
if err != nil {

View File

@@ -7,6 +7,8 @@ package github
import (
"bytes"
"fmt"
"net/url"
)
// MarkdownOptions specifies optional parameters to the Markdown method.
@@ -48,7 +50,7 @@ func (c *Client) Markdown(text string, opt *MarkdownOptions) (string, *Response,
}
}
req, err := c.NewRequest("POST", "/markdown", request)
req, err := c.NewRequest("POST", "markdown", request)
if err != nil {
return "", nil, err
}
@@ -66,7 +68,7 @@ func (c *Client) Markdown(text string, opt *MarkdownOptions) (string, *Response,
//
// GitHub API docs: https://developer.github.com/v3/emojis/
func (c *Client) ListEmojis() (map[string]string, *Response, error) {
req, err := c.NewRequest("GET", "/emojis", nil)
req, err := c.NewRequest("GET", "emojis", nil)
if err != nil {
return nil, nil, err
}
@@ -104,7 +106,7 @@ type APIMeta struct {
//
// GitHub API docs: https://developer.github.com/v3/meta/
func (c *Client) APIMeta() (*APIMeta, *Response, error) {
req, err := c.NewRequest("GET", "/meta", nil)
req, err := c.NewRequest("GET", "meta", nil)
if err != nil {
return nil, nil, err
}
@@ -117,3 +119,43 @@ func (c *Client) APIMeta() (*APIMeta, *Response, error) {
return meta, resp, nil
}
// Octocat returns an ASCII art octocat with the specified message in a speech
// bubble. If message is empty, a random zen phrase is used.
func (c *Client) Octocat(message string) (string, *Response, error) {
u := "octocat"
if message != "" {
u = fmt.Sprintf("%s?s=%s", u, url.QueryEscape(message))
}
req, err := c.NewRequest("GET", u, nil)
if err != nil {
return "", nil, err
}
buf := new(bytes.Buffer)
resp, err := c.Do(req, buf)
if err != nil {
return "", resp, err
}
return buf.String(), resp, nil
}
// Zen returns a random line from The Zen of GitHub.
//
// see also: http://warpspire.com/posts/taste/
func (c *Client) Zen() (string, *Response, error) {
req, err := c.NewRequest("GET", "zen", nil)
if err != nil {
return "", nil, err
}
buf := new(bytes.Buffer)
resp, err := c.Do(req, buf)
if err != nil {
return "", resp, err
}
return buf.String(), resp, nil
}

View File

@@ -89,3 +89,49 @@ func TestAPIMeta(t *testing.T) {
t.Errorf("APIMeta returned %+v, want %+v", meta, want)
}
}
func TestOctocat(t *testing.T) {
setup()
defer teardown()
input := "input"
output := "sample text"
mux.HandleFunc("/octocat", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"s": input})
w.Header().Set("Content-Type", "application/octocat-stream")
fmt.Fprint(w, output)
})
got, _, err := client.Octocat(input)
if err != nil {
t.Errorf("Octocat returned error: %v", err)
}
if want := output; got != want {
t.Errorf("Octocat returned %+v, want %+v", got, want)
}
}
func TestZen(t *testing.T) {
setup()
defer teardown()
output := "sample text"
mux.HandleFunc("/zen", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
w.Header().Set("Content-Type", "text/plain;charset=utf-8")
fmt.Fprint(w, output)
})
got, _, err := client.Zen()
if err != nil {
t.Errorf("Zen returned error: %v", err)
}
if want := output; got != want {
t.Errorf("Zen returned %+v, want %+v", got, want)
}
}

View File

@@ -7,6 +7,31 @@ package github
import "fmt"
// Membership represents the status of a user's membership in an organization or team.
type Membership struct {
URL *string `json:"url,omitempty"`
// State is the user's status within the organization or team.
// Possible values are: "active", "pending"
State *string `json:"state,omitempty"`
// TODO(willnorris): add docs
Role *string `json:"role,omitempty"`
// For organization membership, the API URL of the organization.
OrganizationURL *string `json:"organization_url,omitempty"`
// For organization membership, the organization the membership is for.
Organization *Organization `json:"organization,omitempty"`
// For organization membership, the user the membership is for.
User *User `json:"user,omitempty"`
}
func (m Membership) String() string {
return Stringify(m)
}
// ListMembersOptions specifies optional parameters to the
// OrganizationsService.ListMembers method.
type ListMembersOptions struct {
@@ -120,3 +145,86 @@ func (s *OrganizationsService) ConcealMembership(org, user string) (*Response, e
return s.client.Do(req, nil)
}
// ListOrgMembershipsOptions specifies optional parameters to the
// OrganizationsService.ListOrgMemberships method.
type ListOrgMembershipsOptions struct {
// Filter memberships to include only those withe the specified state.
// Possible values are: "active", "pending".
State string `url:"state,omitempty"`
ListOptions
}
// ListOrgMemberships lists the organization memberships for the authenticated user.
//
// GitHub API docs: https://developer.github.com/v3/orgs/members/#list-your-organization-memberships
func (s *OrganizationsService) ListOrgMemberships(opt *ListOrgMembershipsOptions) ([]Membership, *Response, error) {
u := "user/memberships/orgs"
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
var memberships []Membership
resp, err := s.client.Do(req, &memberships)
if err != nil {
return nil, resp, err
}
return memberships, resp, err
}
// GetOrgMembership gets the membership for the authenticated user for the
// specified organization.
//
// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-your-organization-membership
func (s *OrganizationsService) GetOrgMembership(org string) (*Membership, *Response, error) {
u := fmt.Sprintf("user/memberships/orgs/%v", org)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
membership := new(Membership)
resp, err := s.client.Do(req, membership)
if err != nil {
return nil, resp, err
}
return membership, resp, err
}
// EditOrgMembership edits the membership for the authenticated user for the
// specified organization.
//
// GitHub API docs: https://developer.github.com/v3/orgs/members/#edit-your-organization-membership
func (s *OrganizationsService) EditOrgMembership(org string, membership *Membership) (*Membership, *Response, error) {
u := fmt.Sprintf("user/memberships/orgs/%v", org)
req, err := s.client.NewRequest("PATCH", u, membership)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
m := new(Membership)
resp, err := s.client.Do(req, m)
if err != nil {
return nil, resp, err
}
return m, resp, err
}

View File

@@ -6,6 +6,7 @@
package github
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
@@ -209,3 +210,83 @@ func TestOrganizationsService_RemoveMember_invalidOrg(t *testing.T) {
_, err := client.Organizations.RemoveMember("%", "u")
testURLParseError(t, err)
}
func TestOrganizationsService_ListOrgMemberships(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/user/memberships/orgs", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
testFormValues(t, r, values{
"state": "active",
"page": "2",
})
fmt.Fprint(w, `[{"url":"u"}]`)
})
opt := &ListOrgMembershipsOptions{
State: "active",
ListOptions: ListOptions{Page: 2},
}
memberships, _, err := client.Organizations.ListOrgMemberships(opt)
if err != nil {
t.Errorf("Organizations.ListOrgMemberships returned error: %v", err)
}
want := []Membership{{URL: String("u")}}
if !reflect.DeepEqual(memberships, want) {
t.Errorf("Organizations.ListOrgMemberships returned %+v, want %+v", memberships, want)
}
}
func TestOrganizationsService_GetOrgMembership(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
fmt.Fprint(w, `{"url":"u"}`)
})
membership, _, err := client.Organizations.GetOrgMembership("o")
if err != nil {
t.Errorf("Organizations.GetOrgMembership returned error: %v", err)
}
want := &Membership{URL: String("u")}
if !reflect.DeepEqual(membership, want) {
t.Errorf("Organizations.GetOrgMembership returned %+v, want %+v", membership, want)
}
}
func TestOrganizationsService_EditOrgMembership(t *testing.T) {
setup()
defer teardown()
input := &Membership{State: String("active")}
mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) {
v := new(Membership)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
fmt.Fprint(w, `{"url":"u"}`)
})
membership, _, err := client.Organizations.EditOrgMembership("o", input)
if err != nil {
t.Errorf("Organizations.EditOrgMembership returned error: %v", err)
}
want := &Membership{URL: String("u")}
if !reflect.DeepEqual(membership, want) {
t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want)
}
}

View File

@@ -17,6 +17,7 @@ type Team struct {
Permission *string `json:"permission,omitempty"`
MembersCount *int `json:"members_count,omitempty"`
ReposCount *int `json:"repos_count,omitempty"`
Organization *Organization `json:"organization,omitempty"`
}
func (t Team) String() string {
@@ -251,3 +252,101 @@ func (s *OrganizationsService) RemoveTeamRepo(team int, owner string, repo strin
return s.client.Do(req, nil)
}
// ListUserTeams lists a user's teams
// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams
func (s *OrganizationsService) ListUserTeams(opt *ListOptions) ([]Team, *Response, error) {
u := "user/teams"
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
teams := new([]Team)
resp, err := s.client.Do(req, teams)
if err != nil {
return nil, resp, err
}
return *teams, resp, err
}
// GetTeamMembership returns the membership status for a user in a team.
//
// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership
func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Membership, *Response, error) {
u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
t := new(Membership)
resp, err := s.client.Do(req, t)
if err != nil {
return nil, resp, err
}
return t, resp, err
}
// AddTeamMembership adds or invites a user to a team.
//
// In order to add a membership between a user and a team, the authenticated
// user must have 'admin' permissions to the team or be an owner of the
// organization that the team is associated with.
//
// If the user is already a part of the team's organization (meaning they're on
// at least one other team in the organization), this endpoint will add the
// user to the team.
//
// If the user is completely unaffiliated with the team's organization (meaning
// they're on none of the organization's teams), this endpoint will send an
// invitation to the user via email. This newly-created membership will be in
// the "pending" state until the user accepts the invitation, at which point
// the membership will transition to the "active" state and the user will be
// added as a member of the team.
//
// GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-membership
func (s *OrganizationsService) AddTeamMembership(team int, user string) (*Membership, *Response, error) {
u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
t := new(Membership)
resp, err := s.client.Do(req, t)
if err != nil {
return nil, resp, err
}
return t, resp, err
}
// RemoveTeamMembership removes a user from a team.
//
// GitHub API docs: https://developer.github.com/v3/orgs/teams/#remove-team-membership
func (s *OrganizationsService) RemoveTeamMembership(team int, user string) (*Response, error) {
u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
return s.client.Do(req, nil)
}

View File

@@ -435,3 +435,83 @@ func TestOrganizationsService_RemoveTeamRepo_invalidOwner(t *testing.T) {
_, err := client.Organizations.RemoveTeamRepo(1, "%", "r")
testURLParseError(t, err)
}
func TestOrganizationsService_GetTeamMembership(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
fmt.Fprint(w, `{"url":"u", "state":"active"}`)
})
membership, _, err := client.Organizations.GetTeamMembership(1, "u")
if err != nil {
t.Errorf("Organizations.GetTeamMembership returned error: %v", err)
}
want := &Membership{URL: String("u"), State: String("active")}
if !reflect.DeepEqual(membership, want) {
t.Errorf("Organizations.GetTeamMembership returned %+v, want %+v", membership, want)
}
}
func TestOrganizationsService_AddTeamMembership(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
fmt.Fprint(w, `{"url":"u", "state":"pending"}`)
})
membership, _, err := client.Organizations.AddTeamMembership(1, "u")
if err != nil {
t.Errorf("Organizations.AddTeamMembership returned error: %v", err)
}
want := &Membership{URL: String("u"), State: String("pending")}
if !reflect.DeepEqual(membership, want) {
t.Errorf("Organizations.AddTeamMembership returned %+v, want %+v", membership, want)
}
}
func TestOrganizationsService_RemoveTeamMembership(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Organizations.RemoveTeamMembership(1, "u")
if err != nil {
t.Errorf("Organizations.RemoveTeamMembership returned error: %v", err)
}
}
func TestOrganizationsService_ListUserTeams(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/user/teams", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"page": "1"})
fmt.Fprint(w, `[{"id":1}]`)
})
opt := &ListOptions{Page: 1}
teams, _, err := client.Organizations.ListUserTeams(opt)
if err != nil {
t.Errorf("Organizations.ListUserTeams returned error: %v", err)
}
want := []Team{{ID: Int(1)}}
if !reflect.DeepEqual(teams, want) {
t.Errorf("Organizations.ListUserTeams returned %+v, want %+v", teams, want)
}
}

View File

@@ -37,16 +37,28 @@ type PullRequest struct {
Additions *int `json:"additions,omitempty"`
Deletions *int `json:"deletions,omitempty"`
ChangedFiles *int `json:"changed_files,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
IssueURL *string `json:"issue_url,omitempty"`
StatusesURL *string `json:"statuses_url,omitempty"`
// TODO(willnorris): add head and base once we have a Commit struct defined somewhere
Head *PullRequestBranch `json:"head,omitempty"`
Base *PullRequestBranch `json:"base,omitempty"`
}
func (p PullRequest) String() string {
return Stringify(p)
}
// PullRequestBranch represents a base or head branch in a GitHub pull request.
type PullRequestBranch struct {
Label *string `json:"label,omitempty"`
Ref *string `json:"ref,omitempty"`
SHA *string `json:"sha,omitempty"`
Repo *Repository `json:"repo,omitempty"`
User *User `json:"user,omitempty"`
}
// PullRequestListOptions specifies the optional parameters to the
// PullRequestsService.List method.
type PullRequestListOptions struct {
@@ -107,10 +119,19 @@ func (s *PullRequestsService) Get(owner string, repo string, number int) (*PullR
return pull, resp, err
}
// NewPullRequest represents a new pull request to be created.
type NewPullRequest struct {
Title *string `json:"title,omitempty"`
Head *string `json:"head,omitempty"`
Base *string `json:"base,omitempty"`
Body *string `json:"body,omitempty"`
Issue *int `json:"issue,omitempty"`
}
// Create a new pull request on the specified repository.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#create-a-pull-request
func (s *PullRequestsService) Create(owner string, repo string, pull *PullRequest) (*PullRequest, *Response, error) {
func (s *PullRequestsService) Create(owner string, repo string, pull *NewPullRequest) (*PullRequest, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
req, err := s.client.NewRequest("POST", u, pull)
if err != nil {
@@ -148,7 +169,7 @@ func (s *PullRequestsService) Edit(owner string, repo string, number int, pull *
// ListCommits lists the commits in a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
func (s *PullRequestsService) ListCommits(owner string, repo string, number int, opt *ListOptions) (*[]Commit, *Response, error) {
func (s *PullRequestsService) ListCommits(owner string, repo string, number int, opt *ListOptions) ([]RepositoryCommit, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/%d/commits", owner, repo, number)
u, err := addOptions(u, opt)
if err != nil {
@@ -160,19 +181,19 @@ func (s *PullRequestsService) ListCommits(owner string, repo string, number int,
return nil, nil, err
}
commits := new([]Commit)
commits := new([]RepositoryCommit)
resp, err := s.client.Do(req, commits)
if err != nil {
return nil, resp, err
}
return commits, resp, err
return *commits, resp, err
}
// ListFiles lists the files in a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests-files
func (s *PullRequestsService) ListFiles(owner string, repo string, number int, opt *ListOptions) (*[]CommitFile, *Response, error) {
func (s *PullRequestsService) ListFiles(owner string, repo string, number int, opt *ListOptions) ([]CommitFile, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/%d/files", owner, repo, number)
u, err := addOptions(u, opt)
if err != nil {
@@ -190,7 +211,7 @@ func (s *PullRequestsService) ListFiles(owner string, repo string, number int, o
return nil, resp, err
}
return commitFiles, resp, err
return *commitFiles, resp, err
}
// IsMerged checks if a pull request has been merged.

View File

@@ -37,6 +37,8 @@ type PullRequestListCommentsOptions struct {
// Since filters comments by time.
Since time.Time `url:"since,omitempty"`
ListOptions
}
// ListComments lists all comments on the specified pull request. Specifying a

View File

@@ -24,12 +24,16 @@ func TestPullRequestsService_ListComments_allPulls(t *testing.T) {
"sort": "updated",
"direction": "desc",
"since": "2002-02-10T15:30:00Z",
"page": "2",
})
fmt.Fprint(w, `[{"id":1}]`)
})
opt := &PullRequestListCommentsOptions{"updated", "desc",
time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
opt := &PullRequestListCommentsOptions{
Sort: "updated",
Direction: "desc",
Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
ListOptions: ListOptions{Page: 2},
}
pulls, _, err := client.PullRequests.ListComments("o", "r", 0, opt)

View File

@@ -67,6 +67,37 @@ func TestPullRequestsService_Get(t *testing.T) {
}
}
func TestPullRequestsService_Get_headAndBase(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"number":1,"head":{"ref":"r2","repo":{"id":2}},"base":{"ref":"r1","repo":{"id":1}}}`)
})
pull, _, err := client.PullRequests.Get("o", "r", 1)
if err != nil {
t.Errorf("PullRequests.Get returned error: %v", err)
}
want := &PullRequest{
Number: Int(1),
Head: &PullRequestBranch{
Ref: String("r2"),
Repo: &Repository{ID: Int(2)},
},
Base: &PullRequestBranch{
Ref: String("r1"),
Repo: &Repository{ID: Int(1)},
},
}
if !reflect.DeepEqual(pull, want) {
t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want)
}
}
func TestPullRequestsService_Get_invalidOwner(t *testing.T) {
_, _, err := client.PullRequests.Get("%", "r", 1)
testURLParseError(t, err)
@@ -76,10 +107,10 @@ func TestPullRequestsService_Create(t *testing.T) {
setup()
defer teardown()
input := &PullRequest{Title: String("t")}
input := &NewPullRequest{Title: String("t")}
mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) {
v := new(PullRequest)
v := new(NewPullRequest)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "POST")
@@ -174,19 +205,19 @@ func TestPullRequestsService_ListCommits(t *testing.T) {
t.Errorf("PullRequests.ListCommits returned error: %v", err)
}
want := &[]Commit{
Commit{
want := []RepositoryCommit{
{
SHA: String("3"),
Parents: []Commit{
Commit{
{
SHA: String("2"),
},
},
},
Commit{
{
SHA: String("2"),
Parents: []Commit{
Commit{
{
SHA: String("1"),
},
},
@@ -233,8 +264,8 @@ func TestPullRequestsService_ListFiles(t *testing.T) {
t.Errorf("PullRequests.ListFiles returned error: %v", err)
}
want := &[]CommitFile{
CommitFile{
want := []CommitFile{
{
SHA: String("6dcb09b5b57875f334f61aebed695e2e4193db5e"),
Filename: String("file1.txt"),
Additions: Int(103),
@@ -243,7 +274,7 @@ func TestPullRequestsService_ListFiles(t *testing.T) {
Status: String("added"),
Patch: String("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"),
},
CommitFile{
{
SHA: String("f61aebed695e2e4193db5e6dcb09b5b57875f334"),
Filename: String("file2.txt"),
Additions: Int(5),

View File

@@ -43,6 +43,7 @@ type Repository struct {
SubscribersCount *int `json:"subscribers_count,omitempty"`
WatchersCount *int `json:"watchers_count,omitempty"`
Size *int `json:"size,omitempty"`
AutoInit *bool `json:"auto_init,omitempty"`
Parent *Repository `json:"parent,omitempty"`
Source *Repository `json:"source,omitempty"`
Organization *Organization `json:"organization,omitempty"`
@@ -53,6 +54,8 @@ type Repository struct {
HasIssues *bool `json:"has_issues"`
HasWiki *bool `json:"has_wiki"`
HasDownloads *bool `json:"has_downloads"`
// Creating an organization repository. Required for non-owners.
TeamID *int `json:"team_id"`
// API URLs
URL *string `json:"url,omitempty"`
@@ -91,6 +94,10 @@ type Repository struct {
TagsURL *string `json:"tags_url,omitempty"`
TreesURL *string `json:"trees_url,omitempty"`
TeamsURL *string `json:"teams_url,omitempty"`
// TextMatches is only populated from search results that request text matches
// See: search.go and https://developer.github.com/v3/search/#text-match-metadata
TextMatches []TextMatch `json:"text_matches,omitempty"`
}
func (r Repository) String() string {

View File

@@ -20,6 +20,7 @@ type RepositoryCommit struct {
Committer *User `json:"committer,omitempty"`
Parents []Commit `json:"parents,omitempty"`
Message *string `json:"message,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
// Details about how many changes were made in this commit. Only filled in during GetCommit!
Stats *CommitStats `json:"stats,omitempty"`
@@ -94,6 +95,8 @@ type CommitsListOptions struct {
// Until when should Commits be included in the response.
Until time.Time `url:"until,omitempty"`
ListOptions
}
// ListCommits lists the commits of a repository.

View File

@@ -58,11 +58,11 @@ func (r RepositoryContent) String() string {
}
// Decode decodes the file content if it is base64 encoded.
func (c *RepositoryContent) Decode() ([]byte, error) {
if *c.Encoding != "base64" {
return nil, errors.New("Cannot decode non-base64")
func (r *RepositoryContent) Decode() ([]byte, error) {
if *r.Encoding != "base64" {
return nil, errors.New("cannot decode non-base64")
}
o, err := base64.StdEncoding.DecodeString(*c.Content)
o, err := base64.StdEncoding.DecodeString(*r.Content)
if err != nil {
return nil, err
}
@@ -122,7 +122,7 @@ func (s *RepositoriesService) GetContents(owner, repo, path string, opt *Reposit
if directoryUnmarshalError == nil {
return nil, directoryContent, resp, directoryUnmarshalError
}
return nil, nil, resp, fmt.Errorf("Unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError)
return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError)
}
// CreateFile creates a new file in a repository at the given path and returns

View File

@@ -0,0 +1,174 @@
// Copyright 2014 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"encoding/json"
"fmt"
)
// Deployment represents a deployment in a repo
type Deployment struct {
URL *string `json:"url,omitempty"`
ID *int `json:"id,omitempty"`
SHA *string `json:"sha,omitempty"`
Ref *string `json:"ref,omitempty"`
Task *string `json:"task,omitempty"`
Payload json.RawMessage `json:"payload,omitempty"`
Environment *string `json:"environment,omitempty"`
Description *string `json:"description,omitempty"`
Creator *User `json:"creator,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"pushed_at,omitempty"`
}
// DeploymentRequest represents a deployment request
type DeploymentRequest struct {
Ref *string `json:"ref,omitempty"`
Task *string `json:"task,omitempty"`
AutoMerge *bool `json:"auto_merge,omitempty"`
RequiredContexts []string `json:"required_contexts,omitempty"`
Payload *string `json:"payload,omitempty"`
Environment *string `json:"environment,omitempty"`
Description *string `json:"description,omitempty"`
}
// DeploymentsListOptions specifies the optional parameters to the
// RepositoriesService.ListDeployments method.
type DeploymentsListOptions struct {
// SHA of the Deployment.
SHA string `url:"sha,omitempty"`
// List deployments for a given ref.
Ref string `url:"ref,omitempty"`
// List deployments for a given task.
Task string `url:"task,omitempty"`
// List deployments for a given environment.
Environment string `url:"environment,omitempty"`
ListOptions
}
// ListDeployments lists the deployments of a repository.
//
// GitHub API docs: https://developer.github.com/v3/repos/deployments/#list-deployments
func (s *RepositoriesService) ListDeployments(owner, repo string, opt *DeploymentsListOptions) ([]Deployment, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/deployments", owner, repo)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeDeploymentPreview)
deployments := new([]Deployment)
resp, err := s.client.Do(req, deployments)
if err != nil {
return nil, resp, err
}
return *deployments, resp, err
}
// CreateDeployment creates a new deployment for a repository.
//
// GitHub API docs: https://developer.github.com/v3/repos/deployments/#create-a-deployment
func (s *RepositoriesService) CreateDeployment(owner, repo string, request *DeploymentRequest) (*Deployment, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/deployments", owner, repo)
req, err := s.client.NewRequest("POST", u, request)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeDeploymentPreview)
d := new(Deployment)
resp, err := s.client.Do(req, d)
if err != nil {
return nil, resp, err
}
return d, resp, err
}
// DeploymentStatus represents the status of a
// particular deployment.
type DeploymentStatus struct {
ID *int `json:"id,omitempty"`
State *string `json:"state,omitempty"`
Creator *User `json:"creator,omitempty"`
Description *string `json:"description,omitempty"`
TargetURL *string `json:"target_url,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"pushed_at,omitempty"`
}
// DeploymentStatusRequest represents a deployment request
type DeploymentStatusRequest struct {
State *string `json:"state,omitempty"`
TargetURL *string `json:"target_url,omitempty"`
Description *string `json:"description,omitempty"`
}
// ListDeploymentStatuses lists the statuses of a given deployment of a repository.
//
// GitHub API docs: https://developer.github.com/v3/repos/deployments/#list-deployment-statuses
func (s *RepositoriesService) ListDeploymentStatuses(owner, repo string, deployment int, opt *ListOptions) ([]DeploymentStatus, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses", owner, repo, deployment)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeDeploymentPreview)
statuses := new([]DeploymentStatus)
resp, err := s.client.Do(req, statuses)
if err != nil {
return nil, resp, err
}
return *statuses, resp, err
}
// CreateDeploymentStatus creates a new status for a deployment.
//
// GitHub API docs: https://developer.github.com/v3/repos/deployments/#create-a-deployment-status
func (s *RepositoriesService) CreateDeploymentStatus(owner, repo string, deployment int, request *DeploymentStatusRequest) (*DeploymentStatus, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses", owner, repo, deployment)
req, err := s.client.NewRequest("POST", u, request)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeDeploymentPreview)
d := new(DeploymentStatus)
resp, err := s.client.Do(req, d)
if err != nil {
return nil, resp, err
}
return d, resp, err
}

View File

@@ -0,0 +1,87 @@
// Copyright 2014 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestRepositoriesService_ListDeployments(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/deployments", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"environment": "test"})
fmt.Fprint(w, `[{"id":1}, {"id":2}]`)
})
opt := &DeploymentsListOptions{Environment: "test"}
deployments, _, err := client.Repositories.ListDeployments("o", "r", opt)
if err != nil {
t.Errorf("Repositories.ListDeployments returned error: %v", err)
}
want := []Deployment{{ID: Int(1)}, {ID: Int(2)}}
if !reflect.DeepEqual(deployments, want) {
t.Errorf("Repositories.ListDeployments returned %+v, want %+v", deployments, want)
}
}
func TestRepositoriesService_CreateDeployment(t *testing.T) {
setup()
defer teardown()
input := &DeploymentRequest{Ref: String("1111"), Task: String("deploy")}
mux.HandleFunc("/repos/o/r/deployments", func(w http.ResponseWriter, r *http.Request) {
v := new(DeploymentRequest)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "POST")
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
fmt.Fprint(w, `{"ref": "1111", "task": "deploy"}`)
})
deployment, _, err := client.Repositories.CreateDeployment("o", "r", input)
if err != nil {
t.Errorf("Repositories.CreateDeployment returned error: %v", err)
}
want := &Deployment{Ref: String("1111"), Task: String("deploy")}
if !reflect.DeepEqual(deployment, want) {
t.Errorf("Repositories.CreateDeployment returned %+v, want %+v", deployment, want)
}
}
func TestRepositoriesService_ListDeploymentStatuses(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/deployments/1/statuses", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"page": "2"})
fmt.Fprint(w, `[{"id":1}, {"id":2}]`)
})
opt := &ListOptions{Page: 2}
statutses, _, err := client.Repositories.ListDeploymentStatuses("o", "r", 1, opt)
if err != nil {
t.Errorf("Repositories.ListDeploymentStatuses returned error: %v", err)
}
want := []DeploymentStatus{{ID: Int(1)}, {ID: Int(2)}}
if !reflect.DeepEqual(statutses, want) {
t.Errorf("Repositories.ListDeploymentStatuses returned %+v, want %+v", statutses, want)
}
}

View File

@@ -13,6 +13,8 @@ type RepositoryListForksOptions struct {
// How to sort the forks list. Possible values are: newest, oldest,
// watchers. Default is "newest".
Sort string `url:"sort,omitempty"`
ListOptions
}
// ListForks lists the forks of the specified repository.

View File

@@ -18,11 +18,17 @@ func TestRepositoriesService_ListForks(t *testing.T) {
mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"sort": "newest"})
testFormValues(t, r, values{
"sort": "newest",
"page": "3",
})
fmt.Fprint(w, `[{"id":1},{"id":2}]`)
})
opt := &RepositoryListForksOptions{Sort: "newest"}
opt := &RepositoryListForksOptions{
Sort: "newest",
ListOptions: ListOptions{Page: 3},
}
repos, _, err := client.Repositories.ListForks("o", "r", opt)
if err != nil {
t.Errorf("Repositories.ListForks returned error: %v", err)

View File

@@ -27,7 +27,10 @@ type RepositoryRelease struct {
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
AssetsURL *string `json:"assets_url,omitempty"`
Assets []ReleaseAsset `json:"assets,omitempty"`
UploadURL *string `json:"upload_url,omitempty"`
ZipballURL *string `json:"zipball_url,omitempty"`
TarballURL *string `json:"tarball_url,omitempty"`
}
func (r RepositoryRelease) String() string {
@@ -46,6 +49,8 @@ type ReleaseAsset struct {
DownloadCount *int `json:"download_count,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
BrowserDownloadURL *string `json:"browser_download_url,omitempty"`
Uploader *User `json:"uploader,omitempty"`
}
func (r ReleaseAsset) String() string {
@@ -235,7 +240,7 @@ func (s *RepositoriesService) UploadReleaseAsset(owner, repo string, id int, opt
return nil, nil, err
}
if stat.IsDir() {
return nil, nil, errors.New("The asset to upload can't be a directory")
return nil, nil, errors.New("the asset to upload can't be a directory")
}
mediaType := mime.TypeByExtension(filepath.Ext(file.Name()))

View File

@@ -116,7 +116,7 @@ func (s *RepositoriesService) ListCodeFrequency(owner, repo string) ([]WeeklySta
resp, err := s.client.Do(req, &weeks)
// convert int slices into WeeklyStats
stats := make([]WeeklyStats, 0)
var stats []WeeklyStats
for _, week := range weeks {
if len(week) != 3 {
continue
@@ -197,7 +197,7 @@ func (s *RepositoriesService) ListPunchCard(owner, repo string) ([]PunchCard, *R
resp, err := s.client.Do(req, &results)
// convert int slices into Punchcards
cards := make([]PunchCard, 0)
var cards []PunchCard
for _, result := range results {
if len(result) != 3 {
continue

View File

@@ -46,7 +46,7 @@ func TestRepositoriesService_ListContributorsStats(t *testing.T) {
}
want := []ContributorStats{
ContributorStats{
{
Author: &Contributor{
ID: Int(1),
},

View File

@@ -13,6 +13,7 @@ import (
// RepoStatus represents the status of a repository at a particular reference.
type RepoStatus struct {
ID *int `json:"id,omitempty"`
URL *string `json:"url,omitempty"`
// State is the current state of the repository. Possible values are:
// pending, success, error, or failure.
@@ -42,7 +43,7 @@ func (r RepoStatus) String() string {
//
// GitHub API docs: http://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
func (s *RepositoriesService) ListStatuses(owner, repo, ref string, opt *ListOptions) ([]RepoStatus, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/statuses/%v", owner, repo, ref)
u := fmt.Sprintf("repos/%v/%v/commits/%v/statuses", owner, repo, ref)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
@@ -73,11 +74,55 @@ func (s *RepositoriesService) CreateStatus(owner, repo, ref string, status *Repo
return nil, nil, err
}
statuses := new(RepoStatus)
resp, err := s.client.Do(req, statuses)
repoStatus := new(RepoStatus)
resp, err := s.client.Do(req, repoStatus)
if err != nil {
return nil, resp, err
}
return statuses, resp, err
return repoStatus, resp, err
}
// CombinedStatus represents the combined status of a repository at a particular reference.
type CombinedStatus struct {
// State is the combined state of the repository. Possible values are:
// failture, pending, or success.
State *string `json:"state,omitempty"`
Name *string `json:"name,omitempty"`
SHA *string `json:"sha,omitempty"`
TotalCount *int `json:"total_count,omitempty"`
Statuses []RepoStatus `json:"statuses,omitempty"`
CommitURL *string `json:"commit_url,omitempty"`
RepositoryURL *string `json:"repository_url,omitempty"`
}
func (s CombinedStatus) String() string {
return Stringify(s)
}
// GetCombinedStatus returns the combined status of a repository at the specified
// reference. ref can be a SHA, a branch name, or a tag name.
//
// GitHub API docs: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
func (s *RepositoriesService) GetCombinedStatus(owner, repo, ref string, opt *ListOptions) (*CombinedStatus, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/commits/%v/status", owner, repo, ref)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
status := new(CombinedStatus)
resp, err := s.client.Do(req, status)
if err != nil {
return nil, resp, err
}
return status, resp, err
}

View File

@@ -17,7 +17,7 @@ func TestRepositoriesService_ListStatuses(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/statuses/r", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/repos/o/r/commits/r/statuses", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"page": "2"})
fmt.Fprint(w, `[{"id":1}]`)
@@ -72,3 +72,25 @@ func TestRepositoriesService_CreateStatus_invalidOwner(t *testing.T) {
_, _, err := client.Repositories.CreateStatus("%", "r", "r", nil)
testURLParseError(t, err)
}
func TestRepositoriesService_GetCombinedStatus(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/commits/r/status", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"page": "2"})
fmt.Fprint(w, `{"state":"success", "statuses":[{"id":1}]}`)
})
opt := &ListOptions{Page: 2}
status, _, err := client.Repositories.GetCombinedStatus("o", "r", "r", opt)
if err != nil {
t.Errorf("Repositories.GetCombinedStatus returned error: %v", err)
}
want := &CombinedStatus{State: String("success"), Statuses: []RepoStatus{{ID: Int(1)}}}
if !reflect.DeepEqual(status, want) {
t.Errorf("Repositories.GetCombinedStatus returned %+v, want %+v", status, want)
}
}

View File

@@ -34,6 +34,9 @@ type SearchOptions struct {
// desc. Default is desc.
Order string `url:"order,omitempty"`
// Whether to retrieve text match metadata with a query
TextMatch bool `url:"-"`
ListOptions
}
@@ -82,6 +85,25 @@ func (s *SearchService) Users(query string, opt *SearchOptions) (*UsersSearchRes
return result, resp, err
}
// Match represents a single text match.
type Match struct {
Text *string `json:"text,omitempty"`
Indices []int `json:"indices,omitempty"`
}
// TextMatch represents a text match for a SearchResult
type TextMatch struct {
ObjectURL *string `json:"object_url,omitempty"`
ObjectType *string `json:"object_type,omitempty"`
Property *string `json:"property,omitempty"`
Fragment *string `json:"fragment,omitempty"`
Matches []Match `json:"matches,omitempty"`
}
func (tm TextMatch) String() string {
return Stringify(tm)
}
// CodeSearchResult represents the result of an code search.
type CodeSearchResult struct {
Total *int `json:"total_count,omitempty"`
@@ -95,6 +117,7 @@ type CodeResult struct {
SHA *string `json:"sha,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
Repository *Repository `json:"repository,omitempty"`
TextMatches []TextMatch `json:"text_matches,omitempty"`
}
func (c CodeResult) String() string {
@@ -125,5 +148,11 @@ func (s *SearchService) search(searchType string, query string, opt *SearchOptio
return nil, err
}
if opt.TextMatch {
// Accept header defaults to "application/vnd.github.v3+json"
// We change it here to fetch back text-match metadata
req.Header.Set("Accept", "application/vnd.github.v3.text-match+json")
}
return s.client.Do(req, result)
}

View File

@@ -135,3 +135,62 @@ func TestSearchService_Code(t *testing.T) {
t.Errorf("Search.Code returned %+v, want %+v", result, want)
}
}
func TestSearchService_CodeTextMatch(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
textMatchResponse := `
{
"total_count": 1,
"items": [
{
"name":"gopher1",
"text_matches": [
{
"fragment": "I'm afraid my friend what you have found\nIs a gopher who lives to feed",
"matches": [
{
"text": "gopher",
"indices": [
14,
21
]
}
]
}
]
}
]
}
`
fmt.Fprint(w, textMatchResponse)
})
opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}, TextMatch: true}
result, _, err := client.Search.Code("blah", opts)
if err != nil {
t.Errorf("Search.Code returned error: %v", err)
}
wantedCodeResult := CodeResult{
Name: String("gopher1"),
TextMatches: []TextMatch{{
Fragment: String("I'm afraid my friend what you have found\nIs a gopher who lives to feed"),
Matches: []Match{{Text: String("gopher"), Indices: []int{14, 21}}},
},
},
}
want := &CodeSearchResult{
Total: Int(1),
CodeResults: []CodeResult{wantedCodeResult},
}
if !reflect.DeepEqual(result, want) {
t.Errorf("Search.Code returned %+v, want %+v", result, want)
}
}

View File

@@ -55,6 +55,10 @@ type User struct {
ReposURL *string `json:"repos_url,omitempty"`
StarredURL *string `json:"starred_url,omitempty"`
SubscriptionsURL *string `json:"subscriptions_url,omitempty"`
// TextMatches is only populated from search results that request text matches
// See: search.go and https://developer.github.com/v3/search/#text-match-metadata
TextMatches []TextMatch `json:"text_matches,omitempty"`
}
func (u User) String() string {

View File

@@ -0,0 +1,64 @@
// Copyright 2014 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import "fmt"
// PromoteSiteAdmin promotes a user to a site administrator of a GitHub Enterprise instance.
//
// GitHub API docs: https://developer.github.com/v3/users/administration/#promote-an-ordinary-user-to-a-site-administrator
func (s *UsersService) PromoteSiteAdmin(user string) (*Response, error) {
u := fmt.Sprintf("users/%v/site_admin", user)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// DemoteSiteAdmin demotes a user from site administrator of a GitHub Enterprise instance.
//
// GitHub API docs: https://developer.github.com/v3/users/administration/#demote-a-site-administrator-to-an-ordinary-user
func (s *UsersService) DemoteSiteAdmin(user string) (*Response, error) {
u := fmt.Sprintf("users/%v/site_admin", user)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// Suspend a user on a GitHub Enterprise instance.
//
// GitHub API docs: https://developer.github.com/v3/users/administration/#suspend-a-user
func (s *UsersService) Suspend(user string) (*Response, error) {
u := fmt.Sprintf("users/%v/suspended", user)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// Unsuspend a user on a GitHub Enterprise instance.
//
// GitHub API docs: https://developer.github.com/v3/users/administration/#unsuspend-a-user
func (s *UsersService) Unsuspend(user string) (*Response, error) {
u := fmt.Sprintf("users/%v/suspended", user)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}

View File

@@ -0,0 +1,71 @@
// Copyright 2014 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"net/http"
"testing"
)
func TestUsersService_PromoteSiteAdmin(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/users/u/site_admin", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Users.PromoteSiteAdmin("u")
if err != nil {
t.Errorf("Users.PromoteSiteAdmin returned error: %v", err)
}
}
func TestUsersService_DemoteSiteAdmin(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/users/u/site_admin", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Users.DemoteSiteAdmin("u")
if err != nil {
t.Errorf("Users.DemoteSiteAdmin returned error: %v", err)
}
}
func TestUsersService_Suspend(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/users/u/suspended", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Users.Suspend("u")
if err != nil {
t.Errorf("Users.Suspend returned error: %v", err)
}
}
func TestUsersService_Unsuspend(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/users/u/suspended", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Users.Unsuspend("u")
if err != nil {
t.Errorf("Users.Unsuspend returned error: %v", err)
}
}

View File

@@ -33,6 +33,14 @@ import (
var timeType = reflect.TypeOf(time.Time{})
var encoderType = reflect.TypeOf(new(Encoder)).Elem()
// Encoder is an interface implemented by any type that wishes to encode
// itself into URL values in a non-standard way.
type Encoder interface {
EncodeValues(key string, v *url.Values) error
}
// Values returns the url.Values encoding of v.
//
// Values expects to be passed a struct, and traverses it recursively using the
@@ -107,14 +115,14 @@ func Values(v interface{}) (url.Values, error) {
}
values := make(url.Values)
reflectValue(values, val)
return values, nil
err := reflectValue(values, val)
return values, err
}
// reflectValue populates the values parameter from the struct fields in val.
// Embedded structs are followed recursively (using the rules defined in the
// Values function documentation) breadth-first.
func reflectValue(values url.Values, val reflect.Value) {
func reflectValue(values url.Values, val reflect.Value) error {
var embedded []reflect.Value
typ := val.Type()
@@ -144,6 +152,14 @@ func reflectValue(values url.Values, val reflect.Value) {
continue
}
if sv.Type().Implements(encoderType) {
m := sv.Interface().(Encoder)
if err := m.EncodeValues(name, &values); err != nil {
return err
}
continue
}
if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
var del byte
if opts.Contains("comma") {
@@ -176,8 +192,12 @@ func reflectValue(values url.Values, val reflect.Value) {
}
for _, f := range embedded {
reflectValue(values, f)
if err := reflectValue(values, f); err != nil {
return err
}
}
return nil
}
// valueString returns the string representation of a value.

View File

@@ -5,6 +5,7 @@
package query
import (
"fmt"
"net/url"
"reflect"
"testing"
@@ -188,6 +189,34 @@ func TestValues_invalidInput(t *testing.T) {
}
}
type EncodedArgs []string
func (m EncodedArgs) EncodeValues(key string, v *url.Values) error {
for i, arg := range m {
v.Set(fmt.Sprintf("%s.%d", key, i), arg)
}
return nil
}
func TestValues_Marshaler(t *testing.T) {
s := struct {
Args EncodedArgs `url:"arg"`
}{[]string{"a", "b", "c"}}
v, err := Values(s)
if err != nil {
t.Errorf("Values(%q) returned error: %v", s, err)
}
want := url.Values{
"arg.0": {"a"},
"arg.1": {"b"},
"arg.2": {"c"},
}
if !reflect.DeepEqual(want, v) {
t.Errorf("Values(%q) returned %v, want %v", s, v, want)
}
}
func TestTagParsing(t *testing.T) {
name, opts := parseTag("field,foobar,foo")
if name != "field" {

View File

@@ -66,15 +66,13 @@ func (c *Colorize) Color(v string) string {
result.WriteString(v[m[1]:nm[0]])
m = nm
// If we're disabled, just ignore the color code information
if c.Disable {
continue
}
var replace string
if code, ok := c.Colors[v[m[0]+1:m[1]-1]]; ok {
colored = true
if !c.Disable {
replace = fmt.Sprintf("\033[%sm", code)
}
} else {
replace = v[m[0]:m[1]]
}
@@ -83,7 +81,7 @@ func (c *Colorize) Color(v string) string {
}
result.WriteString(v[m[1]:])
if colored && c.Reset {
if colored && c.Reset && !c.Disable {
// Write the clear byte at the end
result.WriteString("\033[0m")
}

View File

@@ -61,15 +61,29 @@ func TestColorizeColor_disable(t *testing.T) {
c := def
c.Disable = true
input := "[blue]foo"
output := "foo"
actual := c.Color(input)
if actual != output {
cases := []struct {
Input, Output string
}{
{
"[blue]foo",
"foo",
},
{
"[foo]bar",
"[foo]bar",
},
}
for _, tc := range cases {
actual := c.Color(tc.Input)
if actual != tc.Output {
t.Errorf(
"Input: %#v\n\nOutput: %#v\n\nExpected: %#v",
input,
tc.Input,
actual,
output)
tc.Output)
}
}
}

View File

@@ -23,7 +23,7 @@ func printHelp(args []string, additionalArgs []string) {
} else {
command, found := commandMatching(args[0])
if !found {
complainAndQuit(fmt.Sprintf("Unkown command: %s", args[0]))
complainAndQuit(fmt.Sprintf("Unknown command: %s", args[0]))
}
usageForCommand(command, true)

View File

@@ -64,7 +64,7 @@ func (d *DeltaTracker) Delta(suites []testsuite.TestSuite) (delta Delta, errors
func (d *DeltaTracker) WillRun(suite testsuite.TestSuite) error {
s, ok := d.suites[suite.Path]
if !ok {
return fmt.Errorf("unkown suite %s", suite.Path)
return fmt.Errorf("unknown suite %s", suite.Path)
}
return s.MarkAsRunAndRecomputedDependencies(d.maxDepth)

View File

@@ -1,11 +1,11 @@
package codelocation_test
import (
"runtime"
. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"runtime"
)
var _ = Describe("CodeLocation", func() {

View File

@@ -1,8 +1,8 @@
package containernode_test
import (
"math/rand"
"github.com/onsi/ginkgo/internal/leafnodes"
"math/rand"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

View File

@@ -4,10 +4,13 @@ import (
"math"
"time"
"sync"
"github.com/onsi/ginkgo/types"
)
type benchmarker struct {
mu sync.Mutex
measurements map[string]*types.SpecMeasurement
orderCounter int
}
@@ -23,6 +26,8 @@ func (b *benchmarker) Time(name string, body func(), info ...interface{}) (elaps
body()
elapsedTime = time.Since(t)
b.mu.Lock()
defer b.mu.Unlock()
measurement := b.getMeasurement(name, "Fastest Time", "Slowest Time", "Average Time", "s", info...)
measurement.Results = append(measurement.Results, elapsedTime.Seconds())
@@ -31,6 +36,8 @@ func (b *benchmarker) Time(name string, body func(), info ...interface{}) (elaps
func (b *benchmarker) RecordValue(name string, value float64, info ...interface{}) {
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", "", info...)
b.mu.Lock()
defer b.mu.Unlock()
measurement.Results = append(measurement.Results, value)
}
@@ -60,6 +67,8 @@ func (b *benchmarker) getMeasurement(name string, smallestLabel string, largestL
}
func (b *benchmarker) measurementsReport() map[string]*types.SpecMeasurement {
b.mu.Lock()
defer b.mu.Unlock()
for _, measurement := range b.measurements {
measurement.Smallest = math.MaxFloat64
measurement.Largest = -math.MaxFloat64

View File

@@ -5,10 +5,10 @@ import (
. "github.com/onsi/ginkgo/internal/leafnodes"
. "github.com/onsi/gomega"
"time"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
var _ = Describe("Measure Nodes", func() {

View File

@@ -4,11 +4,11 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"time"
"github.com/onsi/ginkgo/config"
. "github.com/onsi/ginkgo/internal/remote"
st "github.com/onsi/ginkgo/reporters/stenographer"
"github.com/onsi/ginkgo/types"
"time"
)
var _ = Describe("Aggregator", func() {