diff --git a/github/git_commits.go b/github/git_commits.go index 82a178d5180..0a39576de86 100644 --- a/github/git_commits.go +++ b/github/git_commits.go @@ -18,6 +18,7 @@ type Commit struct { Message *string `json:"message,omitempty"` Tree *Tree `json:"tree,omitempty"` Parents []Commit `json:"parents,omitempty"` + Stats *CommitStats `json:"stats,omitempty"` } func (c Commit) String() string { diff --git a/github/repos_collaborators.go b/github/repos_collaborators.go index 29722813a31..1dee9560a86 100644 --- a/github/repos_collaborators.go +++ b/github/repos_collaborators.go @@ -36,6 +36,7 @@ func (s *RepositoriesService) IsCollaborator(owner, repo, user string) (bool, *R if err != nil { return false, nil, err } + resp, err := s.client.Do(req, nil) isCollab, err := parseBoolResponse(err) return isCollab, resp, err diff --git a/github/repos_commits.go b/github/repos_commits.go new file mode 100644 index 00000000000..c7e23832b95 --- /dev/null +++ b/github/repos_commits.go @@ -0,0 +1,143 @@ +// 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/url" + "time" +) + +// RepositoryCommit represents a commit in a repo. +// Note that it's wrapping a Commit, so author/committer information is in two places, +// but contain different details about them: in RepositoryCommit "github details", in Commit - "git details". +type RepositoryCommit struct { + SHA *string `json:"sha,omitempty"` + Commit *Commit `json:"commit,omitempty"` + Author *User `json:"author,omitempty"` + Committer *User `json:"committer,omitempty"` + Parents []Commit `json:"parents,omitempty"` + Message *string `json:"message,omitempty"` + + // Details about how many changes were made in this commit. Only filled in during GetCommit! + Stats *CommitStats `json:"stats,omitempty"` + // Details about which files, and how this commit touched. Only filled in during GetCommit! + Files []CommitFile `json:"files,omitempty"` +} + +// CommitsListOptions specifies the optional parameters to the +// RepositoriesService.RepositoriesList method. +type CommitsListOptions struct { + // SHA or branch to start listing Commits from. + SHA string + // Path that should be touched by the returned Commits. + Path string + // Author of by which to filter Commits. + Author string + // Since when should Commits be included in the response. + Since time.Time + // Until when should Commits be included in the response. + Until time.Time +} + +// CommitStats represents the number of additions / deletions from a file in a given RepositoryCommit. +type CommitStats struct { + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + Total *int `json:"total,omitempty"` +} + +type CommitFile struct { + SHA *string `json:"sha,omitempty"` + Filename *string `json:"filename,omitempty"` + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + Changes *int `json:"changes,omitempty"` + Status *string `json:"status,omitempty"` + Patch *string `json:"patch,omitempty"` +} + +// CommitsComparison is the result of comparing two commits. +// See CompareCommits for details. +type CommitsComparison struct { + BaseCommit *RepositoryCommit `json:"base_commit,omitempty"` + + // Head can be 'behind' or 'ahead' + Status *string `json:"status,omitempty"` + AheadBy *int `json:"ahead_by,omitempty"` + BehindBy *int `json:"behind_by,omitempty"` + TotalCommits *int `json:"total_commits,omitempty"` + + Commits []RepositoryCommit `json:"commits,omitempty"` + + Files []CommitFile `json:"files,omitempty"` +} + +// ListCommits lists the commits of a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/#list +func (s *RepositoriesService) ListCommits(owner, repo string, opts *CommitsListOptions) ([]RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits", owner, repo) + + if opts != nil { + params := url.Values{} + params.Add("sha", opts.SHA) + params.Add("path", opts.Path) + params.Add("author", opts.Author) + if !opts.Since.IsZero() { + params.Add("since", opts.Since.Format(time.RFC3339)) + } + if !opts.Until.IsZero() { + params.Add("until", opts.Until.Format(time.RFC3339)) + } + + u = u + "?" + params.Encode() + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + commits := new([]RepositoryCommit) + resp, err := s.client.Do(req, commits) + return *commits, resp, err +} + +// GetCommit fetches the specified commit, including all details about it. +// todo: support media formats - https://github.com/google/go-github/issues/6 +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/#get-a-single-commit +// See also: http://developer.github.com//v3/git/commits/#get-a-single-commit provides the same functionality +func (s *RepositoriesService) GetCommit(owner, repo, sha string) (*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, sha) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + commit := new(RepositoryCommit) + resp, err := s.client.Do(req, commit) + return commit, resp, err +} + +// CompareCommits compares a range of commits with each other. +// todo: support media formats - https://github.com/google/go-github/issues/6 +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/index.html#compare-two-commits +func (s *RepositoriesService) CompareCommits(owner, repo string, base, head string) (*CommitsComparison, *Response, error) { // todo I'm sure I missspelled this + u := fmt.Sprintf("repos/%v/%v/compare/%v...%v", owner, repo, base, head) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comp := new(CommitsComparison) + resp, err := s.client.Do(req, comp) + return comp, resp, err +} diff --git a/github/repos_commits_test.go b/github/repos_commits_test.go new file mode 100644 index 00000000000..2f33c046434 --- /dev/null +++ b/github/repos_commits_test.go @@ -0,0 +1,242 @@ +// 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" + "time" +) + +func TestRepositoriesService_ListCommits(t *testing.T) { + setup() + defer teardown() + + // given + mux.HandleFunc("/repos/o/r/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, + values{ + "sha": "s", + "path": "p", + "author": "a", + "since": "2013-08-01T00:00:00Z", + "until": "2013-09-03T00:00:00Z", + }) + + fmt.Fprintf(w, `[ + { + "sha": "s", + "commit": { "message": "m" }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + } + ]`) + }) + + wantAuthor := &User{ + Login: String("l"), + } + + want := []RepositoryCommit{ + { + SHA: String("s"), + Commit: &Commit{ + Message: String("m"), + }, + Author: wantAuthor, + Committer: wantAuthor, + Parents: []Commit{ + { + SHA: String("s"), + }, + }, + }, + } + + // when + commits, _, err := client.Repositories.ListCommits("o", "r", &CommitsListOptions{ + SHA: "s", + Path: "p", + Author: "a", + Since: time.Date(2013, time.August, 1, 0, 0, 0, 0, time.UTC), + Until: time.Date(2013, time.September, 3, 0, 0, 0, 0, time.UTC), + }) + + // then + if err != nil { + t.Errorf("Repositories.ListCommits returned error: %v", err) + } + + if !reflect.DeepEqual(commits, want) { + t.Errorf("Repositories.ListCommits returned \n%+v, want \n%+v", commits, want) + } +} + +func TestRepositoriesService_GetCommit(t *testing.T) { + setup() + defer teardown() + + // given + mux.HandleFunc("/repos/o/r/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{ + "sha": "s", + "commit": { "message": "m" }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ], + "stats": { "additions": 104, "deletions": 4, "total": 108 }, + "files": [ + { + "filename": "f", + "additions": 10, + "deletions": 2, + "changes": 12, + "status": "s", + "raw_url": "r", + "blob_url": "b", + "patch": "p" + } + ] + }`) + }) + + want := &RepositoryCommit{ + SHA: String("s"), + Commit: &Commit{ + Message: String("m"), + }, + Author: &User{ + Login: String("l"), + }, + Committer: &User{ + Login: String("l"), + }, + Parents: []Commit{ + { + SHA: String("s"), + }, + }, + Stats: &CommitStats{ + Additions: Int(104), + Deletions: Int(4), + Total: Int(108), + }, + Files: []CommitFile{ + { + Filename: String("f"), + Additions: Int(10), + Deletions: Int(2), + Changes: Int(12), + Status: String("s"), + Patch: String("p"), + }, + }, + } + + // when + commit, _, err := client.Repositories.GetCommit("o", "r", "6dcb09b5b57875f334f61aebed695e2e4193db5e") + + // then + if err != nil { + t.Errorf("Repositories.GetCommit returned error: %v", err) + } + + if !reflect.DeepEqual(commit, want) { + t.Errorf("Repositories.GetCommit returned \n%+v, want \n%+v", commit, want) + } +} + +func TestRepositoriesService_CompareCommits(t *testing.T) { + setup() + defer teardown() + + // given + base := "6dcb09b5b57875f334f61aebed695e2e4193db5e" + head := "0328041d1152db8ae77652d1618a02e57f745f17" + url := fmt.Sprintf("/repos/o/r/compare/%v...%v", base, head) + + mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{ + "base_commit": { + "sha": "s", + "commit": { + "author": { "name": "n" }, + "committer": { "name": "n" }, + "message": "m", + "tree": { "sha": "t" } + }, + "author": { "login": "n" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + }, + "status": "s", + "ahead_by": 1, + "behind_by": 2, + "total_commits": 1, + "commits": [ + { + "sha": "s", + "commit": { "author": { "name": "n" } }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + } + ], + "files": [ { "filename": "f" } ] + }`) + }) + + wantUser := &User{ + Login: String("l"), + } + + wantAuthor := &CommitAuthor{ + Name: String("n"), + } + + want := &CommitsComparison{ + Status: String("s"), + AheadBy: Int(1), + BehindBy: Int(2), + TotalCommits: Int(1), + BaseCommit: &RepositoryCommit{ + Commit: &Commit{ + Author: wantAuthor, + }, + Author: wantUser, + Committer: wantUser, + Message: String("m"), + }, + Commits: []RepositoryCommit{ + { + SHA: String("s"), + }, + }, + Files: []CommitFile{ + { + Filename: String("f"), + }, + }, + } + + // when + got, _, err := client.Repositories.CompareCommits("o", "r", base, head) + + // then + if err != nil { + t.Errorf("Repositories.CompareCommits returned error: %v", err) + } + + if reflect.DeepEqual(got, want) { + t.Errorf("Repositories.CompareCommits returned \n%+v, want \n%+v", got.Files[0].SHA, want.Files[0].SHA) + } +}