Merge pull request #8 from zachgersh/master

Allow Github Resource to Fetch Private Assets
This commit is contained in:
Alex Suraci
2015-08-08 10:28:42 -07:00
112 changed files with 1554 additions and 415 deletions

8
Godeps/Godeps.json generated
View File

@@ -15,10 +15,6 @@
"Comment": "v3.0.0",
"Rev": "2f3112b6f8f18f9df8743cc75ed51da08094ef6a"
},
{
"ImportPath": "github.com/google/go-github/github",
"Rev": "7ea4ee6d222607c11ea86e99a6f6723beeae785d"
},
{
"ImportPath": "github.com/google/go-querystring/query",
"Rev": "d8840cbb2baa915f4836edda4750050a2c0b7aea"
@@ -36,6 +32,10 @@
"ImportPath": "github.com/onsi/gomega",
"Comment": "v1.0-28-g8adf9e1",
"Rev": "8adf9e1730c55cdc590de7d49766cb2acc88d8f2"
},
{
"ImportPath": "github.com/zachgersh/go-github/github",
"Rev": "f47a8b33261f10482dfede3cc839c4fcf34da04f"
}
]
}

View File

@@ -7,6 +7,12 @@ package github
import "fmt"
// StarredRepository is returned by ListStarred.
type StarredRepository struct {
StarredAt *Timestamp `json:"starred_at,omitempty"`
Repository *Repository `json:"repo,omitempty"`
}
// ListStargazers lists people who have starred the specified repo.
//
// GitHub API Docs: https://developer.github.com/v3/activity/starring/#list-stargazers
@@ -49,7 +55,7 @@ type ActivityListStarredOptions struct {
// will list the starred repositories for the authenticated user.
//
// GitHub API docs: http://developer.github.com/v3/activity/starring/#list-repositories-being-starred
func (s *ActivityService) ListStarred(user string, opt *ActivityListStarredOptions) ([]Repository, *Response, error) {
func (s *ActivityService) ListStarred(user string, opt *ActivityListStarredOptions) ([]StarredRepository, *Response, error) {
var u string
if user != "" {
u = fmt.Sprintf("users/%v/starred", user)
@@ -66,7 +72,10 @@ func (s *ActivityService) ListStarred(user string, opt *ActivityListStarredOptio
return nil, nil, err
}
repos := new([]Repository)
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeStarringPreview)
repos := new([]StarredRepository)
resp, err := s.client.Do(req, repos)
if err != nil {
return nil, resp, err

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"reflect"
"testing"
"time"
)
func TestActivityService_ListStargazers(t *testing.T) {
@@ -42,7 +43,8 @@ func TestActivityService_ListStarred_authenticatedUser(t *testing.T) {
mux.HandleFunc("/user/starred", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{"id":1}]`)
testHeader(t, r, "Accept", mediaTypeStarringPreview)
fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","repo":{"id":1}}]`)
})
repos, _, err := client.Activity.ListStarred("", nil)
@@ -50,7 +52,7 @@ func TestActivityService_ListStarred_authenticatedUser(t *testing.T) {
t.Errorf("Activity.ListStarred returned error: %v", err)
}
want := []Repository{{ID: Int(1)}}
want := []StarredRepository{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, Repository: &Repository{ID: Int(1)}}}
if !reflect.DeepEqual(repos, want) {
t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want)
}
@@ -62,12 +64,13 @@ func TestActivityService_ListStarred_specifiedUser(t *testing.T) {
mux.HandleFunc("/users/u/starred", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeStarringPreview)
testFormValues(t, r, values{
"sort": "created",
"direction": "asc",
"page": "2",
})
fmt.Fprint(w, `[{"id":2}]`)
fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","repo":{"id":2}}]`)
})
opt := &ActivityListStarredOptions{"created", "asc", ListOptions{Page: 2}}
@@ -76,7 +79,7 @@ func TestActivityService_ListStarred_specifiedUser(t *testing.T) {
t.Errorf("Activity.ListStarred returned error: %v", err)
}
want := []Repository{{ID: Int(2)}}
want := []StarredRepository{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, Repository: &Repository{ID: Int(2)}}}
if !reflect.DeepEqual(repos, want) {
t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want)
}

View File

@@ -28,24 +28,25 @@ Authentication
The go-github library does not directly handle authentication. Instead, when
creating a new client, pass an http.Client that can handle authentication for
you. The easiest and recommended way to do this is using the goauth2 library,
but you can always use any other library that provides an http.Client. If you
have an OAuth2 access token (for example, a personal API token), you can use it
with the goauth2 using:
you. The easiest and recommended way to do this is using the golang.org/x/oauth2
library, but you can always use any other library that provides an http.Client.
If you have an OAuth2 access token (for example, a personal API token), you can
use it with the oauth2 library using:
import "code.google.com/p/goauth2/oauth"
import "golang.org/x/oauth2"
// simple OAuth transport if you already have an access token;
// see goauth2 library for full usage
t := &oauth.Transport{
Token: &oauth.Token{AccessToken: "..."},
func main() {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: "... your access token ..."},
)
tc := oauth2.NewClient(oauth2.NoContext, ts)
client := github.NewClient(tc)
// list all repositories for the authenticated user
repos, _, err := client.Repositories.List("", nil)
}
client := github.NewClient(t.Client())
// list all repositories for the authenticated user
repos, _, err := client.Repositories.List("", nil)
Note that when using an authenticated Client, all calls made by the client will
include the specified OAuth token. Therefore, authenticated clients should
almost never be shared between different users.

View File

@@ -157,6 +157,24 @@ func (s *GistsService) Get(id string) (*Gist, *Response, error) {
return gist, resp, err
}
// GetRevision gets a specific revision of a gist.
//
// GitHub API docs: https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist
func (s *GistsService) GetRevision(id, sha string) (*Gist, *Response, error) {
u := fmt.Sprintf("gists/%v/%v", id, sha)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
gist := new(Gist)
resp, err := s.client.Do(req, gist)
if err != nil {
return nil, resp, err
}
return gist, resp, err
}
// Create a gist for authenticated user.
//
// GitHub API docs: http://developer.github.com/v3/gists/#create-a-gist

View File

@@ -146,6 +146,32 @@ func TestGistsService_Get_invalidID(t *testing.T) {
testURLParseError(t, err)
}
func TestGistsService_GetRevision(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/gists/1/s", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id": "1"}`)
})
gist, _, err := client.Gists.GetRevision("1", "s")
if err != nil {
t.Errorf("Gists.Get returned error: %v", err)
}
want := &Gist{ID: String("1")}
if !reflect.DeepEqual(gist, want) {
t.Errorf("Gists.Get returned %+v, want %+v", gist, want)
}
}
func TestGistsService_GetRevision_invalidID(t *testing.T) {
_, _, err := client.Gists.GetRevision("%", "%")
testURLParseError(t, err)
}
func TestGistsService_Create(t *testing.T) {
setup()
defer teardown()

View File

@@ -37,11 +37,15 @@ const (
// 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/2015-03-09-licenses-api/
mediaTypeLicensesPreview = "application/vnd.github.drax-preview+json"
// https://developer.github.com/changes/2014-01-09-preview-the-new-deployments-api/
mediaTypeDeploymentPreview = "application/vnd.github.cannonball-preview+json"
// https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
// https://developer.github.com/changes/2015-06-24-api-enhancements-for-working-with-organization-permissions/
mediaTypeOrgPermissionPreview = "application/vnd.github.ironman-preview+json"
mediaTypeOrgPermissionRepoPreview = "application/vnd.github.ironman-preview.repository+json"
)
// A Client manages communication with the GitHub API.
@@ -77,6 +81,7 @@ type Client struct {
Repositories *RepositoriesService
Search *SearchService
Users *UsersService
Licenses *LicensesService
}
// ListOptions specifies the optional parameters to various List methods that
@@ -119,7 +124,7 @@ func addOptions(s string, opt interface{}) (string, error) {
// NewClient returns a new GitHub API client. If a nil httpClient is
// provided, http.DefaultClient will be used. To use API methods which require
// authentication, provide an http.Client that will perform the authentication
// for you (such as that provided by the goauth2 library).
// for you (such as that provided by the golang.org/x/oauth2 library).
func NewClient(httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = http.DefaultClient
@@ -138,6 +143,7 @@ func NewClient(httpClient *http.Client) *Client {
c.Repositories = &RepositoriesService{client: c}
c.Search = &SearchService{client: c}
c.Users = &UsersService{client: c}
c.Licenses = &LicensesService{client: c}
return c
}
@@ -219,7 +225,7 @@ type Response struct {
Rate
}
// newResponse creats a new Response for the provided http.Response.
// newResponse creates a new Response for the provided http.Response.
func newResponse(r *http.Response) *Response {
response := &Response{Response: r}
response.populatePageValues()
@@ -333,10 +339,24 @@ type ErrorResponse struct {
func (r *ErrorResponse) Error() string {
return fmt.Sprintf("%v %v: %d %v %+v",
r.Response.Request.Method, r.Response.Request.URL,
r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
r.Response.StatusCode, r.Message, r.Errors)
}
// sanitizeURL redacts the client_id and client_secret tokens from the URL which
// may be exposed to the user, specifically in the ErrorResponse error message.
func sanitizeURL(uri *url.URL) *url.URL {
if uri == nil {
return nil
}
params := uri.Query()
if len(params.Get("client_secret")) > 0 {
params.Set("client_secret", "REDACTED")
uri.RawQuery = params.Encode()
}
return uri
}
/*
An Error reports more details on an individual error in an ErrorResponse.
These are the possible validation error codes:

View File

@@ -430,6 +430,25 @@ func TestDo_rateLimit_errorResponse(t *testing.T) {
}
}
func TestSanitizeURL(t *testing.T) {
tests := []struct {
in, want string
}{
{"/?a=b", "/?a=b"},
{"/?a=b&client_secret=secret", "/?a=b&client_secret=REDACTED"},
{"/?a=b&client_id=id&client_secret=secret", "/?a=b&client_id=id&client_secret=REDACTED"},
}
for _, tt := range tests {
inURL, _ := url.Parse(tt.in)
want, _ := url.Parse(tt.want)
if got := sanitizeURL(inURL); !reflect.DeepEqual(got, want) {
t.Errorf("sanitizeURL(%v) returned %v, want %v", tt.in, got, want)
}
}
}
func TestCheckResponse(t *testing.T) {
res := &http.Response{
Request: &http.Request{},

View File

@@ -49,12 +49,12 @@ func (i Issue) String() string {
// It is separate from Issue above because otherwise Labels
// and Assignee fail to serialize to the correct JSON.
type IssueRequest struct {
Title *string `json:"title,omitempty"`
Body *string `json:"body,omitempty"`
Labels []string `json:"labels,omitempty"`
Assignee *string `json:"assignee,omitempty"`
State *string `json:"state,omitempty"`
Milestone *int `json:"milestone,omitempty"`
Title *string `json:"title,omitempty"`
Body *string `json:"body,omitempty"`
Labels *[]string `json:"labels,omitempty"`
Assignee *string `json:"assignee,omitempty"`
State *string `json:"state,omitempty"`
Milestone *int `json:"milestone,omitempty"`
}
// IssueListOptions specifies the optional parameters to the IssuesService.List
@@ -72,7 +72,7 @@ type IssueListOptions struct {
Labels []string `url:"labels,comma,omitempty"`
// Sort specifies how to sort issues. Possible values are: created, updated,
// and comments. Default value is "assigned".
// and comments. Default value is "created".
Sort string `url:"sort,omitempty"`
// Direction in which to sort issues. Possible values are: asc, desc.
@@ -166,7 +166,7 @@ type IssueListByRepoOptions struct {
Labels []string `url:"labels,omitempty,comma"`
// Sort specifies how to sort issues. Possible values are: created, updated,
// and comments. Default value is "assigned".
// and comments. Default value is "created".
Sort string `url:"sort,omitempty"`
// Direction in which to sort issues. Possible values are: asc, desc.

View File

@@ -7,7 +7,7 @@ package github
import "fmt"
// Label represents a GitHib label on an Issue
// Label represents a GitHub label on an Issue
type Label struct {
URL *string `json:"url,omitempty"`
Name *string `json:"name,omitempty"`

View File

@@ -176,7 +176,7 @@ func TestIssuesService_Create(t *testing.T) {
Title: String("t"),
Body: String("b"),
Assignee: String("a"),
Labels: []string{"l1", "l2"},
Labels: &[]string{"l1", "l2"},
}
mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) {

View File

@@ -0,0 +1,81 @@
// Copyright 2013 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"
// LicensesService handles communication with the license related
// methods of the GitHub API.
//
// GitHub API docs: http://developer.github.com/v3/pulls/
type LicensesService struct {
client *Client
}
// License represents an open source license.
type License struct {
Key *string `json:"key,omitempty"`
Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
Featured *bool `json:"featured,omitempty"`
Description *string `json:"description,omitempty"`
Category *string `json:"category,omitempty"`
Implementation *string `json:"implementation,omitempty"`
Required *[]string `json:"required,omitempty"`
Permitted *[]string `json:"permitted,omitempty"`
Forbidden *[]string `json:"forbidden,omitempty"`
Body *string `json:"body,omitempty"`
}
func (l License) String() string {
return Stringify(l)
}
// List popular open source licenses.
//
// GitHub API docs: https://developer.github.com/v3/licenses/#list-all-licenses
func (s *LicensesService) List() ([]License, *Response, error) {
req, err := s.client.NewRequest("GET", "licenses", nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeLicensesPreview)
licenses := new([]License)
resp, err := s.client.Do(req, licenses)
if err != nil {
return nil, resp, err
}
return *licenses, resp, err
}
// Get extended metadata for one license.
//
// GitHub API docs: https://developer.github.com/v3/licenses/#get-an-individual-license
func (s *LicensesService) Get(licenseName string) (*License, *Response, error) {
u := fmt.Sprintf("licenses/%s", licenseName)
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", mediaTypeLicensesPreview)
license := new(License)
resp, err := s.client.Do(req, license)
if err != nil {
return nil, resp, err
}
return license, resp, err
}

View File

@@ -0,0 +1,64 @@
// Copyright 2013 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"
"net/http"
"reflect"
"testing"
)
func TestLicensesService_List(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/licenses", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeLicensesPreview)
fmt.Fprint(w, `[{"key":"mit","name":"MIT","url":"https://api.github.com/licenses/mit"}]`)
})
licenses, _, err := client.Licenses.List()
if err != nil {
t.Errorf("Licenses.List returned error: %v", err)
}
want := []License{{
Key: String("mit"),
Name: String("MIT"),
URL: String("https://api.github.com/licenses/mit"),
}}
if !reflect.DeepEqual(licenses, want) {
t.Errorf("Licenses.List returned %+v, want %+v", licenses, want)
}
}
func TestLicensesService_Get(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/licenses/mit", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeLicensesPreview)
fmt.Fprint(w, `{"key":"mit","name":"MIT"}`)
})
license, _, err := client.Licenses.Get("mit")
if err != nil {
t.Errorf("Licenses.Get returned error: %v", err)
}
want := &License{Key: String("mit"), Name: String("MIT")}
if !reflect.DeepEqual(license, want) {
t.Errorf("Licenses.Get returned %+v, want %+v", license, want)
}
}
func TestLicensesService_Get_invalidTemplate(t *testing.T) {
_, _, err := client.Licenses.Get("%")
testURLParseError(t, err)
}

View File

@@ -98,6 +98,10 @@ type APIMeta struct {
// username and password, sudo mode, and two-factor authentication are
// not supported on these servers.)
VerifiablePasswordAuthentication *bool `json:"verifiable_password_authentication,omitempty"`
// An array of IP addresses in CIDR format specifying the addresses
// which serve GitHub Pages websites.
Pages []string `json:"pages,omitempty"`
}
// APIMeta returns information about GitHub.com, the service. Or, if you access
@@ -159,3 +163,35 @@ func (c *Client) Zen() (string, *Response, error) {
return buf.String(), resp, nil
}
// ServiceHook represents a hook that has configuration settings, a list of
// available events, and default events.
type ServiceHook struct {
Name *string `json:"name,omitempty"`
Events []string `json:"events,omitempty"`
SupportedEvents []string `json:"supported_events,omitempty"`
Schema [][]string `json:"schema,omitempty"`
}
func (s *ServiceHook) String() string {
return Stringify(s)
}
// ListServiceHooks lists all of the available service hooks.
//
// GitHub API docs: https://developer.github.com/webhooks/#services
func (c *Client) ListServiceHooks() ([]ServiceHook, *Response, error) {
u := "hooks"
req, err := c.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
hooks := new([]ServiceHook)
resp, err := c.Do(req, hooks)
if err != nil {
return nil, resp, err
}
return *hooks, resp, err
}

View File

@@ -72,7 +72,7 @@ func TestAPIMeta(t *testing.T) {
mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"hooks":["h"], "git":["g"], "verifiable_password_authentication": true}`)
fmt.Fprint(w, `{"hooks":["h"], "git":["g"], "pages":["p"], "verifiable_password_authentication": true}`)
})
meta, _, err := client.APIMeta()
@@ -83,6 +83,7 @@ func TestAPIMeta(t *testing.T) {
want := &APIMeta{
Hooks: []string{"h"},
Git: []string{"g"},
Pages: []string{"p"},
VerifiablePasswordAuthentication: Bool(true),
}
if !reflect.DeepEqual(want, meta) {
@@ -135,3 +136,35 @@ func TestZen(t *testing.T) {
t.Errorf("Zen returned %+v, want %+v", got, want)
}
}
func TestRepositoriesService_ListServiceHooks(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/hooks", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{
"name":"n",
"events":["e"],
"supported_events":["s"],
"schema":[
["a", "b"]
]
}]`)
})
hooks, _, err := client.Repositories.ListServiceHooks()
if err != nil {
t.Errorf("Repositories.ListHooks returned error: %v", err)
}
want := []ServiceHook{{
Name: String("n"),
Events: []string{"e"},
SupportedEvents: []string{"s"},
Schema: [][]string{{"a", "b"}},
}}
if !reflect.DeepEqual(hooks, want) {
t.Errorf("Repositories.ListServiceHooks returned %+v, want %+v", hooks, want)
}
}

View File

@@ -0,0 +1,104 @@
// Copyright 2015 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"
// ListHooks lists all Hooks for the specified organization.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#list-hooks
func (s *OrganizationsService) ListHooks(org string, opt *ListOptions) ([]Hook, *Response, error) {
u := fmt.Sprintf("orgs/%v/hooks", org)
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
}
hooks := new([]Hook)
resp, err := s.client.Do(req, hooks)
if err != nil {
return nil, resp, err
}
return *hooks, resp, err
}
// GetHook returns a single specified Hook.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#get-single-hook
func (s *OrganizationsService) GetHook(org string, id int) (*Hook, *Response, error) {
u := fmt.Sprintf("orgs/%v/hooks/%d", org, id)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
hook := new(Hook)
resp, err := s.client.Do(req, hook)
return hook, resp, err
}
// CreateHook creates a Hook for the specified org.
// Name and Config are required fields.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#create-a-hook
func (s *OrganizationsService) CreateHook(org string, hook *Hook) (*Hook, *Response, error) {
u := fmt.Sprintf("orgs/%v/hooks", org)
req, err := s.client.NewRequest("POST", u, hook)
if err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := s.client.Do(req, h)
if err != nil {
return nil, resp, err
}
return h, resp, err
}
// EditHook updates a specified Hook.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#edit-a-hook
func (s *OrganizationsService) EditHook(org string, id int, hook *Hook) (*Hook, *Response, error) {
u := fmt.Sprintf("orgs/%v/hooks/%d", org, id)
req, err := s.client.NewRequest("PATCH", u, hook)
if err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := s.client.Do(req, h)
return h, resp, err
}
// PingHook triggers a 'ping' event to be sent to the Hook.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#ping-a-hook
func (s *OrganizationsService) PingHook(org string, id int) (*Response, error) {
u := fmt.Sprintf("orgs/%v/hooks/%d/pings", org, id)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// DeleteHook deletes a specified Hook.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#delete-a-hook
func (s *OrganizationsService) DeleteHook(org string, id int) (*Response, error) {
u := fmt.Sprintf("orgs/%v/hooks/%d", org, id)
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,134 @@
// Copyright 2015 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 TestOrganizationsService_ListHooks(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/hooks", 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}
hooks, _, err := client.Organizations.ListHooks("o", opt)
if err != nil {
t.Errorf("Organizations.ListHooks returned error: %v", err)
}
want := []Hook{{ID: Int(1)}, {ID: Int(2)}}
if !reflect.DeepEqual(hooks, want) {
t.Errorf("Organizations.ListHooks returned %+v, want %+v", hooks, want)
}
}
func TestOrganizationsService_ListHooks_invalidOrg(t *testing.T) {
_, _, err := client.Organizations.ListHooks("%", nil)
testURLParseError(t, err)
}
func TestOrganizationsService_GetHook(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":1}`)
})
hook, _, err := client.Organizations.GetHook("o", 1)
if err != nil {
t.Errorf("Organizations.GetHook returned error: %v", err)
}
want := &Hook{ID: Int(1)}
if !reflect.DeepEqual(hook, want) {
t.Errorf("Organizations.GetHook returned %+v, want %+v", hook, want)
}
}
func TestOrganizationsService_GetHook_invalidOrg(t *testing.T) {
_, _, err := client.Organizations.GetHook("%", 1)
testURLParseError(t, err)
}
func TestOrganizationsService_EditHook(t *testing.T) {
setup()
defer teardown()
input := &Hook{Name: String("t")}
mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) {
v := new(Hook)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
fmt.Fprint(w, `{"id":1}`)
})
hook, _, err := client.Organizations.EditHook("o", 1, input)
if err != nil {
t.Errorf("Organizations.EditHook returned error: %v", err)
}
want := &Hook{ID: Int(1)}
if !reflect.DeepEqual(hook, want) {
t.Errorf("Organizations.EditHook returned %+v, want %+v", hook, want)
}
}
func TestOrganizationsService_EditHook_invalidOrg(t *testing.T) {
_, _, err := client.Organizations.EditHook("%", 1, nil)
testURLParseError(t, err)
}
func TestOrganizationsService_PingHook(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
})
_, err := client.Organizations.PingHook("o", 1)
if err != nil {
t.Errorf("Organizations.PingHook returned error: %v", err)
}
}
func TestOrganizationsService_DeleteHook(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.Organizations.DeleteHook("o", 1)
if err != nil {
t.Errorf("Organizations.DeleteHook returned error: %v", err)
}
}
func TestOrganizationsService_DeleteHook_invalidOrg(t *testing.T) {
_, err := client.Organizations.DeleteHook("%", 1)
testURLParseError(t, err)
}

View File

@@ -15,7 +15,16 @@ type Membership struct {
// Possible values are: "active", "pending"
State *string `json:"state,omitempty"`
// TODO(willnorris): add docs
// Role identifies the user's role within the organization or team.
// Possible values for organization membership:
// member - non-owner organization member
// admin - organization owner
//
// Possible values for team membership are:
// member - a normal member of the team
// maintainer - a team maintainer. Able to add/remove other team
// members, promote other team members to team
// maintainer, and edit the teams name and description
Role *string `json:"role,omitempty"`
// For organization membership, the API URL of the organization.
@@ -43,6 +52,15 @@ type ListMembersOptions struct {
// 2fa_disabled, all. Default is "all".
Filter string `url:"filter,omitempty"`
// Role filters memebers returned by their role in the organization.
// Possible values are:
// all - all members of the organization, regardless of role
// admin - organization owners
// member - non-organization members
//
// Default is "all".
Role string `url:"role,omitempty"`
ListOptions
}
@@ -68,6 +86,10 @@ func (s *OrganizationsService) ListMembers(org string, opt *ListMembersOptions)
return nil, nil, err
}
if opt != nil && opt.Role != "" {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
members := new([]User)
resp, err := s.client.Do(req, members)
if err != nil {
@@ -120,7 +142,8 @@ func (s *OrganizationsService) RemoveMember(org, user string) (*Response, error)
return s.client.Do(req, nil)
}
// PublicizeMembership publicizes a user's membership in an organization.
// PublicizeMembership publicizes a user's membership in an organization. (A
// user cannot publicize the membership for another user.)
//
// GitHub API docs: http://developer.github.com/v3/orgs/members/#publicize-a-users-membership
func (s *OrganizationsService) PublicizeMembership(org, user string) (*Response, error) {
@@ -171,9 +194,6 @@ func (s *OrganizationsService) ListOrgMemberships(opt *ListOrgMembershipsOptions
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 {
@@ -183,20 +203,25 @@ func (s *OrganizationsService) ListOrgMemberships(opt *ListOrgMembershipsOptions
return memberships, resp, err
}
// GetOrgMembership gets the membership for the authenticated user for the
// specified organization.
// GetOrgMembership gets the membership for a user in a specified organization.
// Passing an empty string for user will get the membership for the
// authenticated user.
//
// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-organization-membership
// 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)
func (s *OrganizationsService) GetOrgMembership(user, org string) (*Membership, *Response, error) {
var u string
if user != "" {
u = fmt.Sprintf("orgs/%v/memberships/%v", org, user)
} else {
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 {
@@ -206,20 +231,25 @@ func (s *OrganizationsService) GetOrgMembership(org string) (*Membership, *Respo
return membership, resp, err
}
// EditOrgMembership edits the membership for the authenticated user for the
// specified organization.
// EditOrgMembership edits the membership for user in specified organization.
// Passing an empty string for user will edit the membership for the
// authenticated user.
//
// GitHub API docs: https://developer.github.com/v3/orgs/members/#add-or-update-organization-membership
// 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)
func (s *OrganizationsService) EditOrgMembership(user, org string, membership *Membership) (*Membership, *Response, error) {
var u string
if user != "" {
u = fmt.Sprintf("orgs/%v/memberships/%v", org, user)
} else {
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 {
@@ -228,3 +258,17 @@ func (s *OrganizationsService) EditOrgMembership(org string, membership *Members
return m, resp, err
}
// RemoveOrgMembership removes user from the specified organization. If the
// user has been invited to the organization, this will cancel their invitation.
//
// GitHub API docs: https://developer.github.com/v3/orgs/members/#remove-organization-membership
func (s *OrganizationsService) RemoveOrgMembership(user, org string) (*Response, error) {
u := fmt.Sprintf("orgs/%v/memberships/%v", org, user)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}

View File

@@ -19,8 +19,10 @@ func TestOrganizationsService_ListMembers(t *testing.T) {
mux.HandleFunc("/orgs/o/members", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
testFormValues(t, r, values{
"filter": "2fa_disabled",
"role": "admin",
"page": "2",
})
fmt.Fprint(w, `[{"id":1}]`)
@@ -29,6 +31,7 @@ func TestOrganizationsService_ListMembers(t *testing.T) {
opt := &ListMembersOptions{
PublicOnly: false,
Filter: "2fa_disabled",
Role: "admin",
ListOptions: ListOptions{Page: 2},
}
members, _, err := client.Organizations.ListMembers("o", opt)
@@ -217,7 +220,6 @@ func TestOrganizationsService_ListOrgMemberships(t *testing.T) {
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",
@@ -240,17 +242,16 @@ func TestOrganizationsService_ListOrgMemberships(t *testing.T) {
}
}
func TestOrganizationsService_GetOrgMembership(t *testing.T) {
func TestOrganizationsService_GetOrgMembership_AuthenticatedUser(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")
membership, _, err := client.Organizations.GetOrgMembership("", "o")
if err != nil {
t.Errorf("Organizations.GetOrgMembership returned error: %v", err)
}
@@ -261,7 +262,27 @@ func TestOrganizationsService_GetOrgMembership(t *testing.T) {
}
}
func TestOrganizationsService_EditOrgMembership(t *testing.T) {
func TestOrganizationsService_GetOrgMembership_SpecifiedUser(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"url":"u"}`)
})
membership, _, err := client.Organizations.GetOrgMembership("u", "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_AuthenticatedUser(t *testing.T) {
setup()
defer teardown()
@@ -272,7 +293,6 @@ func TestOrganizationsService_EditOrgMembership(t *testing.T) {
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)
}
@@ -280,7 +300,7 @@ func TestOrganizationsService_EditOrgMembership(t *testing.T) {
fmt.Fprint(w, `{"url":"u"}`)
})
membership, _, err := client.Organizations.EditOrgMembership("o", input)
membership, _, err := client.Organizations.EditOrgMembership("", "o", input)
if err != nil {
t.Errorf("Organizations.EditOrgMembership returned error: %v", err)
}
@@ -290,3 +310,47 @@ func TestOrganizationsService_EditOrgMembership(t *testing.T) {
t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want)
}
}
func TestOrganizationsService_EditOrgMembership_SpecifiedUser(t *testing.T) {
setup()
defer teardown()
input := &Membership{State: String("active")}
mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) {
v := new(Membership)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
fmt.Fprint(w, `{"url":"u"}`)
})
membership, _, err := client.Organizations.EditOrgMembership("u", "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)
}
}
func TestOrganizationsService_RemoveOrgMembership(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Organizations.RemoveOrgMembership("u", "o")
if err != nil {
t.Errorf("Organizations.RemoveOrgMembership returned error: %v", err)
}
}

View File

@@ -10,11 +10,25 @@ import "fmt"
// Team represents a team within a GitHub organization. Teams are used to
// manage access to an organization's repositories.
type Team struct {
ID *int `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"`
Slug *string `json:"slug,omitempty"`
Permission *string `json:"permission,omitempty"`
ID *int `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"`
Slug *string `json:"slug,omitempty"`
// Permission is deprecated when creating or editing a team in an org
// using the new GitHub permission model. It no longer identifies the
// permission a team has on its repos, but only specifies the default
// permission a repo is initially added with. Avoid confusion by
// specifying a permission value when calling AddTeamRepo.
Permission *string `json:"permission,omitempty"`
// Privacy identifies the level of privacy this team should have.
// Possible values are:
// secret - only visible to organization owners and members of this team
// closed - visible to all members of this organization
// Default is "secret".
Privacy *string `json:"privacy,omitempty"`
MembersCount *int `json:"members_count,omitempty"`
ReposCount *int `json:"repos_count,omitempty"`
Organization *Organization `json:"organization,omitempty"`
@@ -77,6 +91,10 @@ func (s *OrganizationsService) CreateTeam(org string, team *Team) (*Team, *Respo
return nil, nil, err
}
if team.Privacy != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
t := new(Team)
resp, err := s.client.Do(req, t)
if err != nil {
@@ -96,6 +114,10 @@ func (s *OrganizationsService) EditTeam(id int, team *Team) (*Team, *Response, e
return nil, nil, err
}
if team.Privacy != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
t := new(Team)
resp, err := s.client.Do(req, t)
if err != nil {
@@ -118,11 +140,21 @@ func (s *OrganizationsService) DeleteTeam(team int) (*Response, error) {
return s.client.Do(req, nil)
}
// OrganizationListTeamMembersOptions specifies the optional parameters to the
// OrganizationsService.ListTeamMembers method.
type OrganizationListTeamMembersOptions struct {
// Role filters members returned by their role in the team. Possible
// values are "all", "member", "maintainer". Default is "all".
Role string `url:"role,omitempty"`
ListOptions
}
// ListTeamMembers lists all of the users who are members of the specified
// team.
//
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-members
func (s *OrganizationsService) ListTeamMembers(team int, opt *ListOptions) ([]User, *Response, error) {
func (s *OrganizationsService) ListTeamMembers(team int, opt *OrganizationListTeamMembersOptions) ([]User, *Response, error) {
u := fmt.Sprintf("teams/%v/members", team)
u, err := addOptions(u, opt)
if err != nil {
@@ -134,6 +166,10 @@ func (s *OrganizationsService) ListTeamMembers(team int, opt *ListOptions) ([]Us
return nil, nil, err
}
if opt != nil && opt.Role != "" {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
members := new([]User)
resp, err := s.client.Do(req, members)
if err != nil {
@@ -158,32 +194,6 @@ func (s *OrganizationsService) IsTeamMember(team int, user string) (bool, *Respo
return member, resp, err
}
// AddTeamMember adds a user to a team.
//
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#add-team-member
func (s *OrganizationsService) AddTeamMember(team int, user string) (*Response, error) {
u := fmt.Sprintf("teams/%v/members/%v", team, user)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// RemoveTeamMember removes a user from a team.
//
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#remove-team-member
func (s *OrganizationsService) RemoveTeamMember(team int, user string) (*Response, error) {
u := fmt.Sprintf("teams/%v/members/%v", team, user)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// ListTeamRepos lists the repositories that the specified team has access to.
//
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-repos
@@ -208,19 +218,40 @@ func (s *OrganizationsService) ListTeamRepos(team int, opt *ListOptions) ([]Repo
return *repos, resp, err
}
// IsTeamRepo checks if a team manages the specified repository.
// IsTeamRepo checks if a team manages the specified repository. If the
// repository is managed by team, a Repository is returned which includes the
// permissions team has for that repo.
//
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#get-team-repo
func (s *OrganizationsService) IsTeamRepo(team int, owner string, repo string) (bool, *Response, error) {
func (s *OrganizationsService) IsTeamRepo(team int, owner string, repo string) (*Repository, *Response, error) {
u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return false, nil, err
return nil, nil, err
}
resp, err := s.client.Do(req, nil)
manages, err := parseBoolResponse(err)
return manages, resp, err
req.Header.Set("Accept", mediaTypeOrgPermissionRepoPreview)
repository := new(Repository)
resp, err := s.client.Do(req, repository)
if err != nil {
return nil, resp, err
}
return repository, resp, err
}
// OrganizationAddTeamRepoOptions specifies the optional parameters to the
// OrganizationsService.AddTeamRepo method.
type OrganizationAddTeamRepoOptions struct {
// Permission specifies the permission to grant the team on this repository.
// Possible values are:
// pull - team members can pull, but not push to or administer this repository
// push - team members can pull and push, but not administer this repository
// admin - team members can pull, push and administer this repository
//
// If not specified, the team's permission attribute will be used.
Permission string `json:"permission,omitempty"`
}
// AddTeamRepo adds a repository to be managed by the specified team. The
@@ -228,13 +259,17 @@ func (s *OrganizationsService) IsTeamRepo(team int, owner string, repo string) (
// belongs, or a direct fork of a repository owned by the organization.
//
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#add-team-repo
func (s *OrganizationsService) AddTeamRepo(team int, owner string, repo string) (*Response, error) {
func (s *OrganizationsService) AddTeamRepo(team int, owner string, repo string, opt *OrganizationAddTeamRepoOptions) (*Response, error) {
u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
req, err := s.client.NewRequest("PUT", u, nil)
req, err := s.client.NewRequest("PUT", u, opt)
if err != nil {
return nil, err
}
if opt != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
return s.client.Do(req, nil)
}
@@ -286,9 +321,6 @@ func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Member
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 {
@@ -298,6 +330,20 @@ func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Member
return t, resp, err
}
// OrganizationAddTeamMembershipOptions does stuff specifies the optional
// parameters to the OrganizationsService.AddTeamMembership method.
type OrganizationAddTeamMembershipOptions struct {
// Role specifies the role the user should have in the team. Possible
// values are:
// member - a normal member of the team
// maintainer - a team maintainer. Able to add/remove other team
// members, promote other team members to team
// maintainer, and edit the teams name and description
//
// Default value is "member".
Role string `json:"role,omitempty"`
}
// AddTeamMembership adds or invites a user to a team.
//
// In order to add a membership between a user and a team, the authenticated
@@ -316,15 +362,16 @@ func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Member
// 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) {
func (s *OrganizationsService) AddTeamMembership(team int, user string, opt *OrganizationAddTeamMembershipOptions) (*Membership, *Response, error) {
u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
req, err := s.client.NewRequest("PUT", u, nil)
req, err := s.client.NewRequest("PUT", u, opt)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
if opt != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
t := new(Membership)
resp, err := s.client.Do(req, t)
@@ -345,8 +392,5 @@ func (s *OrganizationsService) RemoveTeamMembership(team int, user string) (*Res
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

@@ -64,13 +64,14 @@ func TestOrganizationsService_CreateTeam(t *testing.T) {
setup()
defer teardown()
input := &Team{Name: String("n")}
input := &Team{Name: String("n"), Privacy: String("closed")}
mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) {
v := new(Team)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "POST")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
@@ -98,13 +99,14 @@ func TestOrganizationsService_EditTeam(t *testing.T) {
setup()
defer teardown()
input := &Team{Name: String("n")}
input := &Team{Name: String("n"), Privacy: String("closed")}
mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) {
v := new(Team)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
@@ -143,11 +145,12 @@ func TestOrganizationsService_ListTeamMembers(t *testing.T) {
mux.HandleFunc("/teams/1/members", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"page": "2"})
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
testFormValues(t, r, values{"role": "member", "page": "2"})
fmt.Fprint(w, `[{"id":1}]`)
})
opt := &ListOptions{Page: 2}
opt := &OrganizationListTeamMembersOptions{Role: "member", ListOptions: ListOptions{Page: 2}}
members, _, err := client.Organizations.ListTeamMembers(1, opt)
if err != nil {
t.Errorf("Organizations.ListTeamMembers returned error: %v", err)
@@ -220,46 +223,6 @@ func TestOrganizationsService_IsTeamMember_invalidUser(t *testing.T) {
testURLParseError(t, err)
}
func TestOrganizationsService_AddTeamMember(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Organizations.AddTeamMember(1, "u")
if err != nil {
t.Errorf("Organizations.AddTeamMember returned error: %v", err)
}
}
func TestOrganizationsService_AddTeamMember_invalidUser(t *testing.T) {
_, err := client.Organizations.AddTeamMember(1, "%")
testURLParseError(t, err)
}
func TestOrganizationsService_RemoveTeamMember(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Organizations.RemoveTeamMember(1, "u")
if err != nil {
t.Errorf("Organizations.RemoveTeamMember returned error: %v", err)
}
}
func TestOrganizationsService_RemoveTeamMember_invalidUser(t *testing.T) {
_, err := client.Organizations.RemoveTeamMember(1, "%")
testURLParseError(t, err)
}
func TestOrganizationsService_PublicizeMembership(t *testing.T) {
setup()
defer teardown()
@@ -328,15 +291,18 @@ func TestOrganizationsService_IsTeamRepo_true(t *testing.T) {
mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
w.WriteHeader(http.StatusNoContent)
testHeader(t, r, "Accept", mediaTypeOrgPermissionRepoPreview)
fmt.Fprint(w, `{"id":1}`)
})
managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r")
repo, _, err := client.Organizations.IsTeamRepo(1, "o", "r")
if err != nil {
t.Errorf("Organizations.IsTeamRepo returned error: %v", err)
}
if want := true; managed != want {
t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want)
want := &Repository{ID: Int(1)}
if !reflect.DeepEqual(repo, want) {
t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", repo, want)
}
}
@@ -349,12 +315,15 @@ func TestOrganizationsService_IsTeamRepo_false(t *testing.T) {
w.WriteHeader(http.StatusNotFound)
})
managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r")
if err != nil {
t.Errorf("Organizations.IsTeamRepo returned error: %v", err)
repo, resp, err := client.Organizations.IsTeamRepo(1, "o", "r")
if err == nil {
t.Errorf("Expected HTTP 404 response")
}
if want := false; managed != want {
t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want)
if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want {
t.Errorf("Organizations.IsTeamRepo returned status %d, want %d", got, want)
}
if repo != nil {
t.Errorf("Organizations.IsTeamRepo returned %+v, want nil", repo)
}
}
@@ -367,12 +336,15 @@ func TestOrganizationsService_IsTeamRepo_error(t *testing.T) {
http.Error(w, "BadRequest", http.StatusBadRequest)
})
managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r")
repo, resp, err := client.Organizations.IsTeamRepo(1, "o", "r")
if err == nil {
t.Errorf("Expected HTTP 400 response")
}
if want := false; managed != want {
t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want)
if got, want := resp.Response.StatusCode, http.StatusBadRequest; got != want {
t.Errorf("Organizations.IsTeamRepo returned status %d, want %d", got, want)
}
if repo != nil {
t.Errorf("Organizations.IsTeamRepo returned %+v, want nil", repo)
}
}
@@ -385,12 +357,22 @@ func TestOrganizationsService_AddTeamRepo(t *testing.T) {
setup()
defer teardown()
opt := &OrganizationAddTeamRepoOptions{Permission: "admin"}
mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) {
v := new(OrganizationAddTeamRepoOptions)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PUT")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, opt) {
t.Errorf("Request body = %+v, want %+v", v, opt)
}
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Organizations.AddTeamRepo(1, "o", "r")
_, err := client.Organizations.AddTeamRepo(1, "o", "r", opt)
if err != nil {
t.Errorf("Organizations.AddTeamRepo returned error: %v", err)
}
@@ -405,14 +387,14 @@ func TestOrganizationsService_AddTeamRepo_noAccess(t *testing.T) {
w.WriteHeader(422)
})
_, err := client.Organizations.AddTeamRepo(1, "o", "r")
_, err := client.Organizations.AddTeamRepo(1, "o", "r", nil)
if err == nil {
t.Errorf("Expcted error to be returned")
}
}
func TestOrganizationsService_AddTeamRepo_invalidOwner(t *testing.T) {
_, err := client.Organizations.AddTeamRepo(1, "%", "r")
_, err := client.Organizations.AddTeamRepo(1, "%", "r", nil)
testURLParseError(t, err)
}
@@ -442,7 +424,6 @@ func TestOrganizationsService_GetTeamMembership(t *testing.T) {
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"}`)
})
@@ -461,13 +442,22 @@ func TestOrganizationsService_AddTeamMembership(t *testing.T) {
setup()
defer teardown()
opt := &OrganizationAddTeamMembershipOptions{Role: "maintainer"}
mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) {
v := new(OrganizationAddTeamMembershipOptions)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PUT")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, opt) {
t.Errorf("Request body = %+v, want %+v", v, opt)
}
fmt.Fprint(w, `{"url":"u", "state":"pending"}`)
})
membership, _, err := client.Organizations.AddTeamMembership(1, "u")
membership, _, err := client.Organizations.AddTeamMembership(1, "u", opt)
if err != nil {
t.Errorf("Organizations.AddTeamMembership returned error: %v", err)
}
@@ -484,7 +474,6 @@ func TestOrganizationsService_RemoveTeamMembership(t *testing.T) {
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)
})

View File

@@ -41,6 +41,8 @@ type PullRequest struct {
HTMLURL *string `json:"html_url,omitempty"`
IssueURL *string `json:"issue_url,omitempty"`
StatusesURL *string `json:"statuses_url,omitempty"`
DiffURL *string `json:"diff_url,omitempty"`
PatchURL *string `json:"patch_url,omitempty"`
Head *PullRequestBranch `json:"head,omitempty"`
Base *PullRequestBranch `json:"base,omitempty"`
@@ -73,6 +75,15 @@ type PullRequestListOptions struct {
// Base filters pull requests by base branch name.
Base string `url:"base,omitempty"`
// Sort specifies how to sort pull requests. Possible values are: created,
// updated, popularity, long-running. Default is "created".
Sort string `url:"sort,omitempty"`
// Direction in which to sort pull requests. Possible values are: asc, desc.
// If Sort is "created" or not specified, Default is "desc", otherwise Default
// is "asc"
Direction string `url:"direction,omitempty"`
ListOptions
}

View File

@@ -93,7 +93,7 @@ func (s *PullRequestsService) GetComment(owner string, repo string, number int)
// CreateComment creates a new comment on the specified pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/comments/#get-a-single-comment
// GitHub API docs: https://developer.github.com/v3/pulls/comments/#create-a-comment
func (s *PullRequestsService) CreateComment(owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number)
req, err := s.client.NewRequest("POST", u, comment)

View File

@@ -20,15 +20,17 @@ func TestPullRequestsService_List(t *testing.T) {
mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{
"state": "closed",
"head": "h",
"base": "b",
"page": "2",
"state": "closed",
"head": "h",
"base": "b",
"sort": "created",
"direction": "desc",
"page": "2",
})
fmt.Fprint(w, `[{"number":1}]`)
})
opt := &PullRequestListOptions{"closed", "h", "b", ListOptions{Page: 2}}
opt := &PullRequestListOptions{"closed", "h", "b", "created", "desc", ListOptions{Page: 2}}
pulls, _, err := client.PullRequests.List("o", "r", opt)
if err != nil {
@@ -98,6 +100,29 @@ func TestPullRequestsService_Get_headAndBase(t *testing.T) {
}
}
func TestPullRequestService_Get_DiffURLAndPatchURL(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,
"diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
"patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"}`)
})
pull, _, err := client.PullRequests.Get("o", "r", 1)
if err != nil {
t.Errorf("PullRequests.Get returned error: %v", err)
}
want := &PullRequest{Number: Int(1), DiffURL: String("https://github.com/octocat/Hello-World/pull/1347.diff"), PatchURL: String("https://github.com/octocat/Hello-World/pull/1347.patch")}
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)

View File

@@ -49,6 +49,9 @@ type Repository struct {
Organization *Organization `json:"organization,omitempty"`
Permissions *map[string]bool `json:"permissions,omitempty"`
// Only provided when using RepositoriesService.Get while in preview
License *License `json:"license,omitempty"`
// Additional mutable fields when creating and editing a repository
Private *bool `json:"private"`
HasIssues *bool `json:"has_issues"`
@@ -255,6 +258,10 @@ func (s *RepositoriesService) Get(owner, repo string) (*Repository, *Response, e
return nil, nil, err
}
// TODO: remove custom Accept header when the license support fully launches
// https://developer.github.com/v3/licenses/#get-a-repositorys-license
req.Header.Set("Accept", mediaTypeLicensesPreview)
repository := new(Repository)
resp, err := s.client.Do(req, repository)
if err != nil {

View File

@@ -22,6 +22,8 @@ func (s *RepositoriesService) ListCollaborators(owner, repo string, opt *ListOpt
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
users := new([]User)
resp, err := s.client.Do(req, users)
if err != nil {
@@ -49,15 +51,33 @@ func (s *RepositoriesService) IsCollaborator(owner, repo, user string) (bool, *R
return isCollab, resp, err
}
// RepositoryAddCollaboratorOptions specifies the optional parameters to the
// RepositoriesService.AddCollaborator method.
type RepositoryAddCollaboratorOptions struct {
// Permission specifies the permission to grant the user on this repository.
// Possible values are:
// pull - team members can pull, but not push to or administer this repository
// push - team members can pull and push, but not administer this repository
// admin - team members can pull, push and administer this repository
//
// Default value is "pull". This option is only valid for organization-owned repositories.
Permission string `json:"permission,omitempty"`
}
// AddCollaborator adds the specified Github user as collaborator to the given repo.
//
// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#add-collaborator
func (s *RepositoriesService) AddCollaborator(owner, repo, user string) (*Response, error) {
func (s *RepositoriesService) AddCollaborator(owner, repo, user string, opt *RepositoryAddCollaboratorOptions) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user)
req, err := s.client.NewRequest("PUT", u, nil)
req, err := s.client.NewRequest("PUT", u, opt)
if err != nil {
return nil, err
}
if opt != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
return s.client.Do(req, nil)
}

View File

@@ -6,6 +6,7 @@
package github
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
@@ -18,6 +19,7 @@ func TestRepositoriesService_ListCollaborators(t *testing.T) {
mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
testFormValues(t, r, values{"page": "2"})
fmt.Fprintf(w, `[{"id":1}, {"id":2}]`)
})
@@ -86,19 +88,29 @@ func TestRepositoriesService_AddCollaborator(t *testing.T) {
setup()
defer teardown()
opt := &RepositoryAddCollaboratorOptions{Permission: "admin"}
mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
v := new(RepositoryAddCollaboratorOptions)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PUT")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, opt) {
t.Errorf("Request body = %+v, want %+v", v, opt)
}
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Repositories.AddCollaborator("o", "r", "u")
_, err := client.Repositories.AddCollaborator("o", "r", "u", opt)
if err != nil {
t.Errorf("Repositories.AddCollaborator returned error: %v", err)
}
}
func TestRepositoriesService_AddCollaborator_invalidUser(t *testing.T) {
_, err := client.Repositories.AddCollaborator("%", "%", "%")
_, err := client.Repositories.AddCollaborator("%", "%", "%", nil)
testURLParseError(t, err)
}

View File

@@ -61,7 +61,8 @@ func (c CommitFile) String() string {
// CommitsComparison is the result of comparing two commits.
// See CompareCommits() for details.
type CommitsComparison struct {
BaseCommit *RepositoryCommit `json:"base_commit,omitempty"`
BaseCommit *RepositoryCommit `json:"base_commit,omitempty"`
MergeBaseCommit *RepositoryCommit `json:"merge_base_commit,omitempty"`
// Head can be 'behind' or 'ahead'
Status *string `json:"status,omitempty"`

View File

@@ -13,22 +13,25 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"path"
)
// RepositoryContent represents a file or directory in a github repository.
type RepositoryContent struct {
Type *string `json:"type,omitempty"`
Encoding *string `json:"encoding,omitempty"`
Size *int `json:"size,omitempty"`
Name *string `json:"name,omitempty"`
Path *string `json:"path,omitempty"`
Content *string `json:"content,omitempty"`
SHA *string `json:"sha,omitempty"`
URL *string `json:"url,omitempty"`
GitURL *string `json:"giturl,omitempty"`
HTMLURL *string `json:"htmlurl,omitempty"`
Type *string `json:"type,omitempty"`
Encoding *string `json:"encoding,omitempty"`
Size *int `json:"size,omitempty"`
Name *string `json:"name,omitempty"`
Path *string `json:"path,omitempty"`
Content *string `json:"content,omitempty"`
SHA *string `json:"sha,omitempty"`
URL *string `json:"url,omitempty"`
GitURL *string `json:"git_url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
DownloadURL *string `json:"download_url,omitempty"`
}
// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile.
@@ -90,6 +93,32 @@ func (s *RepositoriesService) GetReadme(owner, repo string, opt *RepositoryConte
return readme, resp, err
}
// DownloadContents returns an io.ReadCloser that reads the contents of the
// specified file. This function will work with files of any size, as opposed
// to GetContents which is limited to 1 Mb files. It is the caller's
// responsibility to close the ReadCloser.
func (s *RepositoriesService) DownloadContents(owner, repo, filepath string, opt *RepositoryContentGetOptions) (io.ReadCloser, error) {
dir := path.Dir(filepath)
filename := path.Base(filepath)
_, dirContents, _, err := s.GetContents(owner, repo, dir, opt)
if err != nil {
return nil, err
}
for _, contents := range dirContents {
if *contents.Name == filename {
if contents.DownloadURL == nil || *contents.DownloadURL == "" {
return nil, fmt.Errorf("No download link found for %s", filepath)
}
resp, err := s.client.client.Get(*contents.DownloadURL)
if err != nil {
return nil, err
}
return resp.Body, nil
}
}
return nil, fmt.Errorf("No file named %s found in %s", filename, dir)
}
// GetContents can return either the metadata and content of a single file
// (when path references a file) or the metadata of all the files and/or
// subdirectories of a directory (when path references a directory). To make it

View File

@@ -2,6 +2,7 @@ package github
import (
"fmt"
"io/ioutil"
"net/http"
"reflect"
"testing"
@@ -54,7 +55,70 @@ func TestRepositoriesService_GetReadme(t *testing.T) {
}
}
func TestRepositoriesService_GetContent_File(t *testing.T) {
func TestRepositoriesService_DownloadContents_Success(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{
"type": "file",
"name": "f",
"download_url": "`+server.URL+`/download/f"
}]`)
})
mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, "foo")
})
r, err := client.Repositories.DownloadContents("o", "r", "d/f", nil)
if err != nil {
t.Errorf("Repositories.DownloadContents returned error: %v", err)
}
bytes, err := ioutil.ReadAll(r)
if err != nil {
t.Errorf("Error reading response body: %v", err)
}
r.Close()
if got, want := string(bytes), "foo"; got != want {
t.Errorf("Repositories.DownloadContents returned %v, want %v", got, want)
}
}
func TestRepositoriesService_DownloadContents_NoDownloadURL(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{
"type": "file",
"name": "f",
}]`)
})
_, err := client.Repositories.DownloadContents("o", "r", "d/f", nil)
if err == nil {
t.Errorf("Repositories.DownloadContents did not return expected error")
}
}
func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[]`)
})
_, err := client.Repositories.DownloadContents("o", "r", "d/f", nil)
if err == nil {
t.Errorf("Repositories.DownloadContents did not return expected error")
}
}
func TestRepositoriesService_GetContents_File(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
@@ -69,7 +133,7 @@ func TestRepositoriesService_GetContent_File(t *testing.T) {
})
fileContents, _, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{})
if err != nil {
t.Errorf("Repositories.GetContents_File returned error: %v", err)
t.Errorf("Repositories.GetContents returned error: %v", err)
}
want := &RepositoryContent{Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Encoding: String("base64"), Path: String("LICENSE")}
if !reflect.DeepEqual(fileContents, want) {
@@ -77,7 +141,7 @@ func TestRepositoriesService_GetContent_File(t *testing.T) {
}
}
func TestRepositoriesService_GetContent_Directory(t *testing.T) {
func TestRepositoriesService_GetContents_Directory(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
@@ -88,7 +152,7 @@ func TestRepositoriesService_GetContent_Directory(t *testing.T) {
"path": "lib"
},
{
"type": "file",
"type": "file",
"size": 20678,
"name": "LICENSE",
"path": "LICENSE"
@@ -96,7 +160,7 @@ func TestRepositoriesService_GetContent_Directory(t *testing.T) {
})
_, directoryContents, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{})
if err != nil {
t.Errorf("Repositories.GetContents_Directory returned error: %v", err)
t.Errorf("Repositories.GetContents returned error: %v", err)
}
want := []*RepositoryContent{{Type: String("dir"), Name: String("lib"), Path: String("lib")},
{Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Path: String("LICENSE")}}

View File

@@ -27,13 +27,13 @@ type Deployment struct {
// 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"`
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
@@ -69,9 +69,6 @@ func (s *RepositoriesService) ListDeployments(owner, repo string, opt *Deploymen
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 {
@@ -92,9 +89,6 @@ func (s *RepositoriesService) CreateDeployment(owner, repo string, request *Depl
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 {
@@ -138,9 +132,6 @@ func (s *RepositoriesService) ListDeploymentStatuses(owner, repo string, deploym
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 {
@@ -161,9 +152,6 @@ func (s *RepositoriesService) CreateDeploymentStatus(owner, repo string, deploym
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 {

View File

@@ -164,6 +164,18 @@ func (s *RepositoriesService) DeleteHook(owner, repo string, id int) (*Response,
return s.client.Do(req, nil)
}
// PingHook triggers a 'ping' event to be sent to the Hook.
//
// GitHub API docs: https://developer.github.com/v3/repos/hooks/#ping-a-hook
func (s *RepositoriesService) PingHook(owner, repo string, id int) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/hooks/%d/pings", owner, repo, id)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// TestHook triggers a test Hook by github.
//
// GitHub API docs: http://developer.github.com/v3/repos/hooks/#test-a-push-hook
@@ -176,34 +188,7 @@ func (s *RepositoriesService) TestHook(owner, repo string, id int) (*Response, e
return s.client.Do(req, nil)
}
// ServiceHook represents a hook that has configuration settings, a list of
// available events, and default events.
type ServiceHook struct {
Name *string `json:"name,omitempty"`
Events []string `json:"events,omitempty"`
SupportedEvents []string `json:"supported_events,omitempty"`
Schema [][]string `json:"schema,omitempty"`
}
func (s *ServiceHook) String() string {
return Stringify(s)
}
// ListServiceHooks lists all of the available service hooks.
//
// GitHub API docs: https://developer.github.com/webhooks/#services
// ListServiceHooks is deprecated. Use Client.ListServiceHooks instead.
func (s *RepositoriesService) ListServiceHooks() ([]ServiceHook, *Response, error) {
u := "hooks"
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
hooks := new([]ServiceHook)
resp, err := s.client.Do(req, hooks)
if err != nil {
return nil, resp, err
}
return *hooks, resp, err
return s.client.ListServiceHooks()
}

View File

@@ -153,6 +153,20 @@ func TestRepositoriesService_DeleteHook_invalidOwner(t *testing.T) {
testURLParseError(t, err)
}
func TestRepositoriesService_PingHook(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
})
_, err := client.Repositories.PingHook("o", "r", 1)
if err != nil {
t.Errorf("Repositories.PingHook returned error: %v", err)
}
}
func TestRepositoriesService_TestHook(t *testing.T) {
setup()
defer teardown()
@@ -171,35 +185,3 @@ func TestRepositoriesService_TestHook_invalidOwner(t *testing.T) {
_, err := client.Repositories.TestHook("%", "%", 1)
testURLParseError(t, err)
}
func TestRepositoriesService_ListServiceHooks(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/hooks", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{
"name":"n",
"events":["e"],
"supported_events":["s"],
"schema":[
["a", "b"]
]
}]`)
})
hooks, _, err := client.Repositories.ListServiceHooks()
if err != nil {
t.Errorf("Repositories.ListHooks returned error: %v", err)
}
want := []ServiceHook{{
Name: String("n"),
Events: []string{"e"},
SupportedEvents: []string{"s"},
Schema: [][]string{{"a", "b"}},
}}
if !reflect.DeepEqual(hooks, want) {
t.Errorf("Repositories.ListServiceHooks returned %+v, want %+v", hooks, want)
}
}

View File

@@ -8,7 +8,9 @@ package github
import (
"errors"
"fmt"
"io"
"mime"
"net/http"
"os"
"path/filepath"
)
@@ -85,8 +87,27 @@ func (s *RepositoriesService) ListReleases(owner, repo string, opt *ListOptions)
// GitHub API docs: http://developer.github.com/v3/repos/releases/#get-a-single-release
func (s *RepositoriesService) GetRelease(owner, repo string, id int) (*RepositoryRelease, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id)
return s.getSingleRelease(u)
}
req, err := s.client.NewRequest("GET", u, nil)
// GetLatestRelease fetches the latest published release for the repository.
//
// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-the-latest-release
func (s *RepositoriesService) GetLatestRelease(owner, repo string) (*RepositoryRelease, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/releases/latest", owner, repo)
return s.getSingleRelease(u)
}
// GetReleaseByTag fetches a release with the specified tag.
//
// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name
func (s *RepositoriesService) GetReleaseByTag(owner, repo, tag string) (*RepositoryRelease, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/releases/tags/%s", owner, repo, tag)
return s.getSingleRelease(u)
}
func (s *RepositoriesService) getSingleRelease(url string) (*RepositoryRelease, *Response, error) {
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
@@ -192,6 +213,54 @@ func (s *RepositoriesService) GetReleaseAsset(owner, repo string, id int) (*Rele
return asset, resp, err
}
// DowloadReleaseAsset downloads a release asset.
//
// DowloadReleaseAsset returns an io.ReadCloser that reads the contents of the
// specified release asset. It is the caller's responsibility to close the ReadCloser.
//
// GitHub API docs : http://developer.github.com/v3/repos/releases/#get-a-single-release-asset
func (s *RepositoriesService) DownloadReleaseAsset(owner, repo string, id int) (io.ReadCloser, error) {
u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", defaultMediaType)
var resp *http.Response
if s.client.client.Transport == nil {
resp, err = http.DefaultTransport.RoundTrip(req)
} else {
resp, err = s.client.client.Transport.RoundTrip(req)
}
if err != nil {
return nil, err
}
// GitHub API streamed the asset directly
if resp.StatusCode == http.StatusOK {
return resp.Body, nil
}
if resp.StatusCode != http.StatusFound {
return nil, fmt.Errorf("Expected status code 200 or 302, got %d", resp.StatusCode)
}
// GitHub API redirected to pre-signed S3 URL
downloadURL, err := resp.Location()
if err != nil {
return nil, err
}
resp, err = http.Get(downloadURL.String())
if err != nil {
return nil, err
}
return resp.Body, nil
}
// EditReleaseAsset edits a repository release asset.
//
// GitHub API docs : http://developer.github.com/v3/repos/releases/#edit-a-release-asset

View File

@@ -6,8 +6,10 @@
package github
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"reflect"
@@ -55,6 +57,46 @@ func TestRepositoriesService_GetRelease(t *testing.T) {
}
}
func TestRepositoriesService_GetLatestRelease(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/releases/latest", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":3}`)
})
release, resp, err := client.Repositories.GetLatestRelease("o", "r")
if err != nil {
t.Errorf("Repositories.GetLatestRelease returned error: %v\n%v", err, resp.Body)
}
want := &RepositoryRelease{ID: Int(3)}
if !reflect.DeepEqual(release, want) {
t.Errorf("Repositories.GetLatestRelease returned %+v, want %+v", release, want)
}
}
func TestRepositoriesService_GetReleaseByTag(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/releases/tags/foo", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":13}`)
})
release, resp, err := client.Repositories.GetReleaseByTag("o", "r", "foo")
if err != nil {
t.Errorf("Repositories.GetReleaseByTag returned error: %v\n%v", err, resp.Body)
}
want := &RepositoryRelease{ID: Int(13)}
if !reflect.DeepEqual(release, want) {
t.Errorf("Repositories.GetReleaseByTag returned %+v, want %+v", release, want)
}
}
func TestRepositoriesService_CreateRelease(t *testing.T) {
setup()
defer teardown()
@@ -164,6 +206,64 @@ func TestRepositoriesService_GetReleaseAsset(t *testing.T) {
}
}
func TestRepositoriesService_DownloadReleaseAsset_Stream(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", defaultMediaType)
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt")
fmt.Fprint(w, "Hello World")
})
reader, err := client.Repositories.DowloadReleaseAsset("o", "r", 1)
if err != nil {
t.Errorf("Repositories.DowloadReleaseAsset returned error: %v", err)
}
want := []byte("Hello World")
content, err := ioutil.ReadAll(reader)
if err != nil {
t.Errorf("Repositories.DowloadReleaseAsset returned bad reader: %v", err)
}
if !bytes.Equal(want, content) {
t.Errorf("Repositories.DowloadReleaseAsset returned %+v, want %+v", content, want)
}
}
func TestRepositoriesService_DownloadReleaseAsset_Redirect(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", defaultMediaType)
w.Header().Set("Location", server.URL+"/github-cloud/releases/1/hello-world.txt")
w.WriteHeader(http.StatusFound)
})
mux.HandleFunc("/github-cloud/releases/1/hello-world.txt", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt")
fmt.Fprint(w, "Hello World")
})
reader, err := client.Repositories.DowloadReleaseAsset("o", "r", 1)
if err != nil {
t.Errorf("Repositories.DowloadReleaseAsset returned error: %v", err)
}
want := []byte("Hello World")
content, err := ioutil.ReadAll(reader)
if err != nil {
t.Errorf("Repositories.DowloadReleaseAsset returned bad reader: %v", err)
}
if !bytes.Equal(want, content) {
t.Errorf("Repositories.DowloadReleaseAsset returned %+v, want %+v", content, want)
}
}
func TestRepositoriesService_EditReleaseAsset(t *testing.T) {
setup()
defer teardown()

View File

@@ -45,7 +45,6 @@ func TestRepositoriesService_List_specifiedUser(t *testing.T) {
"direction": "asc",
"page": "2",
})
fmt.Fprint(w, `[{"id":1}]`)
})
@@ -191,7 +190,8 @@ func TestRepositoriesService_Get(t *testing.T) {
mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":1,"name":"n","description":"d","owner":{"login":"l"}}`)
testHeader(t, r, "Accept", mediaTypeLicensesPreview)
fmt.Fprint(w, `{"id":1,"name":"n","description":"d","owner":{"login":"l"},"license":{"key":"mit"}}`)
})
repo, _, err := client.Repositories.Get("o", "r")
@@ -199,7 +199,7 @@ func TestRepositoriesService_Get(t *testing.T) {
t.Errorf("Repositories.Get returned error: %v", err)
}
want := &Repository{ID: Int(1), Name: String("n"), Description: String("d"), Owner: &User{Login: String("l")}}
want := &Repository{ID: Int(1), Name: String("n"), Description: String("d"), Owner: &User{Login: String("l")}, License: &License{Key: String("mit")}}
if !reflect.DeepEqual(repo, want) {
t.Errorf("Repositories.Get returned %+v, want %+v", repo, want)
}

View File

@@ -59,6 +59,10 @@ type User struct {
// 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"`
// Permissions identifies the permissions that a user has on a given
// repository. This is only populated when calling Repositories.ListCollaborators.
Permissions *map[string]bool `json:"permissions,omitempty"`
}
func (u User) String() string {

Some files were not shown because too many files have changed in this diff Show More