github.com/haya14busa/reviewdog@v0.0.0-20180723114510-ffb00ef78fd3/gitlab_mr_commit.go (about) 1 package reviewdog 2 3 import ( 4 "context" 5 "fmt" 6 "os/exec" 7 "path/filepath" 8 "strings" 9 "sync" 10 11 "github.com/xanzy/go-gitlab" 12 "golang.org/x/sync/errgroup" 13 ) 14 15 var _ CommentService = &GitLabMergeRequestCommitCommenter{} 16 17 // GitLabMergeRequestCommitCommenter is a comment service for GitLab MergeRequest. 18 // 19 // API: 20 // https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit 21 // POST /projects/:id/repository/commits/:sha/comments 22 type GitLabMergeRequestCommitCommenter struct { 23 cli *gitlab.Client 24 pr int 25 sha string 26 projects string 27 28 muComments sync.Mutex 29 postComments []*Comment 30 31 postedcs postedcomments 32 33 // wd is working directory relative to root of repository. 34 wd string 35 } 36 37 // NewGitLabMergeRequestCommitCommenter returns a new GitLabMergeRequestCommitCommenter service. 38 // GitLabMergeRequestCommitCommenter service needs git command in $PATH. 39 func NewGitLabMergeRequestCommitCommenter(cli *gitlab.Client, owner, repo string, pr int, sha string) (*GitLabMergeRequestCommitCommenter, error) { 40 workDir, err := gitRelWorkdir() 41 if err != nil { 42 return nil, fmt.Errorf("GitLabMergeRequestCommitCommenter needs 'git' command: %v", err) 43 } 44 return &GitLabMergeRequestCommitCommenter{ 45 cli: cli, 46 pr: pr, 47 sha: sha, 48 projects: owner + "/" + repo, 49 wd: workDir, 50 }, nil 51 } 52 53 // Post accepts a comment and holds it. Flush method actually posts comments to 54 // GitLab in parallel. 55 func (g *GitLabMergeRequestCommitCommenter) 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 *GitLabMergeRequestCommitCommenter) 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 72 return g.postCommentsForEach(ctx) 73 } 74 75 func (g *GitLabMergeRequestCommitCommenter) postCommentsForEach(ctx context.Context) error { 76 var eg errgroup.Group 77 for _, c := range g.postComments { 78 comment := c 79 if g.postedcs.IsPosted(comment, comment.Lnum) { 80 continue 81 } 82 eg.Go(func() error { 83 commitID, err := g.getLastCommitsID(comment.Path, comment.Lnum) 84 if err != nil { 85 commitID = g.sha 86 } 87 body := commentBody(comment) 88 ltype := "new" 89 prcomment := &gitlab.PostCommitCommentOptions{ 90 Note: &body, 91 Path: &comment.Path, 92 Line: &comment.Lnum, 93 LineType: <ype, 94 } 95 _, _, err = g.cli.Commits.PostCommitComment(g.projects, commitID, prcomment, gitlab.WithContext(ctx)) 96 return err 97 }) 98 } 99 return eg.Wait() 100 } 101 102 func (g *GitLabMergeRequestCommitCommenter) getLastCommitsID(path string, line int) (string, error) { 103 lineFormat := fmt.Sprintf("%d,%d", line, line) 104 s, err := exec.Command("git", "blame", "-l", "-L", lineFormat, path).Output() 105 if err != nil { 106 return "", fmt.Errorf("failed to get commitID: %v", err) 107 } 108 commitID := strings.Split(string(s), " ")[0] 109 return commitID, nil 110 } 111 112 func (g *GitLabMergeRequestCommitCommenter) setPostedComment(ctx context.Context) error { 113 g.postedcs = make(postedcomments) 114 cs, err := g.comment(ctx) 115 if err != nil { 116 return err 117 } 118 for _, c := range cs { 119 if c.Line == 0 || c.Path == "" || c.Note == "" { 120 // skip resolved comments. Or comments which do not have "path" nor 121 // "body". 122 continue 123 } 124 g.postedcs.AddPostedComment(c.Path, c.Line, c.Note) 125 } 126 return nil 127 } 128 129 func (g *GitLabMergeRequestCommitCommenter) comment(ctx context.Context) ([]*gitlab.CommitComment, error) { 130 commits, _, err := g.cli.MergeRequests.GetMergeRequestCommits( 131 g.projects, g.pr, nil, gitlab.WithContext(ctx)) 132 if err != nil { 133 return nil, err 134 } 135 comments := make([]*gitlab.CommitComment, 0) 136 for _, c := range commits { 137 tmpComments, _, err := g.cli.Commits.GetCommitComments( 138 g.projects, c.ID, nil, gitlab.WithContext(ctx)) 139 if err != nil { 140 continue 141 } 142 comments = append(comments, tmpComments...) 143 } 144 return comments, nil 145 }