github.com/Mistwind/reviewdog@v0.0.0-20230317041057-48e69b6d9e86/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/reviewdog/reviewdog"
    13  	"github.com/reviewdog/reviewdog/service/commentutil"
    14  	"github.com/reviewdog/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  // Post accepts a comment and holds it. Flush method actually posts comments to
    54  // GitLab in parallel.
    55  func (g *PushCommitsCommenter) Post(_ context.Context, c *reviewdog.Comment) error {
    56  	c.Result.Diagnostic.GetLocation().Path = filepath.ToSlash(
    57  		filepath.Join(g.wd, c.Result.Diagnostic.GetLocation().GetPath()))
    58  	g.Lock()
    59  	defer g.Unlock()
    60  	g.postComments = append(g.postComments, c)
    61  	return nil
    62  }
    63  
    64  // Flush posts comments which has not been posted yet.
    65  func (g *PushCommitsCommenter) Flush(ctx context.Context) error {
    66  	g.Lock()
    67  	defer g.Unlock()
    68  
    69  	return g.postCommentsForEach(ctx)
    70  }
    71  
    72  func (g *PushCommitsCommenter) postCommentsForEach(ctx context.Context) error {
    73  	var eg errgroup.Group
    74  	for _, c := range g.postComments {
    75  		c := c
    76  		loc := c.Result.Diagnostic.GetLocation()
    77  		lnum := int(loc.GetRange().GetStart().GetLine())
    78  		body := commentutil.MarkdownComment(c)
    79  		if !c.Result.InDiffFile || lnum == 0 || g.postedcs.IsPosted(c, lnum, body) {
    80  			continue
    81  		}
    82  		eg.Go(func() error {
    83  			prcomment := &gitlab.PostCommitCommentOptions{
    84  				Note:     gitlab.String(body),
    85  				Path:     gitlab.String(loc.GetPath()),
    86  				Line:     gitlab.Int(lnum),
    87  				LineType: gitlab.String("new"),
    88  			}
    89  			_, _, err := g.cli.Commits.PostCommitComment(g.projects, g.sha, prcomment, gitlab.WithContext(ctx))
    90  			return err
    91  		})
    92  	}
    93  	return eg.Wait()
    94  }