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