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.
| @ -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 | |||||
| } | |||||
| @ -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) | |||||
| } | |||||