Browse Source

first pass at pull request methods

This includes most API methods on pull requests (with the exception of
listing commits and the merge functions), as well as all of the methods
for pull request comments. (Fixes #13)

This also fixes a few oversights in the issues API.
Will Norris 13 years ago
parent
commit
76aa5bccb6
5 changed files with 600 additions and 17 deletions
  1. +2
    -0
      github/github.go
  2. +37
    -14
      github/issues.go
  3. +11
    -3
      github/issues_test.go
  4. +238
    -0
      github/pulls.go
  5. +312
    -0
      github/pulls_test.go

+ 2
- 0
github/github.go View File

@ -78,6 +78,7 @@ type Client struct {
Issues *IssuesService
Organizations *OrganizationsService
PullRequests *PullRequestsService
Repositories *RepositoriesService
Users *UsersService
}
@ -102,6 +103,7 @@ func NewClient(httpClient *http.Client) *Client {
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent}
c.Issues = &IssuesService{client: c}
c.Organizations = &OrganizationsService{client: c}
c.PullRequests = &PullRequestsService{client: c}
c.Repositories = &RepositoriesService{client: c}
c.Users = &UsersService{client: c}
return c


+ 37
- 14
github/issues.go View File

@ -51,25 +51,25 @@ type IssueComment struct {
type IssueListOptions struct {
// Filter specifies which issues to list. Possible values are: assigned,
// created, mentioned, subscribed, all. Default is "assigned".
Filter string
Filter string
// State filters issues based on their state. Possible values are: open,
// closed. Default is "open".
State string
State string
// Labels filters issues based on their label.
Labels []string
Labels []string
// Sort specifies how to sort issues. Possible values are: created, updated,
// and comments. Default value is "assigned".
Sort string
Sort string
// Direction in which to sort issues. Possible values are: asc, desc.
// Default is "asc".
Direction string
// Since filters issues by time.
Since time.Time
Since time.Time
}
// List the issues for the authenticated user. If all is true, list issues
@ -132,32 +132,32 @@ type IssueListByRepoOptions struct {
// State filters issues based on their state. Possible values are: open,
// closed. Default is "open".
State string
State string
// Assignee filters issues based on their assignee. Possible values are a
// user name, "none" for issues that are not assigned, "*" for issues with
// any assigned user.
Assignee string
Assignee string
// Assignee filters issues based on their creator.
Creator string
Creator string
// Assignee filters issues to those mentioned a specific user.
Mentioned string
// Labels filters issues based on their label.
Labels []string
Labels []string
// Sort specifies how to sort issues. Possible values are: created, updated,
// and comments. Default value is "assigned".
Sort string
Sort string
// Direction in which to sort issues. Possible values are: asc, desc.
// Default is "asc".
Direction string
// Since filters issues by time.
Since time.Time
Since time.Time
}
// ListByRepo lists the issues for the specified repository.
@ -262,11 +262,24 @@ func (s *IssuesService) CheckAssignee(owner string, repo string, user string) (b
return parseBoolResponse(err)
}
// IssueListCommentsOptions specifies the optional parameters to the
// IssuesService.ListComments method.
type IssueListCommentsOptions struct {
// Sort specifies how to sort comments. Possible values are: created, updated.
Sort string
// Direction in which to sort comments. Possible values are: asc, desc.
Direction string
// Since filters comments by time.
Since time.Time
}
// ListComments lists all comments on the specified issue. Specifying an issue
// number of 0 will return all comments on all issues for the repository.
//
// GitHub API docs: http://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
func (s *IssuesService) ListComments(owner string, repo string, number int) ([]IssueComment, error) {
func (s *IssuesService) ListComments(owner string, repo string, number int, opt *IssueListCommentsOptions) ([]IssueComment, error) {
var u string
if number == 0 {
u = fmt.Sprintf("repos/%v/%v/issues/comments", owner, repo)
@ -274,6 +287,17 @@ func (s *IssuesService) ListComments(owner string, repo string, number int) ([]I
u = fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number)
}
if opt != nil {
params := url.Values{
"sort": {opt.Sort},
"direction": {opt.Direction},
}
if !opt.Since.IsZero() {
params.Add("since", opt.Since.Format(time.RFC3339))
}
u += "?" + params.Encode()
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
@ -326,7 +350,7 @@ func (s *IssuesService) EditComment(owner string, repo string, id int, comment *
return c, err
}
// DeleteComment updates an issue comment.
// DeleteComment deletes an issue comment.
//
// GitHub API docs: http://developer.github.com/v3/issues/comments/#delete-a-comment
func (s *IssuesService) DeleteComment(owner string, repo string, id int) error {
@ -335,7 +359,6 @@ func (s *IssuesService) DeleteComment(owner string, repo string, id int) error {
if err != nil {
return err
}
_, err = s.client.Do(req, nil)
return err
}

+ 11
- 3
github/issues_test.go View File

@ -315,10 +315,18 @@ func TestIssuesService_ListComments_allIssues(t *testing.T) {
mux.HandleFunc("/repos/o/r/issues/comments", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{
"sort": "updated",
"direction": "desc",
"since": "2002-02-10T15:30:00Z",
})
fmt.Fprint(w, `[{"id":1}]`)
})
comments, err := client.Issues.ListComments("o", "r", 0)
opt := &IssueListCommentsOptions{"updated", "desc",
time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
}
comments, err := client.Issues.ListComments("o", "r", 0, opt)
if err != nil {
t.Errorf("Issues.ListComments returned error: %v", err)
}
@ -338,7 +346,7 @@ func TestIssuesService_ListComments_specificIssue(t *testing.T) {
fmt.Fprint(w, `[{"id":1}]`)
})
comments, err := client.Issues.ListComments("o", "r", 1)
comments, err := client.Issues.ListComments("o", "r", 1, nil)
if err != nil {
t.Errorf("Issues.ListComments returned error: %v", err)
}
@ -350,7 +358,7 @@ func TestIssuesService_ListComments_specificIssue(t *testing.T) {
}
func TestIssuesService_ListComments_invalidOwner(t *testing.T) {
_, err := client.Issues.ListComments("%", "r", 1)
_, err := client.Issues.ListComments("%", "r", 1, nil)
testURLParseError(t, err)
}


+ 238
- 0
github/pulls.go View File

@ -0,0 +1,238 @@
// Copyright 2013 Google. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
package github
import (
"fmt"
"net/url"
"time"
)
// PullRequestsService handles communication with the pull request related
// methods of the GitHub API.
//
// GitHub API docs: http://developer.github.com/v3/pulls/
type PullRequestsService struct {
client *Client
}
// PullRequest represents a GitHub pull request on a repository.
type PullRequest struct {
Number int `json:"number,omitempty"`
State string `json:"state,omitempty"`
Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
ClosedAt *time.Time `json:"closed_at,omitempty"`
MergedAt *time.Time `json:"merged_at,omitempty"`
User *User `json:"user,omitempty"`
Merged bool `json:"merged,omitempty"`
Mergeable bool `json:"mergeable,omitempty"`
MergedBy *User `json:"merged_by,omitempty"`
Comments int `json:"comments,omitempty"`
Commits int `json:"commits,omitempty"`
Additions int `json:"additions,omitempty"`
Deletions int `json:"deletions,omitempty"`
ChangedFiles int `json:"changed_files,omitempty"`
// TODO(willnorris): add head and base once we have a Commit struct defined somewhere
}
// PullRequestComment represents a comment left on a pull request.
type PullRequestComment struct {
ID int `json:"id,omitempty"`
Body string `json:"body,omitempty"`
Path string `json:"path,omitempty"`
Position int `json:"position,omitempty"`
CommitID string `json:"commit_id,omitempty"`
User *User `json:"user,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
// PullRequestListOptions specifies the optional parameters to the
// PullRequestsService.List method.
type PullRequestListOptions struct {
// State filters pull requests based on their state. Possible values are:
// open, closed. Default is "open".
State string
// Head filters pull requests by head user and branch name in the format of:
// "user:ref-name".
Head string
// Base filters pull requests by base branch name.
Base string
}
// List the pull requests for the specified repository.
//
// GitHub API docs: http://developer.github.com/v3/pulls/#list-pull-requests
func (s *PullRequestsService) List(owner string, repo string, opt *PullRequestListOptions) ([]PullRequest, error) {
u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
if opt != nil {
params := url.Values{
"state": {opt.State},
"head": {opt.Head},
"base": {opt.Base},
}
u += "?" + params.Encode()
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
pulls := new([]PullRequest)
_, err = s.client.Do(req, pulls)
return *pulls, err
}
// Get a single pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request
func (s *PullRequestsService) Get(owner string, repo string, number int) (*PullRequest, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
pull := new(PullRequest)
_, err = s.client.Do(req, pull)
return pull, err
}
// 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, error) {
u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo)
req, err := s.client.NewRequest("POST", u, pull)
if err != nil {
return nil, err
}
p := new(PullRequest)
_, err = s.client.Do(req, p)
return p, err
}
// Edit a pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/#update-a-pull-request
func (s *PullRequestsService) Edit(owner string, repo string, number int, pull *PullRequest) (*PullRequest, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number)
req, err := s.client.NewRequest("PATCH", u, pull)
if err != nil {
return nil, err
}
p := new(PullRequest)
_, err = s.client.Do(req, p)
return p, err
}
// PullRequestListCommentsOptions specifies the optional parameters to the
// PullRequestsService.ListComments method.
type PullRequestListCommentsOptions struct {
// Sort specifies how to sort comments. Possible values are: created, updated.
Sort string
// Direction in which to sort comments. Possible values are: asc, desc.
Direction string
// Since filters comments by time.
Since time.Time
}
// ListComments lists all comments on the specified pull request. Specifying a
// pull request number of 0 will return all comments on all pull requests for
// the repository.
//
// GitHub API docs: https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request
func (s *PullRequestsService) ListComments(owner string, repo string, number int, opt *PullRequestListCommentsOptions) ([]PullRequestComment, error) {
var u string
if number == 0 {
u = fmt.Sprintf("repos/%v/%v/pulls/comments", owner, repo)
} else {
u = fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number)
}
if opt != nil {
params := url.Values{
"sort": {opt.Sort},
"direction": {opt.Direction},
}
if !opt.Since.IsZero() {
params.Add("since", opt.Since.Format(time.RFC3339))
}
u += "?" + params.Encode()
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
comments := new([]PullRequestComment)
_, err = s.client.Do(req, comments)
return *comments, err
}
// GetComment fetches the specified pull request comment.
//
// GitHub API docs: https://developer.github.com/v3/pulls/comments/#get-a-single-comment
func (s *PullRequestsService) GetComment(owner string, repo string, number int) (*PullRequestComment, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
comment := new(PullRequestComment)
_, err = s.client.Do(req, comment)
return comment, err
}
// CreateComment creates a new comment on the specified pull request.
//
// GitHub API docs: https://developer.github.com/v3/pulls/comments/#get-a-single-comment
func (s *PullRequestsService) CreateComment(owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number)
req, err := s.client.NewRequest("POST", u, comment)
if err != nil {
return nil, err
}
c := new(PullRequestComment)
_, err = s.client.Do(req, c)
return c, err
}
// EditComment updates a pull request comment.
//
// GitHub API docs: https://developer.github.com/v3/pulls/comments/#edit-a-comment
func (s *PullRequestsService) EditComment(owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number)
req, err := s.client.NewRequest("PATCH", u, comment)
if err != nil {
return nil, err
}
c := new(PullRequestComment)
_, err = s.client.Do(req, c)
return c, err
}
// DeleteComment deletes a pull request comment.
//
// GitHub API docs: https://developer.github.com/v3/pulls/comments/#delete-a-comment
func (s *PullRequestsService) DeleteComment(owner string, repo string, number int) error {
u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return err
}
_, err = s.client.Do(req, nil)
return err
}

+ 312
- 0
github/pulls_test.go View File

@ -0,0 +1,312 @@
// Copyright 2013 Google. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
package github
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
"time"
)
func TestPullRequestsService_List(t *testing.T) {
setup()
defer teardown()
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",
})
fmt.Fprint(w, `[{"number":1}]`)
})
opt := &PullRequestListOptions{"closed", "h", "b"}
pulls, err := client.PullRequests.List("o", "r", opt)
if err != nil {
t.Errorf("PullRequests.List returned error: %v", err)
}
want := []PullRequest{PullRequest{Number: 1}}
if !reflect.DeepEqual(pulls, want) {
t.Errorf("PullRequests.List returned %+v, want %+v", pulls, want)
}
}
func TestPullRequestsService_List_invalidOwner(t *testing.T) {
_, err := client.PullRequests.List("%", "r", nil)
testURLParseError(t, err)
}
func TestPullRequestsService_Get(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}`)
})
pull, err := client.PullRequests.Get("o", "r", 1)
if err != nil {
t.Errorf("PullRequests.Get returned error: %v", err)
}
want := &PullRequest{Number: 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)
}
func TestPullRequestsService_Create(t *testing.T) {
setup()
defer teardown()
input := &PullRequest{Title: "t"}
mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) {
v := new(PullRequest)
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, `{"number":1}`)
})
pull, err := client.PullRequests.Create("o", "r", input)
if err != nil {
t.Errorf("PullRequests.Create returned error: %v", err)
}
want := &PullRequest{Number: 1}
if !reflect.DeepEqual(pull, want) {
t.Errorf("PullRequests.Create returned %+v, want %+v", pull, want)
}
}
func TestPullRequestsService_Create_invalidOwner(t *testing.T) {
_, err := client.PullRequests.Create("%", "r", nil)
testURLParseError(t, err)
}
func TestPullRequestsService_Edit(t *testing.T) {
setup()
defer teardown()
input := &PullRequest{Title: "t"}
mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) {
v := new(PullRequest)
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, `{"number":1}`)
})
pull, err := client.PullRequests.Edit("o", "r", 1, input)
if err != nil {
t.Errorf("PullRequests.Edit returned error: %v", err)
}
want := &PullRequest{Number: 1}
if !reflect.DeepEqual(pull, want) {
t.Errorf("PullRequests.Edit returned %+v, want %+v", pull, want)
}
}
func TestPullRequestsService_Edit_invalidOwner(t *testing.T) {
_, err := client.PullRequests.Edit("%", "r", 1, nil)
testURLParseError(t, err)
}
func TestPullRequestsService_ListComments_allPulls(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/pulls/comments", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{
"sort": "updated",
"direction": "desc",
"since": "2002-02-10T15:30:00Z",
})
fmt.Fprint(w, `[{"id":1}]`)
})
opt := &PullRequestListCommentsOptions{"updated", "desc",
time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
}
pulls, err := client.PullRequests.ListComments("o", "r", 0, opt)
if err != nil {
t.Errorf("PullRequests.ListComments returned error: %v", err)
}
want := []PullRequestComment{PullRequestComment{ID: 1}}
if !reflect.DeepEqual(pulls, want) {
t.Errorf("PullRequests.ListComments returned %+v, want %+v", pulls, want)
}
}
func TestPullRequestsService_ListComments_specificPull(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/pulls/1/comments", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{"id":1}]`)
})
pulls, err := client.PullRequests.ListComments("o", "r", 1, nil)
if err != nil {
t.Errorf("PullRequests.ListComments returned error: %v", err)
}
want := []PullRequestComment{PullRequestComment{ID: 1}}
if !reflect.DeepEqual(pulls, want) {
t.Errorf("PullRequests.ListComments returned %+v, want %+v", pulls, want)
}
}
func TestPullRequestsService_ListComments_invalidOwner(t *testing.T) {
_, err := client.PullRequests.ListComments("%", "r", 1, nil)
testURLParseError(t, err)
}
func TestPullRequestsService_GetComment(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":1}`)
})
comment, err := client.PullRequests.GetComment("o", "r", 1)
if err != nil {
t.Errorf("PullRequests.GetComment returned error: %v", err)
}
want := &PullRequestComment{ID: 1}
if !reflect.DeepEqual(comment, want) {
t.Errorf("PullRequests.GetComment returned %+v, want %+v", comment, want)
}
}
func TestPullRequestsService_GetComment_invalidOwner(t *testing.T) {
_, err := client.PullRequests.GetComment("%", "r", 1)
testURLParseError(t, err)
}
func TestPullRequestsService_CreateComment(t *testing.T) {
setup()
defer teardown()
input := &PullRequestComment{Body: "b"}
mux.HandleFunc("/repos/o/r/pulls/1/comments", func(w http.ResponseWriter, r *http.Request) {
v := new(PullRequestComment)
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, `{"id":1}`)
})
comment, err := client.PullRequests.CreateComment("o", "r", 1, input)
if err != nil {
t.Errorf("PullRequests.CreateComment returned error: %v", err)
}
want := &PullRequestComment{ID: 1}
if !reflect.DeepEqual(comment, want) {
t.Errorf("PullRequests.CreateComment returned %+v, want %+v", comment, want)
}
}
func TestPullRequestsService_CreateComment_invalidOwner(t *testing.T) {
_, err := client.PullRequests.CreateComment("%", "r", 1, nil)
testURLParseError(t, err)
}
func TestPullRequestsService_EditComment(t *testing.T) {
setup()
defer teardown()
input := &PullRequestComment{Body: "b"}
mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) {
v := new(PullRequestComment)
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}`)
})
comment, err := client.PullRequests.EditComment("o", "r", 1, input)
if err != nil {
t.Errorf("PullRequests.EditComment returned error: %v", err)
}
want := &PullRequestComment{ID: 1}
if !reflect.DeepEqual(comment, want) {
t.Errorf("PullRequests.EditComment returned %+v, want %+v", comment, want)
}
}
func TestPullRequestsService_EditComment_invalidOwner(t *testing.T) {
_, err := client.PullRequests.EditComment("%", "r", 1, nil)
testURLParseError(t, err)
}
func TestPullRequestsService_DeleteComment(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
err := client.PullRequests.DeleteComment("o", "r", 1)
if err != nil {
t.Errorf("PullRequests.DeleteComment returned error: %v", err)
}
}
func TestPullRequestsService_DeleteComment_invalidOwner(t *testing.T) {
err := client.PullRequests.DeleteComment("%", "r", 1)
testURLParseError(t, err)
}

Loading…
Cancel
Save