diff --git a/github/github.go b/github/github.go index 8f9dde6235c..fd3f4651275 100644 --- a/github/github.go +++ b/github/github.go @@ -93,6 +93,9 @@ const ( // https://developer.github.com/changes/2016-11-28-preview-org-membership/ mediaTypeOrgMembershipPreview = "application/vnd.github.korra-preview+json" + + // https://developer.github.com/changes/2017-01-05-commit-search-api/ + mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json" ) // A Client manages communication with the GitHub API. diff --git a/github/search.go b/github/search.go index 579a57d7f41..8fd9430110f 100644 --- a/github/search.go +++ b/github/search.go @@ -21,6 +21,7 @@ type SearchService service type SearchOptions struct { // How to sort the search results. Possible values are: // - for repositories: stars, fork, updated + // - for commits: author-date, committer-date // - for code: indexed // - for issues: comments, created, updated // - for users: followers, repositories, joined @@ -54,6 +55,37 @@ func (s *SearchService) Repositories(query string, opt *SearchOptions) (*Reposit return result, resp, err } +// CommitsSearchResult represents the result of a commits search. +type CommitsSearchResult struct { + Total *int `json:"total_count,omitempty"` + IncompleteResults *bool `json:"incomplete_results,omitempty"` + Commits []*CommitResult `json:"items,omitempty"` +} + +// CommitResult represents a commit object as returned in commit search endpoint response. +type CommitResult struct { + Hash *string `json:"hash,omitempty"` + Message *string `json:"message,omitempty"` + AuthorID *int `json:"author_id,omitempty"` + AuthorName *string `json:"author_name,omitempty"` + AuthorEmail *string `json:"author_email,omitempty"` + AuthorDate *Timestamp `json:"author_date,omitempty"` + CommitterID *int `json:"committer_id,omitempty"` + CommitterName *string `json:"committer_name,omitempty"` + CommitterEmail *string `json:"committer_email,omitempty"` + CommitterDate *Timestamp `json:"committer_date,omitempty"` + Repository *Repository `json:"repository,omitempty"` +} + +// Commits searches commits via various criteria. +// +// GitHub API Docs: https://developer.github.com/v3/search/#search-commits +func (s *SearchService) Commits(query string, opt *SearchOptions) (*CommitsSearchResult, *Response, error) { + result := new(CommitsSearchResult) + resp, err := s.search("commits", query, opt, result) + return result, resp, err +} + // IssuesSearchResult represents the result of an issues search. type IssuesSearchResult struct { Total *int `json:"total_count,omitempty"` @@ -136,7 +168,7 @@ func (s *SearchService) Code(query string, opt *SearchOptions) (*CodeSearchResul } // Helper function that executes search queries against different -// GitHub search types (repositories, code, issues, users) +// GitHub search types (repositories, commits, code, issues, users) func (s *SearchService) search(searchType string, query string, opt *SearchOptions, result interface{}) (*Response, error) { params, err := qs.Values(opt) if err != nil { @@ -150,7 +182,12 @@ func (s *SearchService) search(searchType string, query string, opt *SearchOptio return nil, err } - if opt != nil && opt.TextMatch { + switch { + case searchType == "commits": + // Accept header for search commits preview endpoint + // TODO: remove custom Accept header when this API fully launches. + req.Header.Set("Accept", mediaTypeCommitSearchPreview) + case opt != nil && opt.TextMatch: // Accept header defaults to "application/vnd.github.v3+json" // We change it here to fetch back text-match metadata req.Header.Set("Accept", "application/vnd.github.v3.text-match+json") diff --git a/github/search_test.go b/github/search_test.go index 4626de0ea0e..0427a8bb485 100644 --- a/github/search_test.go +++ b/github/search_test.go @@ -46,6 +46,37 @@ func TestSearchService_Repositories(t *testing.T) { } } +func TestSearchService_Commits(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "author-date", + "order": "desc", + }) + + fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"hash":"random_hash1"},{"hash":"random_hash2"}]}`) + }) + + opts := &SearchOptions{Sort: "author-date", Order: "desc"} + result, _, err := client.Search.Commits("blah", opts) + if err != nil { + t.Errorf("Search.Commits returned error: %v", err) + } + + want := &CommitsSearchResult{ + Total: Int(4), + IncompleteResults: Bool(false), + Commits: []*CommitResult{{Hash: String("random_hash1")}, {Hash: String("random_hash2")}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Commits returned %+v, want %+v", result, want) + } +} + func TestSearchService_Issues(t *testing.T) { setup() defer teardown()