github.com/mistwind/reviewdog@v0.0.0-20230322024206-9cfa11856d58/service/gitlab/gitlab_push_commits.go (about)

     1  package gitlab
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path/filepath"
     7  	"sync"
     8  
     9  	"github.com/xanzy/go-gitlab"
    10  	"golang.org/x/sync/errgroup"
    11  
    12  	"github.com/mistwind/reviewdog"
    13  	"github.com/mistwind/reviewdog/service/commentutil"
    14  	"github.com/mistwind/reviewdog/service/serviceutil"
    15  )
    16  
    17  var _ reviewdog.CommentService = &PushCommitsCommenter{}
    18  
    19  // PushCommitsCommenter is a comment service for GitLab commit in push.
    20  //
    21  // API:
    22  //  https://docs.gitlab.com/ce/api/commits.html#post-comment-to-commit
    23  //  POST /projects/:id/repository/commits/:sha/comments
    24  type PushCommitsCommenter struct {
    25  	cli      *gitlab.Client
    26  	sha      string
    27  	projects string
    28  
    29  	sync.Mutex
    30  	postComments []*reviewdog.Comment
    31  
    32  	postedcs commentutil.PostedComments
    33  
    34  	// wd is working directory relative to root of repository.
    35  	wd string
    36  }
    37  
    38  // NewGitLabPushCommitsCommenter returns a new PushCommitsCommenter service.
    39  // PushCommitsCommenter service needs git command in $PATH.
    40  func NewGitLabPushCommitsCommenter(cli *gitlab.Client, owner, repo string, sha string) (*PushCommitsCommenter, error) {
    41  	workDir, err := serviceutil.GitRelWorkdir()
    42  	if err != nil {
    43  		return nil, fmt.Errorf("PushCommitsCommenter needs 'git' command: %w", err)
    44  	}
    45  	return &PushCommitsCommenter{
    46  		cli:      cli,
    47  		sha:      sha,
    48  		projects: owner + "/" + repo,
    49  		wd:       workDir,
    50  	}, nil
    51  }
    52  
    53  // NewGitLabPushCommitsCommenterWithProjectID returns a new PushCommitsCommenter service.
    54  // PushCommitsCommenter service needs git command in $PATH.
    55  func NewGitLabPushCommitsCommenterWithProjectID(cli *gitlab.Client, projectID string, sha string) (*PushCommitsCommenter, error) {
    56  	workDir, err := serviceutil.GitRelWorkdir()
    57  	if err != nil {
    58  		return nil, fmt.Errorf("PushCommitsCommenter needs 'git' command: %w", err)
    59  	}
    60  	return &PushCommitsCommenter{
    61  		cli:      cli,
    62  		sha:      sha,
    63  		projects: projectID,
    64  		wd:       workDir,
    65  	}, nil
    66  }
    67  
    68  // Post accepts a comment and holds it. Flush method actually posts comments to
    69  // GitLab in parallel.
    70  func (g *PushCommitsCommenter) Post(_ context.Context, c *reviewdog.Comment) error {
    71  	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
    72  		filepath.Join(g.wd, c.Result.Diagnostic.GetLocation().GetPath()))
    73  	g.Lock()
    74  	defer g.Unlock()
    75  	g.postComments = append(g.postComments, c)
    76  	return nil
    77  }
    78  
    79  // Flush posts comments which has not been posted yet.
    80  func (g *PushCommitsCommenter) Flush(ctx context.Context) error {
    81  	g.Lock()
    82  	defer g.Unlock()
    83  
    84  	return g.postCommentsForEach(ctx)
    85  }
    86  
    87  func (g *PushCommitsCommenter) postCommentsForEach(ctx context.Context) error {
    88  	var eg errgroup.Group
    89  	for _, c := range g.postComments {
    90  		c := c
    91  		loc := c.Result.Diagnostic.GetLocation()
    92  		lnum := int(loc.GetRange().GetStart().GetLine())
    93  		body := commentutil.MarkdownComment(c)
    94  		if !c.Result.InDiffFile || lnum == 0 || g.postedcs.IsPosted(c, lnum, body) {
    95  			continue
    96  		}
    97  		eg.Go(func() error {
    98  			prcomment := &gitlab.PostCommitCommentOptions{
    99  				Note:     gitlab.String(body),
   100  				Path:     gitlab.String(loc.GetPath()),
   101  				Line:     gitlab.Int(lnum),
   102  				LineType: gitlab.String("new"),
   103  			}
   104  			_, _, err := g.cli.Commits.PostCommitComment(g.projects, g.sha, prcomment, gitlab.WithContext(ctx))
   105  			return err
   106  		})
   107  	}
   108  	return eg.Wait()
   109  }