github.com/haya14busa/reviewdog@v0.0.0-20180723114510-ffb00ef78fd3/github.go (about) 1 package reviewdog 2 3 import ( 4 "context" 5 "fmt" 6 "path/filepath" 7 "sync" 8 9 "github.com/google/go-github/github" 10 ) 11 12 var _ CommentService = &GitHubPullRequest{} 13 var _ DiffService = &GitHubPullRequest{} 14 15 // GitHubPullRequest is a comment and diff service for GitHub PullRequest. 16 // 17 // API: 18 // https://developer.github.com/v3/pulls/comments/#create-a-comment 19 // POST /repos/:owner/:repo/pulls/:number/comments 20 type GitHubPullRequest struct { 21 cli *github.Client 22 owner string 23 repo string 24 pr int 25 sha string 26 27 muComments sync.Mutex 28 postComments []*Comment 29 30 postedcs postedcomments 31 32 // wd is working directory relative to root of repository. 33 wd string 34 } 35 36 // NewGitHubPullReqest returns a new GitHubPullRequest service. 37 // GitHubPullRequest service needs git command in $PATH. 38 func NewGitHubPullReqest(cli *github.Client, owner, repo string, pr int, sha string) (*GitHubPullRequest, error) { 39 workDir, err := gitRelWorkdir() 40 if err != nil { 41 return nil, fmt.Errorf("GitHubPullRequest needs 'git' command: %v", err) 42 } 43 return &GitHubPullRequest{ 44 cli: cli, 45 owner: owner, 46 repo: repo, 47 pr: pr, 48 sha: sha, 49 wd: workDir, 50 }, nil 51 } 52 53 // Post accepts a comment and holds it. Flush method actually posts comments to 54 // GitHub in parallel. 55 func (g *GitHubPullRequest) Post(_ context.Context, c *Comment) error { 56 c.Path = filepath.Join(g.wd, c.Path) 57 g.muComments.Lock() 58 defer g.muComments.Unlock() 59 g.postComments = append(g.postComments, c) 60 return nil 61 } 62 63 // Flush posts comments which has not been posted yet. 64 func (g *GitHubPullRequest) Flush(ctx context.Context) error { 65 g.muComments.Lock() 66 defer g.muComments.Unlock() 67 68 if err := g.setPostedComment(ctx); err != nil { 69 return err 70 } 71 return g.postAsReviewComment(ctx) 72 } 73 74 func (g *GitHubPullRequest) postAsReviewComment(ctx context.Context) error { 75 comments := make([]*github.DraftReviewComment, 0, len(g.postComments)) 76 for _, c := range g.postComments { 77 if g.postedcs.IsPosted(c, c.LnumDiff) { 78 continue 79 } 80 cbody := commentBody(c) 81 comments = append(comments, &github.DraftReviewComment{ 82 Path: &c.Path, 83 Position: &c.LnumDiff, 84 Body: &cbody, 85 }) 86 } 87 88 if len(comments) == 0 { 89 return nil 90 } 91 92 // TODO(haya14busa): it might be useful to report overview results by "body" 93 // field. 94 review := &github.PullRequestReviewRequest{ 95 CommitID: &g.sha, 96 Event: github.String("COMMENT"), 97 Comments: comments, 98 } 99 _, _, err := g.cli.PullRequests.CreateReview(ctx, g.owner, g.repo, g.pr, review) 100 return err 101 } 102 103 func (g *GitHubPullRequest) setPostedComment(ctx context.Context) error { 104 g.postedcs = make(postedcomments) 105 cs, err := g.comment(ctx) 106 if err != nil { 107 return err 108 } 109 for _, c := range cs { 110 if c.Position == nil || c.Path == nil || c.Body == nil { 111 // skip resolved comments. Or comments which do not have "path" nor 112 // "body". 113 continue 114 } 115 g.postedcs.AddPostedComment(c.GetPath(), c.GetPosition(), c.GetBody()) 116 } 117 return nil 118 } 119 120 // Diff returns a diff of PullRequest. 121 func (g *GitHubPullRequest) Diff(ctx context.Context) ([]byte, error) { 122 opt := github.RawOptions{Type: github.Diff} 123 d, _, err := g.cli.PullRequests.GetRaw(ctx, g.owner, g.repo, g.pr, opt) 124 if err != nil { 125 return nil, err 126 } 127 return []byte(d), nil 128 } 129 130 // Strip returns 1 as a strip of git diff. 131 func (g *GitHubPullRequest) Strip() int { 132 return 1 133 } 134 135 func (g *GitHubPullRequest) comment(ctx context.Context) ([]*github.PullRequestComment, error) { 136 // https://developer.github.com/v3/guides/traversing-with-pagination/ 137 opts := &github.PullRequestListCommentsOptions{ 138 ListOptions: github.ListOptions{ 139 PerPage: 100, 140 }, 141 } 142 comments, err := listAllPullRequestsComments(ctx, g.cli, g.owner, g.repo, g.pr, opts) 143 if err != nil { 144 return nil, err 145 } 146 return comments, nil 147 } 148 149 func listAllPullRequestsComments(ctx context.Context, cli *github.Client, 150 owner, repo string, pr int, opts *github.PullRequestListCommentsOptions) ([]*github.PullRequestComment, error) { 151 comments, resp, err := cli.PullRequests.ListComments(ctx, owner, repo, pr, opts) 152 if err != nil { 153 return nil, err 154 } 155 if resp.NextPage == 0 { 156 return comments, nil 157 } 158 newOpts := &github.PullRequestListCommentsOptions{ 159 ListOptions: github.ListOptions{ 160 Page: resp.NextPage, 161 PerPage: opts.PerPage, 162 }, 163 } 164 restComments, err := listAllPullRequestsComments(ctx, cli, owner, repo, pr, newOpts) 165 if err != nil { 166 return nil, err 167 } 168 return append(comments, restComments...), nil 169 }