• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jfrog / froggit-go / 15272314159

27 May 2025 10:00AM UTC coverage: 85.319%. First build
15272314159

Pull #153

github

eyalk007
cr fixes
Pull Request #153: Added support for all needed functions of global installation of Frogbot

174 of 324 new or added lines in 5 files covered. (53.7%)

4376 of 5129 relevant lines covered (85.32%)

6.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.03
/vcsclient/github.go
1
package vcsclient
2

3
import (
4
        "context"
5
        "crypto/rand"
6
        base64Utils "encoding/base64"
7
        "encoding/json"
8
        "errors"
9
        "fmt"
10
        "github.com/google/go-github/v56/github"
11
        "github.com/grokify/mogo/encoding/base64"
12
        "github.com/jfrog/froggit-go/vcsutils"
13
        "github.com/jfrog/gofrog/datastructures"
14
        "github.com/mitchellh/mapstructure"
15
        "golang.org/x/crypto/nacl/box"
16
        "golang.org/x/exp/slices"
17
        "golang.org/x/oauth2"
18
        "io"
19
        "net/http"
20
        "net/url"
21
        "path/filepath"
22
        "sort"
23
        "strconv"
24
        "strings"
25
        "time"
26
)
27

28
const (
29
        maxRetries               = 5
30
        retriesIntervalMilliSecs = 60000
31
        // https://github.com/orgs/community/discussions/27190
32
        githubPrContentSizeLimit = 65536
33
        ghMaxEnvReviewers        = 6
34
        regularFileCode          = "100644"
35
)
36

37
var rateLimitRetryStatuses = []int{http.StatusForbidden, http.StatusTooManyRequests}
38

39
type GitHubRateLimitExecutionHandler func() (*github.Response, error)
40

41
type GitHubRateLimitRetryExecutor struct {
42
        vcsutils.RetryExecutor
43
        GitHubRateLimitExecutionHandler
44
}
45

46
type FileToCommit struct {
47
        Path    string
48
        Content string
49
}
50

51
func (ghe *GitHubRateLimitRetryExecutor) Execute() error {
103✔
52
        ghe.ExecutionHandler = func() (bool, error) {
206✔
53
                ghResponse, err := ghe.GitHubRateLimitExecutionHandler()
103✔
54
                return shouldRetryIfRateLimitExceeded(ghResponse, err), err
103✔
55
        }
103✔
56
        return ghe.RetryExecutor.Execute()
103✔
57
}
58

59
// GitHubClient API version 3
60
type GitHubClient struct {
61
        vcsInfo                VcsInfo
62
        rateLimitRetryExecutor GitHubRateLimitRetryExecutor
63
        logger                 vcsutils.Log
64
        ghClient               *github.Client
65
}
66

67
// NewGitHubClient create a new GitHubClient
68
func NewGitHubClient(vcsInfo VcsInfo, logger vcsutils.Log) (*GitHubClient, error) {
137✔
69
        ghClient, err := buildGithubClient(vcsInfo, logger)
137✔
70
        if err != nil {
137✔
71
                return nil, err
×
72
        }
×
73
        return &GitHubClient{
137✔
74
                        vcsInfo:  vcsInfo,
137✔
75
                        logger:   logger,
137✔
76
                        ghClient: ghClient,
137✔
77
                        rateLimitRetryExecutor: GitHubRateLimitRetryExecutor{RetryExecutor: vcsutils.RetryExecutor{
137✔
78
                                Logger:                   logger,
137✔
79
                                MaxRetries:               maxRetries,
137✔
80
                                RetriesIntervalMilliSecs: retriesIntervalMilliSecs},
137✔
81
                        }},
137✔
82
                nil
137✔
83
}
84

85
func (client *GitHubClient) runWithRateLimitRetries(handler func() (*github.Response, error)) error {
103✔
86
        client.rateLimitRetryExecutor.GitHubRateLimitExecutionHandler = handler
103✔
87
        return client.rateLimitRetryExecutor.Execute()
103✔
88
}
103✔
89

90
// TestConnection on GitHub
91
func (client *GitHubClient) TestConnection(ctx context.Context) error {
4✔
92
        _, _, err := client.ghClient.Meta.Zen(ctx)
4✔
93
        return err
4✔
94
}
4✔
95

96
func buildGithubClient(vcsInfo VcsInfo, logger vcsutils.Log) (*github.Client, error) {
137✔
97
        httpClient := &http.Client{}
137✔
98
        if vcsInfo.Token != "" {
194✔
99
                httpClient = oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: vcsInfo.Token}))
57✔
100
        }
57✔
101
        ghClient := github.NewClient(httpClient)
137✔
102
        if vcsInfo.APIEndpoint != "" {
240✔
103
                baseURL, err := url.Parse(strings.TrimSuffix(vcsInfo.APIEndpoint, "/") + "/")
103✔
104
                if err != nil {
103✔
105
                        return nil, err
×
106
                }
×
107
                logger.Info("Using API endpoint:", baseURL)
103✔
108
                ghClient.BaseURL = baseURL
103✔
109
        }
110
        return ghClient, nil
137✔
111
}
112

113
// AddSshKeyToRepository on GitHub
114
func (client *GitHubClient) AddSshKeyToRepository(ctx context.Context, owner, repository, keyName, publicKey string, permission Permission) error {
8✔
115
        err := validateParametersNotBlank(map[string]string{
8✔
116
                "owner":      owner,
8✔
117
                "repository": repository,
8✔
118
                "key name":   keyName,
8✔
119
                "public key": publicKey,
8✔
120
        })
8✔
121
        if err != nil {
13✔
122
                return err
5✔
123
        }
5✔
124

125
        readOnly := permission != ReadWrite
3✔
126
        key := github.Key{
3✔
127
                Key:      &publicKey,
3✔
128
                Title:    &keyName,
3✔
129
                ReadOnly: &readOnly,
3✔
130
        }
3✔
131

3✔
132
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
6✔
133
                _, ghResponse, err := client.ghClient.Repositories.CreateKey(ctx, owner, repository, &key)
3✔
134
                return ghResponse, err
3✔
135
        })
3✔
136
}
137

138
// ListRepositories on GitHub
139
func (client *GitHubClient) ListRepositories(ctx context.Context) (results map[string][]string, err error) {
5✔
140
        results = make(map[string][]string)
5✔
141
        for nextPage := 1; ; nextPage++ {
11✔
142
                var repositoriesInPage []*github.Repository
6✔
143
                var ghResponse *github.Response
6✔
144
                err = client.runWithRateLimitRetries(func() (*github.Response, error) {
12✔
145
                        repositoriesInPage, ghResponse, err = client.executeListRepositoriesInPage(ctx, nextPage)
6✔
146
                        return ghResponse, err
6✔
147
                })
6✔
148
                if err != nil {
8✔
149
                        return
2✔
150
                }
2✔
151

152
                for _, repo := range repositoriesInPage {
37✔
153
                        results[*repo.Owner.Login] = append(results[*repo.Owner.Login], *repo.Name)
33✔
154
                }
33✔
155
                if nextPage+1 > ghResponse.LastPage {
7✔
156
                        break
3✔
157
                }
158
        }
159
        return
3✔
160
}
161

162
func (client *GitHubClient) executeListRepositoriesInPage(ctx context.Context, page int) ([]*github.Repository, *github.Response, error) {
6✔
163
        options := &github.RepositoryListOptions{ListOptions: github.ListOptions{Page: page}}
6✔
164
        return client.ghClient.Repositories.List(ctx, "", options)
6✔
165
}
6✔
166

167
// ListBranches on GitHub
168
func (client *GitHubClient) ListBranches(ctx context.Context, owner, repository string) (branchList []string, err error) {
2✔
169
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
170
                var ghResponse *github.Response
2✔
171
                branchList, ghResponse, err = client.executeListBranch(ctx, owner, repository)
2✔
172
                return ghResponse, err
2✔
173
        })
2✔
174
        return
2✔
175
}
176

177
func (client *GitHubClient) executeListBranch(ctx context.Context, owner, repository string) ([]string, *github.Response, error) {
2✔
178
        branches, ghResponse, err := client.ghClient.Repositories.ListBranches(ctx, owner, repository, nil)
2✔
179
        if err != nil {
3✔
180
                return []string{}, ghResponse, err
1✔
181
        }
1✔
182

183
        branchList := make([]string, 0, len(branches))
1✔
184
        for _, branch := range branches {
3✔
185
                branchList = append(branchList, *branch.Name)
2✔
186
        }
2✔
187
        return branchList, ghResponse, nil
1✔
188
}
189

190
// CreateWebhook on GitHub
191
func (client *GitHubClient) CreateWebhook(ctx context.Context, owner, repository, _, payloadURL string,
192
        webhookEvents ...vcsutils.WebhookEvent) (string, string, error) {
2✔
193
        token := vcsutils.CreateToken()
2✔
194
        hook := createGitHubHook(token, payloadURL, webhookEvents...)
2✔
195
        var ghResponseHook *github.Hook
2✔
196
        var err error
2✔
197
        if err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
198
                var ghResponse *github.Response
2✔
199
                ghResponseHook, ghResponse, err = client.ghClient.Repositories.CreateHook(ctx, owner, repository, hook)
2✔
200
                return ghResponse, err
2✔
201
        }); err != nil {
3✔
202
                return "", "", err
1✔
203
        }
1✔
204

205
        return strconv.FormatInt(*ghResponseHook.ID, 10), token, nil
1✔
206
}
207

208
// UpdateWebhook on GitHub
209
func (client *GitHubClient) UpdateWebhook(ctx context.Context, owner, repository, _, payloadURL, token,
210
        webhookID string, webhookEvents ...vcsutils.WebhookEvent) error {
2✔
211
        webhookIDInt64, err := strconv.ParseInt(webhookID, 10, 64)
2✔
212
        if err != nil {
2✔
213
                return err
×
214
        }
×
215

216
        hook := createGitHubHook(token, payloadURL, webhookEvents...)
2✔
217
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
218
                var ghResponse *github.Response
2✔
219
                _, ghResponse, err = client.ghClient.Repositories.EditHook(ctx, owner, repository, webhookIDInt64, hook)
2✔
220
                return ghResponse, err
2✔
221
        })
2✔
222
}
223

224
// DeleteWebhook on GitHub
225
func (client *GitHubClient) DeleteWebhook(ctx context.Context, owner, repository, webhookID string) error {
2✔
226
        webhookIDInt64, err := strconv.ParseInt(webhookID, 10, 64)
2✔
227
        if err != nil {
3✔
228
                return err
1✔
229
        }
1✔
230

231
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
2✔
232
                return client.ghClient.Repositories.DeleteHook(ctx, owner, repository, webhookIDInt64)
1✔
233
        })
1✔
234
}
235

236
// SetCommitStatus on GitHub
237
func (client *GitHubClient) SetCommitStatus(ctx context.Context, commitStatus CommitStatus, owner, repository, ref,
238
        title, description, detailsURL string) error {
2✔
239
        state := getGitHubCommitState(commitStatus)
2✔
240
        status := &github.RepoStatus{
2✔
241
                Context:     &title,
2✔
242
                TargetURL:   &detailsURL,
2✔
243
                State:       &state,
2✔
244
                Description: &description,
2✔
245
        }
2✔
246

2✔
247
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
248
                _, ghResponse, err := client.ghClient.Repositories.CreateStatus(ctx, owner, repository, ref, status)
2✔
249
                return ghResponse, err
2✔
250
        })
2✔
251
}
252

253
// GetCommitStatuses on GitHub
254
func (client *GitHubClient) GetCommitStatuses(ctx context.Context, owner, repository, ref string) (statusInfoList []CommitStatusInfo, err error) {
6✔
255
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
12✔
256
                var ghResponse *github.Response
6✔
257
                statusInfoList, ghResponse, err = client.executeGetCommitStatuses(ctx, owner, repository, ref)
6✔
258
                return ghResponse, err
6✔
259
        })
6✔
260
        return
6✔
261
}
262

263
func (client *GitHubClient) executeGetCommitStatuses(ctx context.Context, owner, repository, ref string) (statusInfoList []CommitStatusInfo, ghResponse *github.Response, err error) {
6✔
264
        statuses, ghResponse, err := client.ghClient.Repositories.GetCombinedStatus(ctx, owner, repository, ref, nil)
6✔
265
        if err != nil {
10✔
266
                return
4✔
267
        }
4✔
268

269
        for _, singleStatus := range statuses.Statuses {
6✔
270
                statusInfoList = append(statusInfoList, CommitStatusInfo{
4✔
271
                        State:         commitStatusAsStringToStatus(*singleStatus.State),
4✔
272
                        Description:   singleStatus.GetDescription(),
4✔
273
                        DetailsUrl:    singleStatus.GetTargetURL(),
4✔
274
                        Creator:       singleStatus.GetCreator().GetName(),
4✔
275
                        LastUpdatedAt: singleStatus.GetUpdatedAt().Time,
4✔
276
                        CreatedAt:     singleStatus.GetCreatedAt().Time,
4✔
277
                })
4✔
278
        }
4✔
279
        return
2✔
280
}
281

282
// DownloadRepository on GitHub
283
func (client *GitHubClient) DownloadRepository(ctx context.Context, owner, repository, branch, localPath string) (err error) {
2✔
284
        // Get the archive download link from GitHub
2✔
285
        var baseURL *url.URL
2✔
286
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
287
                var ghResponse *github.Response
2✔
288
                baseURL, ghResponse, err = client.executeGetArchiveLink(ctx, owner, repository, branch)
2✔
289
                return ghResponse, err
2✔
290
        })
2✔
291
        if err != nil {
3✔
292
                return
1✔
293
        }
1✔
294

295
        // Download the archive
296
        httpResponse, err := executeDownloadArchiveFromLink(baseURL.String())
1✔
297
        if err != nil {
1✔
298
                return
×
299
        }
×
300
        defer func() { err = errors.Join(err, httpResponse.Body.Close()) }()
2✔
301
        client.logger.Info(repository, vcsutils.SuccessfulRepoDownload)
1✔
302

1✔
303
        // Untar the archive
1✔
304
        if err = vcsutils.Untar(localPath, httpResponse.Body, true); err != nil {
1✔
305
                return
×
306
        }
×
307
        client.logger.Info(vcsutils.SuccessfulRepoExtraction)
1✔
308

1✔
309
        repositoryInfo, err := client.GetRepositoryInfo(ctx, owner, repository)
1✔
310
        if err != nil {
1✔
311
                return
×
312
        }
×
313
        // Create a .git folder in the archive with the remote repository HTTP clone url
314
        err = vcsutils.CreateDotGitFolderWithRemote(localPath, vcsutils.RemoteName, repositoryInfo.CloneInfo.HTTP)
1✔
315
        return
1✔
316
}
317

318
func (client *GitHubClient) executeGetArchiveLink(ctx context.Context, owner, repository, branch string) (baseURL *url.URL, ghResponse *github.Response, err error) {
2✔
319
        client.logger.Debug("Getting GitHub archive link to download")
2✔
320
        return client.ghClient.Repositories.GetArchiveLink(ctx, owner, repository, github.Tarball,
2✔
321
                &github.RepositoryContentGetOptions{Ref: branch}, 5)
2✔
322
}
2✔
323

324
func executeDownloadArchiveFromLink(baseURL string) (*http.Response, error) {
1✔
325
        httpClient := &http.Client{}
1✔
326
        req, err := http.NewRequest(http.MethodGet, baseURL, nil)
1✔
327
        if err != nil {
1✔
328
                return nil, err
×
329
        }
×
330
        httpResponse, err := httpClient.Do(req)
1✔
331
        if err != nil {
1✔
332
                return httpResponse, err
×
333
        }
×
334
        return httpResponse, vcsutils.CheckResponseStatusWithBody(httpResponse, http.StatusOK)
1✔
335
}
336

337
func (client *GitHubClient) GetPullRequestCommentSizeLimit() int {
×
338
        return githubPrContentSizeLimit
×
339
}
×
340

341
func (client *GitHubClient) GetPullRequestDetailsSizeLimit() int {
×
342
        return githubPrContentSizeLimit
×
343
}
×
344

345
// CreatePullRequest on GitHub
346
func (client *GitHubClient) CreatePullRequest(ctx context.Context, owner, repository, sourceBranch, targetBranch, title, description string) error {
2✔
347
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
348
                return client.executeCreatePullRequest(ctx, owner, repository, sourceBranch, targetBranch, title, description)
2✔
349
        })
2✔
350
}
351

352
func (client *GitHubClient) executeCreatePullRequest(ctx context.Context, owner, repository, sourceBranch, targetBranch, title, description string) (*github.Response, error) {
2✔
353
        head := owner + ":" + sourceBranch
2✔
354
        client.logger.Debug(vcsutils.CreatingPullRequest, title)
2✔
355

2✔
356
        _, ghResponse, err := client.ghClient.PullRequests.Create(ctx, owner, repository, &github.NewPullRequest{
2✔
357
                Title: &title,
2✔
358
                Body:  &description,
2✔
359
                Head:  &head,
2✔
360
                Base:  &targetBranch,
2✔
361
        })
2✔
362
        return ghResponse, err
2✔
363
}
2✔
364

365
// UpdatePullRequest on GitHub
366
func (client *GitHubClient) UpdatePullRequest(ctx context.Context, owner, repository, title, body, targetBranchName string, id int, state vcsutils.PullRequestState) error {
3✔
367
        client.logger.Debug(vcsutils.UpdatingPullRequest, id)
3✔
368
        var baseRef *github.PullRequestBranch
3✔
369
        if targetBranchName != "" {
5✔
370
                baseRef = &github.PullRequestBranch{Ref: &targetBranchName}
2✔
371
        }
2✔
372
        pullRequest := &github.PullRequest{
3✔
373
                Body:  &body,
3✔
374
                Title: &title,
3✔
375
                State: vcsutils.MapPullRequestState(&state),
3✔
376
                Base:  baseRef,
3✔
377
        }
3✔
378

3✔
379
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
6✔
380
                _, ghResponse, err := client.ghClient.PullRequests.Edit(ctx, owner, repository, id, pullRequest)
3✔
381
                return ghResponse, err
3✔
382
        })
3✔
383
}
384

385
// ListOpenPullRequestsWithBody on GitHub
386
func (client *GitHubClient) ListOpenPullRequestsWithBody(ctx context.Context, owner, repository string) ([]PullRequestInfo, error) {
1✔
387
        return client.getOpenPullRequests(ctx, owner, repository, true)
1✔
388
}
1✔
389

390
// ListOpenPullRequests on GitHub
391
func (client *GitHubClient) ListOpenPullRequests(ctx context.Context, owner, repository string) ([]PullRequestInfo, error) {
1✔
392
        return client.getOpenPullRequests(ctx, owner, repository, false)
1✔
393
}
1✔
394

395
func (client *GitHubClient) getOpenPullRequests(ctx context.Context, owner, repository string, withBody bool) ([]PullRequestInfo, error) {
2✔
396
        var pullRequests []*github.PullRequest
2✔
397
        client.logger.Debug(vcsutils.FetchingOpenPullRequests, repository)
2✔
398
        err := client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
399
                var ghResponse *github.Response
2✔
400
                var err error
2✔
401
                pullRequests, ghResponse, err = client.ghClient.PullRequests.List(ctx, owner, repository, &github.PullRequestListOptions{State: "open"})
2✔
402
                return ghResponse, err
2✔
403
        })
2✔
404
        if err != nil {
2✔
405
                return []PullRequestInfo{}, err
×
406
        }
×
407

408
        return mapGitHubPullRequestToPullRequestInfoList(pullRequests, withBody)
2✔
409
}
410

411
func (client *GitHubClient) GetPullRequestByID(ctx context.Context, owner, repository string, pullRequestId int) (PullRequestInfo, error) {
4✔
412
        var pullRequest *github.PullRequest
4✔
413
        var ghResponse *github.Response
4✔
414
        var err error
4✔
415
        client.logger.Debug(vcsutils.FetchingPullRequestById, repository)
4✔
416
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
8✔
417
                pullRequest, ghResponse, err = client.ghClient.PullRequests.Get(ctx, owner, repository, pullRequestId)
4✔
418
                return ghResponse, err
4✔
419
        })
4✔
420
        if err != nil {
7✔
421
                return PullRequestInfo{}, err
3✔
422
        }
3✔
423

424
        if err = vcsutils.CheckResponseStatusWithBody(ghResponse.Response, http.StatusOK); err != nil {
1✔
425
                return PullRequestInfo{}, err
×
426
        }
×
427

428
        return mapGitHubPullRequestToPullRequestInfo(pullRequest, false)
1✔
429
}
430

431
func mapGitHubPullRequestToPullRequestInfo(ghPullRequest *github.PullRequest, withBody bool) (PullRequestInfo, error) {
4✔
432
        var sourceBranch, targetBranch string
4✔
433
        var err1, err2 error
4✔
434
        if ghPullRequest != nil && ghPullRequest.Head != nil && ghPullRequest.Base != nil {
8✔
435
                sourceBranch, err1 = extractBranchFromLabel(vcsutils.DefaultIfNotNil(ghPullRequest.Head.Label))
4✔
436
                targetBranch, err2 = extractBranchFromLabel(vcsutils.DefaultIfNotNil(ghPullRequest.Base.Label))
4✔
437
                err := errors.Join(err1, err2)
4✔
438
                if err != nil {
4✔
439
                        return PullRequestInfo{}, err
×
440
                }
×
441
        }
442

443
        var sourceRepoName, sourceRepoOwner string
4✔
444
        if ghPullRequest.Head.Repo == nil {
4✔
445
                return PullRequestInfo{}, errors.New("the source repository information is missing when fetching the pull request details")
×
446
        }
×
447
        if ghPullRequest.Head.Repo.Owner == nil {
4✔
448
                return PullRequestInfo{}, errors.New("the source repository owner name is missing when fetching the pull request details")
×
449
        }
×
450
        sourceRepoName = vcsutils.DefaultIfNotNil(ghPullRequest.Head.Repo.Name)
4✔
451
        sourceRepoOwner = vcsutils.DefaultIfNotNil(ghPullRequest.Head.Repo.Owner.Login)
4✔
452

4✔
453
        var targetRepoName, targetRepoOwner string
4✔
454
        if ghPullRequest.Base.Repo == nil {
4✔
455
                return PullRequestInfo{}, errors.New("the target repository information is missing when fetching the pull request details")
×
456
        }
×
457
        if ghPullRequest.Base.Repo.Owner == nil {
4✔
458
                return PullRequestInfo{}, errors.New("the target repository owner name is missing when fetching the pull request details")
×
459
        }
×
460
        targetRepoName = vcsutils.DefaultIfNotNil(ghPullRequest.Base.Repo.Name)
4✔
461
        targetRepoOwner = vcsutils.DefaultIfNotNil(ghPullRequest.Base.Repo.Owner.Login)
4✔
462

4✔
463
        var body string
4✔
464
        if withBody {
5✔
465
                body = vcsutils.DefaultIfNotNil(ghPullRequest.Body)
1✔
466
        }
1✔
467

468
        return PullRequestInfo{
4✔
469
                ID:     int64(vcsutils.DefaultIfNotNil(ghPullRequest.Number)),
4✔
470
                Title:  vcsutils.DefaultIfNotNil(ghPullRequest.Title),
4✔
471
                URL:    vcsutils.DefaultIfNotNil(ghPullRequest.HTMLURL),
4✔
472
                Body:   body,
4✔
473
                Author: vcsutils.DefaultIfNotNil(ghPullRequest.User.Login),
4✔
474
                Source: BranchInfo{
4✔
475
                        Name:       sourceBranch,
4✔
476
                        Repository: sourceRepoName,
4✔
477
                        Owner:      sourceRepoOwner,
4✔
478
                },
4✔
479
                Target: BranchInfo{
4✔
480
                        Name:       targetBranch,
4✔
481
                        Repository: targetRepoName,
4✔
482
                        Owner:      targetRepoOwner,
4✔
483
                },
4✔
484
        }, nil
4✔
485
}
486

487
// Extracts branch name from the following expected label format repo:branch
488
func extractBranchFromLabel(label string) (string, error) {
8✔
489
        split := strings.Split(label, ":")
8✔
490
        if len(split) <= 1 {
8✔
491
                return "", fmt.Errorf("bad label format %s", label)
×
492
        }
×
493
        return split[1], nil
8✔
494
}
495

496
// AddPullRequestComment on GitHub
497
func (client *GitHubClient) AddPullRequestComment(ctx context.Context, owner, repository, content string, pullRequestID int) error {
6✔
498
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository, "content": content})
6✔
499
        if err != nil {
10✔
500
                return err
4✔
501
        }
4✔
502

503
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
504
                var ghResponse *github.Response
2✔
505
                // We use the Issues API to add a regular comment. The PullRequests API adds a code review comment.
2✔
506
                _, ghResponse, err = client.ghClient.Issues.CreateComment(ctx, owner, repository, pullRequestID, &github.IssueComment{Body: &content})
2✔
507
                return ghResponse, err
2✔
508
        })
2✔
509
}
510

511
// AddPullRequestReviewComments on GitHub
512
func (client *GitHubClient) AddPullRequestReviewComments(ctx context.Context, owner, repository string, pullRequestID int, comments ...PullRequestComment) error {
2✔
513
        prID := strconv.Itoa(pullRequestID)
2✔
514
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository, "pullRequestID": prID})
2✔
515
        if err != nil {
2✔
516
                return err
×
517
        }
×
518
        if len(comments) == 0 {
2✔
519
                return errors.New(vcsutils.ErrNoCommentsProvided)
×
520
        }
×
521

522
        var commits []*github.RepositoryCommit
2✔
523
        var ghResponse *github.Response
2✔
524
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
525
                commits, ghResponse, err = client.ghClient.PullRequests.ListCommits(ctx, owner, repository, pullRequestID, nil)
2✔
526
                return ghResponse, err
2✔
527
        })
2✔
528
        if err != nil {
3✔
529
                return err
1✔
530
        }
1✔
531
        if len(commits) == 0 {
1✔
532
                return errors.New("could not fetch the commits list for pull request " + prID)
×
533
        }
×
534

535
        latestCommitSHA := commits[len(commits)-1].GetSHA()
1✔
536

1✔
537
        for _, comment := range comments {
3✔
538
                err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
539
                        ghResponse, err = client.executeCreatePullRequestReviewComment(ctx, owner, repository, latestCommitSHA, pullRequestID, comment)
2✔
540
                        return ghResponse, err
2✔
541
                })
2✔
542
                if err != nil {
2✔
543
                        return err
×
544
                }
×
545
        }
546
        return nil
1✔
547
}
548

549
func (client *GitHubClient) executeCreatePullRequestReviewComment(ctx context.Context, owner, repository, latestCommitSHA string, pullRequestID int, comment PullRequestComment) (*github.Response, error) {
2✔
550
        filePath := filepath.Clean(comment.NewFilePath)
2✔
551
        startLine := &comment.NewStartLine
2✔
552
        // GitHub API won't accept 'start_line' if it equals the end line
2✔
553
        if *startLine == comment.NewEndLine {
2✔
554
                startLine = nil
×
555
        }
×
556
        _, ghResponse, err := client.ghClient.PullRequests.CreateComment(ctx, owner, repository, pullRequestID, &github.PullRequestComment{
2✔
557
                CommitID:  &latestCommitSHA,
2✔
558
                Body:      &comment.Content,
2✔
559
                StartLine: startLine,
2✔
560
                Line:      &comment.NewEndLine,
2✔
561
                Path:      &filePath,
2✔
562
        })
2✔
563
        if err != nil {
2✔
564
                err = fmt.Errorf("could not create a code review comment for <%s/%s> in pull request %d. error received: %w",
×
565
                        owner, repository, pullRequestID, err)
×
566
        }
×
567
        return ghResponse, err
2✔
568
}
569

570
// ListPullRequestReviewComments on GitHub
571
func (client *GitHubClient) ListPullRequestReviewComments(ctx context.Context, owner, repository string, pullRequestID int) ([]CommentInfo, error) {
2✔
572
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
2✔
573
        if err != nil {
2✔
574
                return nil, err
×
575
        }
×
576

577
        commentsInfoList := []CommentInfo{}
2✔
578
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
579
                var ghResponse *github.Response
2✔
580
                commentsInfoList, ghResponse, err = client.executeListPullRequestReviewComments(ctx, owner, repository, pullRequestID)
2✔
581
                return ghResponse, err
2✔
582
        })
2✔
583
        return commentsInfoList, err
2✔
584
}
585

586
// ListPullRequestReviews on GitHub
587
func (client *GitHubClient) ListPullRequestReviews(ctx context.Context, owner, repository string, pullRequestID int) ([]PullRequestReviewDetails, error) {
2✔
588
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
2✔
589
        if err != nil {
2✔
590
                return nil, err
×
591
        }
×
592

593
        var reviews []*github.PullRequestReview
2✔
594
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
595
                var ghResponse *github.Response
2✔
596
                reviews, ghResponse, err = client.ghClient.PullRequests.ListReviews(ctx, owner, repository, pullRequestID, nil)
2✔
597
                return ghResponse, err
2✔
598
        })
2✔
599
        if err != nil {
3✔
600
                return nil, err
1✔
601
        }
1✔
602

603
        var reviewInfos []PullRequestReviewDetails
1✔
604
        for _, review := range reviews {
2✔
605
                reviewInfos = append(reviewInfos, PullRequestReviewDetails{
1✔
606
                        ID:          review.GetID(),
1✔
607
                        Reviewer:    review.GetUser().GetLogin(),
1✔
608
                        Body:        review.GetBody(),
1✔
609
                        State:       review.GetState(),
1✔
610
                        SubmittedAt: review.GetSubmittedAt().String(),
1✔
611
                        CommitID:    review.GetCommitID(),
1✔
612
                })
1✔
613
        }
1✔
614

615
        return reviewInfos, nil
1✔
616
}
617

618
func (client *GitHubClient) executeListPullRequestReviewComments(ctx context.Context, owner, repository string, pullRequestID int) ([]CommentInfo, *github.Response, error) {
2✔
619
        commentsList, ghResponse, err := client.ghClient.PullRequests.ListComments(ctx, owner, repository, pullRequestID, nil)
2✔
620
        if err != nil {
3✔
621
                return []CommentInfo{}, ghResponse, err
1✔
622
        }
1✔
623
        commentsInfoList := []CommentInfo{}
1✔
624
        for _, comment := range commentsList {
2✔
625
                commentsInfoList = append(commentsInfoList, CommentInfo{
1✔
626
                        ID:      comment.GetID(),
1✔
627
                        Content: comment.GetBody(),
1✔
628
                        Created: comment.GetCreatedAt().Time,
1✔
629
                })
1✔
630
        }
1✔
631
        return commentsInfoList, ghResponse, nil
1✔
632
}
633

634
// ListPullRequestComments on GitHub
635
func (client *GitHubClient) ListPullRequestComments(ctx context.Context, owner, repository string, pullRequestID int) ([]CommentInfo, error) {
4✔
636
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
4✔
637
        if err != nil {
4✔
638
                return []CommentInfo{}, err
×
639
        }
×
640

641
        var commentsList []*github.IssueComment
4✔
642
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
8✔
643
                var ghResponse *github.Response
4✔
644
                commentsList, ghResponse, err = client.ghClient.Issues.ListComments(ctx, owner, repository, pullRequestID, &github.IssueListCommentsOptions{})
4✔
645
                return ghResponse, err
4✔
646
        })
4✔
647

648
        if err != nil {
7✔
649
                return []CommentInfo{}, err
3✔
650
        }
3✔
651

652
        return mapGitHubIssuesCommentToCommentInfoList(commentsList)
1✔
653
}
654

655
// DeletePullRequestReviewComments on GitHub
656
func (client *GitHubClient) DeletePullRequestReviewComments(ctx context.Context, owner, repository string, _ int, comments ...CommentInfo) error {
2✔
657
        for _, comment := range comments {
5✔
658
                commentID := comment.ID
3✔
659
                err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository, "commentID": strconv.FormatInt(commentID, 10)})
3✔
660
                if err != nil {
3✔
661
                        return err
×
662
                }
×
663

664
                err = client.runWithRateLimitRetries(func() (*github.Response, error) {
6✔
665
                        return client.executeDeletePullRequestReviewComment(ctx, owner, repository, commentID)
3✔
666
                })
3✔
667
                if err != nil {
4✔
668
                        return err
1✔
669
                }
1✔
670

671
        }
672
        return nil
1✔
673
}
674

675
func (client *GitHubClient) executeDeletePullRequestReviewComment(ctx context.Context, owner, repository string, commentID int64) (*github.Response, error) {
3✔
676
        ghResponse, err := client.ghClient.PullRequests.DeleteComment(ctx, owner, repository, commentID)
3✔
677
        if err != nil {
4✔
678
                err = fmt.Errorf("could not delete pull request review comment: %w", err)
1✔
679
        }
1✔
680
        return ghResponse, err
3✔
681
}
682

683
// DeletePullRequestComment on GitHub
684
func (client *GitHubClient) DeletePullRequestComment(ctx context.Context, owner, repository string, _, commentID int) error {
2✔
685
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
2✔
686
        if err != nil {
2✔
687
                return err
×
688
        }
×
689
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
690
                return client.executeDeletePullRequestComment(ctx, owner, repository, commentID)
2✔
691
        })
2✔
692
}
693

694
func (client *GitHubClient) executeDeletePullRequestComment(ctx context.Context, owner, repository string, commentID int) (*github.Response, error) {
2✔
695
        ghResponse, err := client.ghClient.Issues.DeleteComment(ctx, owner, repository, int64(commentID))
2✔
696
        if err != nil {
3✔
697
                return ghResponse, err
1✔
698
        }
1✔
699

700
        var statusCode int
1✔
701
        if ghResponse.Response != nil {
2✔
702
                statusCode = ghResponse.Response.StatusCode
1✔
703
        }
1✔
704
        if statusCode != http.StatusNoContent && statusCode != http.StatusOK {
1✔
705
                return ghResponse, fmt.Errorf("expected %d status code while received %d status code", http.StatusNoContent, ghResponse.Response.StatusCode)
×
706
        }
×
707

708
        return ghResponse, nil
1✔
709
}
710

711
// GetLatestCommit on GitHub
712
func (client *GitHubClient) GetLatestCommit(ctx context.Context, owner, repository, branch string) (CommitInfo, error) {
10✔
713
        commits, err := client.GetCommits(ctx, owner, repository, branch)
10✔
714
        if err != nil {
18✔
715
                return CommitInfo{}, err
8✔
716
        }
8✔
717
        latestCommit := CommitInfo{}
2✔
718
        if len(commits) > 0 {
4✔
719
                latestCommit = commits[0]
2✔
720
        }
2✔
721
        return latestCommit, nil
2✔
722
}
723

724
// GetCommits on GitHub
725
func (client *GitHubClient) GetCommits(ctx context.Context, owner, repository, branch string) ([]CommitInfo, error) {
12✔
726
        err := validateParametersNotBlank(map[string]string{
12✔
727
                "owner":      owner,
12✔
728
                "repository": repository,
12✔
729
                "branch":     branch,
12✔
730
        })
12✔
731
        if err != nil {
16✔
732
                return nil, err
4✔
733
        }
4✔
734

735
        var commitsInfo []CommitInfo
8✔
736
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
16✔
737
                var ghResponse *github.Response
8✔
738
                listOptions := &github.CommitsListOptions{
8✔
739
                        SHA: branch,
8✔
740
                        ListOptions: github.ListOptions{
8✔
741
                                Page:    1,
8✔
742
                                PerPage: vcsutils.NumberOfCommitsToFetch,
8✔
743
                        },
8✔
744
                }
8✔
745
                commitsInfo, ghResponse, err = client.executeGetCommits(ctx, owner, repository, listOptions)
8✔
746
                return ghResponse, err
8✔
747
        })
8✔
748
        return commitsInfo, err
8✔
749
}
750

751
// GetCommitsWithQueryOptions on GitHub
752
func (client *GitHubClient) GetCommitsWithQueryOptions(ctx context.Context, owner, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
2✔
753
        err := validateParametersNotBlank(map[string]string{
2✔
754
                "owner":      owner,
2✔
755
                "repository": repository,
2✔
756
        })
2✔
757
        if err != nil {
2✔
758
                return nil, err
×
759
        }
×
760
        var commitsInfo []CommitInfo
2✔
761
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
762
                var ghResponse *github.Response
2✔
763
                commitsInfo, ghResponse, err = client.executeGetCommits(ctx, owner, repository, convertToGitHubCommitsListOptions(listOptions))
2✔
764
                return ghResponse, err
2✔
765
        })
2✔
766
        return commitsInfo, err
2✔
767
}
768

769
func convertToGitHubCommitsListOptions(listOptions GitCommitsQueryOptions) *github.CommitsListOptions {
2✔
770
        return &github.CommitsListOptions{
2✔
771
                Since: listOptions.Since,
2✔
772
                Until: time.Now(),
2✔
773
                ListOptions: github.ListOptions{
2✔
774
                        Page:    listOptions.Page,
2✔
775
                        PerPage: listOptions.PerPage,
2✔
776
                },
2✔
777
        }
2✔
778
}
2✔
779

780
func (client *GitHubClient) executeGetCommits(ctx context.Context, owner, repository string, listOptions *github.CommitsListOptions) ([]CommitInfo, *github.Response, error) {
10✔
781
        commits, ghResponse, err := client.ghClient.Repositories.ListCommits(ctx, owner, repository, listOptions)
10✔
782
        if err != nil {
16✔
783
                return nil, ghResponse, err
6✔
784
        }
6✔
785

786
        var commitsInfo []CommitInfo
4✔
787
        for _, commit := range commits {
11✔
788
                commitInfo := mapGitHubCommitToCommitInfo(commit)
7✔
789
                commitsInfo = append(commitsInfo, commitInfo)
7✔
790
        }
7✔
791
        return commitsInfo, ghResponse, nil
4✔
792
}
793

794
// GetRepositoryInfo on GitHub
795
func (client *GitHubClient) GetRepositoryInfo(ctx context.Context, owner, repository string) (RepositoryInfo, error) {
6✔
796
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
6✔
797
        if err != nil {
9✔
798
                return RepositoryInfo{}, err
3✔
799
        }
3✔
800

801
        var repo *github.Repository
3✔
802
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
6✔
803
                var ghResponse *github.Response
3✔
804
                repo, ghResponse, err = client.ghClient.Repositories.Get(ctx, owner, repository)
3✔
805
                return ghResponse, err
3✔
806
        })
3✔
807
        if err != nil {
4✔
808
                return RepositoryInfo{}, err
1✔
809
        }
1✔
810

811
        return RepositoryInfo{RepositoryVisibility: getGitHubRepositoryVisibility(repo), CloneInfo: CloneInfo{HTTP: repo.GetCloneURL(), SSH: repo.GetSSHURL()}}, nil
2✔
812
}
813

814
// GetCommitBySha on GitHub
815
func (client *GitHubClient) GetCommitBySha(ctx context.Context, owner, repository, sha string) (CommitInfo, error) {
7✔
816
        err := validateParametersNotBlank(map[string]string{
7✔
817
                "owner":      owner,
7✔
818
                "repository": repository,
7✔
819
                "sha":        sha,
7✔
820
        })
7✔
821
        if err != nil {
11✔
822
                return CommitInfo{}, err
4✔
823
        }
4✔
824

825
        var commit *github.RepositoryCommit
3✔
826
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
6✔
827
                var ghResponse *github.Response
3✔
828
                commit, ghResponse, err = client.ghClient.Repositories.GetCommit(ctx, owner, repository, sha, nil)
3✔
829
                return ghResponse, err
3✔
830
        })
3✔
831
        if err != nil {
5✔
832
                return CommitInfo{}, err
2✔
833
        }
2✔
834

835
        return mapGitHubCommitToCommitInfo(commit), nil
1✔
836
}
837

838
// CreateLabel on GitHub
839
func (client *GitHubClient) CreateLabel(ctx context.Context, owner, repository string, labelInfo LabelInfo) error {
6✔
840
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository, "LabelInfo.name": labelInfo.Name})
6✔
841
        if err != nil {
10✔
842
                return err
4✔
843
        }
4✔
844

845
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
846
                var ghResponse *github.Response
2✔
847
                _, ghResponse, err = client.ghClient.Issues.CreateLabel(ctx, owner, repository, &github.Label{
2✔
848
                        Name:        &labelInfo.Name,
2✔
849
                        Description: &labelInfo.Description,
2✔
850
                        Color:       &labelInfo.Color,
2✔
851
                })
2✔
852
                return ghResponse, err
2✔
853
        })
2✔
854
}
855

856
// GetLabel on GitHub
857
func (client *GitHubClient) GetLabel(ctx context.Context, owner, repository, name string) (*LabelInfo, error) {
7✔
858
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository, "name": name})
7✔
859
        if err != nil {
11✔
860
                return nil, err
4✔
861
        }
4✔
862

863
        var labelInfo *LabelInfo
3✔
864
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
6✔
865
                var ghResponse *github.Response
3✔
866
                labelInfo, ghResponse, err = client.executeGetLabel(ctx, owner, repository, name)
3✔
867
                return ghResponse, err
3✔
868
        })
3✔
869
        return labelInfo, err
3✔
870
}
871

872
func (client *GitHubClient) executeGetLabel(ctx context.Context, owner, repository, name string) (*LabelInfo, *github.Response, error) {
3✔
873
        label, ghResponse, err := client.ghClient.Issues.GetLabel(ctx, owner, repository, name)
3✔
874
        if err != nil {
5✔
875
                if ghResponse != nil && ghResponse.Response != nil && ghResponse.Response.StatusCode == http.StatusNotFound {
3✔
876
                        return nil, ghResponse, nil
1✔
877
                }
1✔
878
                return nil, ghResponse, err
1✔
879
        }
880

881
        labelInfo := &LabelInfo{
1✔
882
                Name:        *label.Name,
1✔
883
                Description: *label.Description,
1✔
884
                Color:       *label.Color,
1✔
885
        }
1✔
886
        return labelInfo, ghResponse, nil
1✔
887
}
888

889
// ListPullRequestLabels on GitHub
890
func (client *GitHubClient) ListPullRequestLabels(ctx context.Context, owner, repository string, pullRequestID int) ([]string, error) {
5✔
891
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
5✔
892
        if err != nil {
8✔
893
                return nil, err
3✔
894
        }
3✔
895

896
        results := []string{}
2✔
897
        for nextPage := 0; ; nextPage++ {
4✔
898
                options := &github.ListOptions{Page: nextPage}
2✔
899
                var labels []*github.Label
2✔
900
                var ghResponse *github.Response
2✔
901
                err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
902
                        labels, ghResponse, err = client.ghClient.Issues.ListLabelsByIssue(ctx, owner, repository, pullRequestID, options)
2✔
903
                        return ghResponse, err
2✔
904
                })
2✔
905
                if err != nil {
3✔
906
                        return nil, err
1✔
907
                }
1✔
908
                for _, label := range labels {
2✔
909
                        results = append(results, *label.Name)
1✔
910
                }
1✔
911
                if nextPage+1 >= ghResponse.LastPage {
2✔
912
                        break
1✔
913
                }
914
        }
915
        return results, nil
1✔
916
}
917

918
func (client *GitHubClient) ListPullRequestsAssociatedWithCommit(ctx context.Context, owner, repository string, commitSHA string) ([]PullRequestInfo, error) {
2✔
919
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
2✔
920
        if err != nil {
2✔
921
                return nil, err
×
922
        }
×
923

924
        var pulls []*github.PullRequest
2✔
925
        if err = client.runWithRateLimitRetries(func() (ghResponse *github.Response, err error) {
4✔
926
                pulls, ghResponse, err = client.ghClient.PullRequests.ListPullRequestsWithCommit(ctx, owner, repository, commitSHA, nil)
2✔
927
                return ghResponse, err
2✔
928
        }); err != nil {
3✔
929
                return nil, err
1✔
930
        }
1✔
931
        return mapGitHubPullRequestToPullRequestInfoList(pulls, false)
1✔
932
}
933

934
// UnlabelPullRequest on GitHub
935
func (client *GitHubClient) UnlabelPullRequest(ctx context.Context, owner, repository, name string, pullRequestID int) error {
5✔
936
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository})
5✔
937
        if err != nil {
8✔
938
                return err
3✔
939
        }
3✔
940

941
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
942
                return client.ghClient.Issues.RemoveLabelForIssue(ctx, owner, repository, pullRequestID, name)
2✔
943
        })
2✔
944
}
945

946
// UploadCodeScanning to GitHub Security tab
947
func (client *GitHubClient) UploadCodeScanning(ctx context.Context, owner, repository, branch, sarifContent string) (id string, err error) {
2✔
948
        commit, err := client.GetLatestCommit(ctx, owner, repository, branch)
2✔
949
        if err != nil {
3✔
950
                return
1✔
951
        }
1✔
952

953
        commitSHA := commit.Hash
1✔
954
        branch = vcsutils.AddBranchPrefix(branch)
1✔
955
        client.logger.Debug(vcsutils.UploadingCodeScanning, repository, "/", branch)
1✔
956

1✔
957
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
2✔
958
                var ghResponse *github.Response
1✔
959
                id, ghResponse, err = client.executeUploadCodeScanning(ctx, owner, repository, branch, commitSHA, sarifContent)
1✔
960
                return ghResponse, err
1✔
961
        })
1✔
962
        return
1✔
963
}
964

965
func (client *GitHubClient) executeUploadCodeScanning(ctx context.Context, owner, repository, branch, commitSHA, sarifContent string) (id string, ghResponse *github.Response, err error) {
1✔
966
        encodedSarif, err := encodeScanningResult(sarifContent)
1✔
967
        if err != nil {
1✔
968
                return
×
969
        }
×
970

971
        sarifID, ghResponse, err := client.ghClient.CodeScanning.UploadSarif(ctx, owner, repository, &github.SarifAnalysis{
1✔
972
                CommitSHA: &commitSHA,
1✔
973
                Ref:       &branch,
1✔
974
                Sarif:     &encodedSarif,
1✔
975
        })
1✔
976

1✔
977
        // According to go-github API - successful ghResponse will return 202 status code
1✔
978
        // The body of the ghResponse will appear in the error, and the Sarif struct will be empty.
1✔
979
        if err != nil && ghResponse.Response.StatusCode != http.StatusAccepted {
1✔
980
                return
×
981
        }
×
982

983
        id, err = handleGitHubUploadSarifID(sarifID, err)
1✔
984
        return
1✔
985
}
986

987
func handleGitHubUploadSarifID(sarifID *github.SarifID, uploadSarifErr error) (id string, err error) {
1✔
988
        if sarifID != nil && *sarifID.ID != "" {
2✔
989
                id = *sarifID.ID
1✔
990
                return
1✔
991
        }
1✔
992
        var result map[string]string
×
993
        var ghAcceptedError *github.AcceptedError
×
994
        if errors.As(uploadSarifErr, &ghAcceptedError) {
×
995
                if err = json.Unmarshal(ghAcceptedError.Raw, &result); err != nil {
×
996
                        return
×
997
                }
×
998
                id = result["id"]
×
999
        }
1000
        return
×
1001
}
1002

1003
// DownloadFileFromRepo on GitHub
1004
func (client *GitHubClient) DownloadFileFromRepo(ctx context.Context, owner, repository, branch, path string) (content []byte, statusCode int, err error) {
3✔
1005
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
6✔
1006
                var ghResponse *github.Response
3✔
1007
                content, statusCode, ghResponse, err = client.executeDownloadFileFromRepo(ctx, owner, repository, branch, path)
3✔
1008
                return ghResponse, err
3✔
1009
        })
3✔
1010
        return
3✔
1011
}
1012

1013
func (client *GitHubClient) executeDownloadFileFromRepo(ctx context.Context, owner, repository, branch, path string) (content []byte, statusCode int, ghResponse *github.Response, err error) {
3✔
1014
        body, ghResponse, err := client.ghClient.Repositories.DownloadContents(ctx, owner, repository, path, &github.RepositoryContentGetOptions{Ref: branch})
3✔
1015
        defer func() {
6✔
1016
                if body != nil {
4✔
1017
                        err = errors.Join(err, body.Close())
1✔
1018
                }
1✔
1019
        }()
1020

1021
        if ghResponse == nil || ghResponse.Response == nil {
4✔
1022
                return
1✔
1023
        }
1✔
1024

1025
        statusCode = ghResponse.StatusCode
2✔
1026
        if err != nil && statusCode != http.StatusOK {
2✔
1027
                err = fmt.Errorf("expected %d status code while received %d status code with error:\n%s", http.StatusOK, ghResponse.StatusCode, err)
×
1028
                return
×
1029
        }
×
1030

1031
        if body != nil {
3✔
1032
                content, err = io.ReadAll(body)
1✔
1033
        }
1✔
1034
        return
2✔
1035
}
1036

1037
// GetRepositoryEnvironmentInfo on GitHub
1038
func (client *GitHubClient) GetRepositoryEnvironmentInfo(ctx context.Context, owner, repository, name string) (RepositoryEnvironmentInfo, error) {
2✔
1039
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository, "name": name})
2✔
1040
        if err != nil {
2✔
1041
                return RepositoryEnvironmentInfo{}, err
×
1042
        }
×
1043

1044
        var repositoryEnvInfo *RepositoryEnvironmentInfo
2✔
1045
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
1046
                var ghResponse *github.Response
2✔
1047
                repositoryEnvInfo, ghResponse, err = client.executeGetRepositoryEnvironmentInfo(ctx, owner, repository, name)
2✔
1048
                return ghResponse, err
2✔
1049
        })
2✔
1050
        return *repositoryEnvInfo, err
2✔
1051
}
1052

1053
func (client *GitHubClient) CreateBranch(ctx context.Context, owner, repository, sourceBranch, newBranch string) error {
2✔
1054
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository, "sourceBranch": sourceBranch, "newBranch": newBranch})
2✔
1055
        if err != nil {
2✔
NEW
1056
                return err
×
NEW
1057
        }
×
1058

1059
        var sourceBranchRef *github.Branch
2✔
1060
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
1061
                sourceBranchRef, _, err = client.ghClient.Repositories.GetBranch(ctx, owner, repository, sourceBranch, 3)
2✔
1062
                if err != nil {
3✔
1063
                        return nil, err
1✔
1064
                }
1✔
1065
                return nil, nil
1✔
1066
        })
1067
        if err != nil {
3✔
1068
                return err
1✔
1069
        }
1✔
1070

1071
        latestCommitSHA := sourceBranchRef.Commit.SHA
1✔
1072
        ref := &github.Reference{
1✔
1073
                Ref:    github.String("refs/heads/" + newBranch),
1✔
1074
                Object: &github.GitObject{SHA: latestCommitSHA},
1✔
1075
        }
1✔
1076

1✔
1077
        return client.runWithRateLimitRetries(func() (*github.Response, error) {
2✔
1078
                _, _, err = client.ghClient.Git.CreateRef(ctx, owner, repository, ref)
1✔
1079
                if err != nil {
1✔
NEW
1080
                        return nil, err
×
NEW
1081
                }
×
1082
                return nil, nil
1✔
1083
        })
1084
}
1085

1086
func (client *GitHubClient) AddOrganizationSecret(ctx context.Context, owner, secretName, secretValue string) error {
2✔
1087
        err := validateParametersNotBlank(map[string]string{"secretName": secretName, "owner": owner, "secretValue": secretValue})
2✔
1088
        if err != nil {
2✔
NEW
1089
                return err
×
NEW
1090
        }
×
1091

1092
        publicKey, _, err := client.ghClient.Actions.GetOrgPublicKey(ctx, owner)
2✔
1093
        if err != nil {
3✔
1094
                return err
1✔
1095
        }
1✔
1096

1097
        encryptedValue, err := encryptSecret(publicKey, secretValue)
1✔
1098
        if err != nil {
1✔
NEW
1099
                return err
×
NEW
1100
        }
×
1101

1102
        secret := &github.EncryptedSecret{
1✔
1103
                Name:           secretName,
1✔
1104
                KeyID:          publicKey.GetKeyID(),
1✔
1105
                EncryptedValue: encryptedValue,
1✔
1106
                Visibility:     "all",
1✔
1107
        }
1✔
1108

1✔
1109
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
2✔
1110
                _, err = client.ghClient.Actions.CreateOrUpdateOrgSecret(ctx, owner, secret)
1✔
1111
                return nil, err
1✔
1112
        })
1✔
1113
        return err
1✔
1114
}
1115

1116
func (client *GitHubClient) AllowWorkflows(ctx context.Context, owner string) error {
2✔
1117
        err := validateParametersNotBlank(map[string]string{"owner": owner})
2✔
1118
        if err != nil {
2✔
NEW
1119
                return err
×
NEW
1120
        }
×
1121

1122
        requestBody := &github.ActionsPermissions{
2✔
1123
                AllowedActions:      github.String("all"),
2✔
1124
                EnabledRepositories: github.String("all"),
2✔
1125
        }
2✔
1126

2✔
1127
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
1128
                _, _, err = client.ghClient.Actions.EditActionsPermissions(ctx, owner, *requestBody)
2✔
1129
                return nil, err
2✔
1130
        })
2✔
1131
        return err
2✔
1132
}
1133

1134
func (client *GitHubClient) GetRepoCollaborators(ctx context.Context, owner, repo, affiliation, permission string) ([]string, error) {
2✔
1135
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repo": repo, "affiliation": affiliation, "permission": permission})
2✔
1136
        if err != nil {
2✔
NEW
1137
                return nil, err
×
NEW
1138
        }
×
1139

1140
        var collaborators []*github.User
2✔
1141
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
1142
                var ghResponse *github.Response
2✔
1143
                var err error
2✔
1144
                collaborators, ghResponse, err = client.ghClient.Repositories.ListCollaborators(ctx, owner, repo, &github.ListCollaboratorsOptions{
2✔
1145
                        Affiliation: affiliation,
2✔
1146
                        Permission:  permission,
2✔
1147
                })
2✔
1148
                return ghResponse, err
2✔
1149
        })
2✔
1150
        if err != nil {
3✔
1151
                return nil, err
1✔
1152
        }
1✔
1153

1154
        var names []string
1✔
1155
        for _, collab := range collaborators {
2✔
1156
                names = append(names, collab.GetLogin())
1✔
1157
        }
1✔
1158
        return names, nil
1✔
1159
}
1160

1161
func (client *GitHubClient) GetRepoTeamsByPermissions(ctx context.Context, owner, repo string, permissions []string) ([]int64, error) {
2✔
1162
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repo": repo, "permissions": strings.Join(permissions, ",")})
2✔
1163
        if err != nil {
2✔
NEW
1164
                return nil, err
×
NEW
1165
        }
×
1166

1167
        var allTeams []*github.Team
2✔
1168
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
1169
                var resp *github.Response
2✔
1170
                var err error
2✔
1171
                allTeams, resp, err = client.ghClient.Repositories.ListTeams(ctx, owner, repo, nil)
2✔
1172
                return resp, err
2✔
1173
        })
2✔
1174
        if err != nil {
3✔
1175
                return nil, err
1✔
1176
        }
1✔
1177

1178
        permMap := make(map[string]bool)
1✔
1179
        for _, p := range permissions {
2✔
1180
                permMap[strings.ToLower(p)] = true
1✔
1181
        }
1✔
1182

1183
        var matchedTeams []int64
1✔
1184
        for _, team := range allTeams {
2✔
1185
                if permMap[strings.ToLower(team.GetPermission())] {
2✔
1186
                        matchedTeams = append(matchedTeams, team.GetID())
1✔
1187
                }
1✔
1188
        }
1189

1190
        return matchedTeams, nil
1✔
1191
}
1192

1193
func (client *GitHubClient) CreateOrUpdateEnvironment(ctx context.Context, owner, repo, envName string, teams []int64, users []string) error {
2✔
1194
        err := validateParametersNotBlank(map[string]string{"owner": owner, "repo": repo, "envName": envName})
2✔
1195
        if err != nil {
2✔
NEW
1196
                return err
×
NEW
1197
        }
×
1198

1199
        envReviewers := make([]*github.EnvReviewers, 0)
2✔
1200
        for _, team := range teams {
4✔
1201
                envReviewers = append(envReviewers, &github.EnvReviewers{
2✔
1202
                        Type: github.String("Team"),
2✔
1203
                        ID:   &team,
2✔
1204
                })
2✔
1205
        }
2✔
1206

1207
        if len(envReviewers) >= ghMaxEnvReviewers {
2✔
NEW
1208
                envReviewers = envReviewers[:ghMaxEnvReviewers]
×
NEW
1209
                _, _, err := client.ghClient.Repositories.CreateUpdateEnvironment(ctx, owner, repo, envName, &github.CreateUpdateEnvironment{
×
NEW
1210
                        Reviewers: envReviewers,
×
NEW
1211
                })
×
NEW
1212
                return err
×
NEW
1213
        }
×
1214

1215
        for _, userName := range users {
2✔
NEW
1216
                user, _, err := client.ghClient.Users.Get(ctx, userName)
×
NEW
1217

×
NEW
1218
                if err != nil {
×
NEW
1219
                        return err
×
NEW
1220
                }
×
NEW
1221
                userId := user.GetID()
×
NEW
1222
                envReviewers = append(envReviewers, &github.EnvReviewers{
×
NEW
1223
                        Type: github.String("User"),
×
NEW
1224
                        ID:   github.Int64(userId),
×
NEW
1225
                })
×
1226
        }
1227

1228
        if len(envReviewers) >= ghMaxEnvReviewers {
2✔
NEW
1229
                envReviewers = envReviewers[:ghMaxEnvReviewers]
×
NEW
1230
                _, _, err := client.ghClient.Repositories.CreateUpdateEnvironment(ctx, owner, repo, envName, &github.CreateUpdateEnvironment{
×
NEW
1231
                        Reviewers: envReviewers,
×
NEW
1232
                })
×
NEW
1233
                return err
×
NEW
1234
        }
×
1235

1236
        _, _, err = client.ghClient.Repositories.CreateUpdateEnvironment(ctx, owner, repo, envName, &github.CreateUpdateEnvironment{
2✔
1237
                Reviewers: envReviewers,
2✔
1238
        })
2✔
1239
        return err
2✔
1240
}
1241

1242
func (client *GitHubClient) CommitAndPushFiles(
1243
        ctx context.Context,
1244
        owner, repo, sourceBranch, commitMessage, authorName, authorEmail string,
1245
        files []FileToCommit,
1246
) error {
2✔
1247
        if len(files) == 0 {
2✔
NEW
1248
                return errors.New("no files provided to commit")
×
NEW
1249
        }
×
1250

1251
        ref, _, err := client.ghClient.Git.GetRef(ctx, owner, repo, "refs/heads/"+sourceBranch)
2✔
1252
        if err != nil {
3✔
1253
                return fmt.Errorf("failed to get branch ref: %w", err)
1✔
1254
        }
1✔
1255

1256
        parentCommit, _, err := client.ghClient.Git.GetCommit(ctx, owner, repo, *ref.Object.SHA)
1✔
1257
        if err != nil {
1✔
NEW
1258
                return fmt.Errorf("failed to get parent commit: %w", err)
×
NEW
1259
        }
×
1260

1261
        var treeEntries []*github.TreeEntry
1✔
1262
        for _, file := range files {
2✔
1263
                blob, _, err := client.ghClient.Git.CreateBlob(ctx, owner, repo, &github.Blob{
1✔
1264
                        Content:  github.String(file.Content),
1✔
1265
                        Encoding: github.String("utf-8"),
1✔
1266
                })
1✔
1267
                if err != nil {
1✔
NEW
1268
                        return fmt.Errorf("failed to create blob for %s: %w", file.Path, err)
×
NEW
1269
                }
×
1270

1271
                // Add each file to the treeEntries
1272
                treeEntries = append(treeEntries, &github.TreeEntry{
1✔
1273
                        Path: github.String(file.Path),
1✔
1274
                        Mode: github.String(regularFileCode),
1✔
1275
                        Type: github.String("blob"),
1✔
1276
                        SHA:  blob.SHA,
1✔
1277
                })
1✔
1278
        }
1279

1280
        tree, _, err := client.ghClient.Git.CreateTree(ctx, owner, repo, *parentCommit.Tree.SHA, treeEntries)
1✔
1281
        if err != nil {
1✔
NEW
1282
                return fmt.Errorf("failed to create tree: %w", err)
×
NEW
1283
        }
×
1284

1285
        commit := &github.Commit{
1✔
1286
                Message: github.String(commitMessage),
1✔
1287
                Tree:    tree,
1✔
1288
                Parents: []*github.Commit{{SHA: parentCommit.SHA}},
1✔
1289
                Author: &github.CommitAuthor{
1✔
1290
                        Name:  github.String(authorName),
1✔
1291
                        Email: github.String(authorEmail),
1✔
1292
                        Date:  &github.Timestamp{Time: time.Now()},
1✔
1293
                },
1✔
1294
        }
1✔
1295

1✔
1296
        newCommit, _, err := client.ghClient.Git.CreateCommit(ctx, owner, repo, commit, nil)
1✔
1297
        if err != nil {
1✔
NEW
1298
                return fmt.Errorf("failed to create commit: %w", err)
×
NEW
1299
        }
×
1300

1301
        ref.Object.SHA = newCommit.SHA
1✔
1302
        _, _, err = client.ghClient.Git.UpdateRef(ctx, owner, repo, ref, false)
1✔
1303
        if err != nil {
1✔
NEW
1304
                return fmt.Errorf("failed to update branch ref: %w", err)
×
NEW
1305
        }
×
1306
        return nil
1✔
1307
}
1308

1309
func (client *GitHubClient) MergePullRequest(ctx context.Context, owner, repo string, prNumber int, commitMessage string) error {
2✔
1310
        err := client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
1311
                _, resp, err := client.ghClient.PullRequests.Merge(ctx, owner, repo, prNumber, commitMessage, nil)
2✔
1312
                return resp, err
2✔
1313
        })
2✔
1314
        return err
2✔
1315
}
1316

1317
func (client *GitHubClient) executeGetRepositoryEnvironmentInfo(ctx context.Context, owner, repository, name string) (*RepositoryEnvironmentInfo, *github.Response, error) {
2✔
1318
        environment, ghResponse, err := client.ghClient.Repositories.GetEnvironment(ctx, owner, repository, name)
2✔
1319
        if err != nil {
3✔
1320
                return &RepositoryEnvironmentInfo{}, ghResponse, err
1✔
1321
        }
1✔
1322

1323
        if err = vcsutils.CheckResponseStatusWithBody(ghResponse.Response, http.StatusOK); err != nil {
1✔
1324
                return &RepositoryEnvironmentInfo{}, ghResponse, err
×
1325
        }
×
1326

1327
        reviewers, err := extractGitHubEnvironmentReviewers(environment)
1✔
1328
        if err != nil {
1✔
1329
                return &RepositoryEnvironmentInfo{}, ghResponse, err
×
1330
        }
×
1331

1332
        return &RepositoryEnvironmentInfo{
1✔
1333
                        Name:      environment.GetName(),
1✔
1334
                        Url:       environment.GetURL(),
1✔
1335
                        Reviewers: reviewers,
1✔
1336
                },
1✔
1337
                ghResponse,
1✔
1338
                nil
1✔
1339
}
1340

1341
func (client *GitHubClient) GetModifiedFiles(ctx context.Context, owner, repository, refBefore, refAfter string) ([]string, error) {
6✔
1342
        err := validateParametersNotBlank(map[string]string{
6✔
1343
                "owner":      owner,
6✔
1344
                "repository": repository,
6✔
1345
                "refBefore":  refBefore,
6✔
1346
                "refAfter":   refAfter,
6✔
1347
        })
6✔
1348
        if err != nil {
10✔
1349
                return nil, err
4✔
1350
        }
4✔
1351

1352
        var fileNamesList []string
2✔
1353
        err = client.runWithRateLimitRetries(func() (*github.Response, error) {
4✔
1354
                var ghResponse *github.Response
2✔
1355
                fileNamesList, ghResponse, err = client.executeGetModifiedFiles(ctx, owner, repository, refBefore, refAfter)
2✔
1356
                return ghResponse, err
2✔
1357
        })
2✔
1358
        return fileNamesList, err
2✔
1359
}
1360

1361
func (client *GitHubClient) executeGetModifiedFiles(ctx context.Context, owner, repository, refBefore, refAfter string) ([]string, *github.Response, error) {
2✔
1362
        // According to the https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits
2✔
1363
        // the list of changed files is always returned with the first page fully,
2✔
1364
        // so we don't need to iterate over other pages to get additional info about the files.
2✔
1365
        // And we also do not need info about the change that is why we can limit only to a single entity.
2✔
1366
        listOptions := &github.ListOptions{PerPage: 1}
2✔
1367

2✔
1368
        comparison, ghResponse, err := client.ghClient.Repositories.CompareCommits(ctx, owner, repository, refBefore, refAfter, listOptions)
2✔
1369
        if err != nil {
3✔
1370
                return nil, ghResponse, err
1✔
1371
        }
1✔
1372

1373
        if err = vcsutils.CheckResponseStatusWithBody(ghResponse.Response, http.StatusOK); err != nil {
1✔
1374
                return nil, ghResponse, err
×
1375
        }
×
1376

1377
        fileNamesSet := datastructures.MakeSet[string]()
1✔
1378
        for _, file := range comparison.Files {
18✔
1379
                fileNamesSet.Add(vcsutils.DefaultIfNotNil(file.Filename))
17✔
1380
                fileNamesSet.Add(vcsutils.DefaultIfNotNil(file.PreviousFilename))
17✔
1381
        }
17✔
1382

1383
        _ = fileNamesSet.Remove("") // Make sure there are no blank filepath.
1✔
1384
        fileNamesList := fileNamesSet.ToSlice()
1✔
1385
        sort.Strings(fileNamesList)
1✔
1386

1✔
1387
        return fileNamesList, ghResponse, nil
1✔
1388
}
1389

1390
// Extract code reviewers from environment
1391
func extractGitHubEnvironmentReviewers(environment *github.Environment) ([]string, error) {
2✔
1392
        var reviewers []string
2✔
1393
        protectionRules := environment.ProtectionRules
2✔
1394
        if protectionRules == nil {
2✔
1395
                return reviewers, nil
×
1396
        }
×
1397
        reviewerStruct := repositoryEnvironmentReviewer{}
2✔
1398
        for _, rule := range protectionRules {
4✔
1399
                for _, reviewer := range rule.Reviewers {
5✔
1400
                        if err := mapstructure.Decode(reviewer.Reviewer, &reviewerStruct); err != nil {
3✔
1401
                                return []string{}, err
×
1402
                        }
×
1403
                        reviewers = append(reviewers, reviewerStruct.Login)
3✔
1404
                }
1405
        }
1406
        return reviewers, nil
2✔
1407
}
1408

1409
func createGitHubHook(token, payloadURL string, webhookEvents ...vcsutils.WebhookEvent) *github.Hook {
4✔
1410
        return &github.Hook{
4✔
1411
                Events: getGitHubWebhookEvents(webhookEvents...),
4✔
1412
                Config: map[string]interface{}{
4✔
1413
                        "url":          payloadURL,
4✔
1414
                        "content_type": "json",
4✔
1415
                        "secret":       token,
4✔
1416
                },
4✔
1417
        }
4✔
1418
}
4✔
1419

1420
// Get varargs of webhook events and return a slice of GitHub webhook events
1421
func getGitHubWebhookEvents(webhookEvents ...vcsutils.WebhookEvent) []string {
4✔
1422
        events := datastructures.MakeSet[string]()
4✔
1423
        for _, event := range webhookEvents {
16✔
1424
                switch event {
12✔
1425
                case vcsutils.PrOpened, vcsutils.PrEdited, vcsutils.PrMerged, vcsutils.PrRejected:
8✔
1426
                        events.Add("pull_request")
8✔
1427
                case vcsutils.Push, vcsutils.TagPushed, vcsutils.TagRemoved:
4✔
1428
                        events.Add("push")
4✔
1429
                }
1430
        }
1431
        return events.ToSlice()
4✔
1432
}
1433

1434
func getGitHubRepositoryVisibility(repo *github.Repository) RepositoryVisibility {
5✔
1435
        switch *repo.Visibility {
5✔
1436
        case "public":
3✔
1437
                return Public
3✔
1438
        case "internal":
1✔
1439
                return Internal
1✔
1440
        default:
1✔
1441
                return Private
1✔
1442
        }
1443
}
1444

1445
func getGitHubCommitState(commitState CommitStatus) string {
7✔
1446
        switch commitState {
7✔
1447
        case Pass:
1✔
1448
                return "success"
1✔
1449
        case Fail:
1✔
1450
                return "failure"
1✔
1451
        case Error:
3✔
1452
                return "error"
3✔
1453
        case InProgress:
1✔
1454
                return "pending"
1✔
1455
        }
1456
        return ""
1✔
1457
}
1458

1459
func mapGitHubCommitToCommitInfo(commit *github.RepositoryCommit) CommitInfo {
8✔
1460
        parents := make([]string, len(commit.Parents))
8✔
1461
        for i, c := range commit.Parents {
15✔
1462
                parents[i] = c.GetSHA()
7✔
1463
        }
7✔
1464
        details := commit.GetCommit()
8✔
1465
        return CommitInfo{
8✔
1466
                Hash:          commit.GetSHA(),
8✔
1467
                AuthorName:    details.GetAuthor().GetName(),
8✔
1468
                CommitterName: details.GetCommitter().GetName(),
8✔
1469
                Url:           commit.GetURL(),
8✔
1470
                Timestamp:     details.GetCommitter().GetDate().UTC().Unix(),
8✔
1471
                Message:       details.GetMessage(),
8✔
1472
                ParentHashes:  parents,
8✔
1473
                AuthorEmail:   details.GetAuthor().GetEmail(),
8✔
1474
        }
8✔
1475
}
1476

1477
func mapGitHubIssuesCommentToCommentInfoList(commentsList []*github.IssueComment) (res []CommentInfo, err error) {
1✔
1478
        for _, comment := range commentsList {
3✔
1479
                res = append(res, CommentInfo{
2✔
1480
                        ID:      comment.GetID(),
2✔
1481
                        Content: comment.GetBody(),
2✔
1482
                        Created: comment.GetCreatedAt().Time,
2✔
1483
                })
2✔
1484
        }
2✔
1485
        return
1✔
1486
}
1487

1488
func mapGitHubPullRequestToPullRequestInfoList(pullRequestList []*github.PullRequest, withBody bool) (res []PullRequestInfo, err error) {
3✔
1489
        var mappedPullRequest PullRequestInfo
3✔
1490
        for _, pullRequest := range pullRequestList {
6✔
1491
                mappedPullRequest, err = mapGitHubPullRequestToPullRequestInfo(pullRequest, withBody)
3✔
1492
                if err != nil {
3✔
1493
                        return
×
1494
                }
×
1495
                res = append(res, mappedPullRequest)
3✔
1496
        }
1497
        return
3✔
1498
}
1499

1500
func encodeScanningResult(data string) (string, error) {
1✔
1501
        compressedScan, err := base64.EncodeGzip([]byte(data), 6)
1✔
1502
        if err != nil {
1✔
1503
                return "", err
×
1504
        }
×
1505

1506
        return compressedScan, err
1✔
1507
}
1508

1509
type repositoryEnvironmentReviewer struct {
1510
        Login string `mapstructure:"login"`
1511
}
1512

1513
func shouldRetryIfRateLimitExceeded(ghResponse *github.Response, requestError error) bool {
107✔
1514
        if ghResponse == nil || ghResponse.Response == nil {
153✔
1515
                return false
46✔
1516
        }
46✔
1517

1518
        if !slices.Contains(rateLimitRetryStatuses, ghResponse.StatusCode) {
120✔
1519
                return false
59✔
1520
        }
59✔
1521

1522
        // In case of encountering a rate limit abuse, it's advisable to observe a considerate delay before attempting a retry.
1523
        // This prevents immediate retries within the current sequence, allowing a respectful interval before reattempting the request.
1524
        if requestError != nil && isRateLimitAbuseError(requestError) {
3✔
1525
                return false
1✔
1526
        }
1✔
1527

1528
        body, err := io.ReadAll(ghResponse.Body)
1✔
1529
        if err != nil {
1✔
1530
                return false
×
1531
        }
×
1532
        return strings.Contains(string(body), "rate limit")
1✔
1533
}
1534

1535
func isRateLimitAbuseError(requestError error) bool {
4✔
1536
        var abuseRateLimitError *github.AbuseRateLimitError
4✔
1537
        var rateLimitError *github.RateLimitError
4✔
1538
        return errors.As(requestError, &abuseRateLimitError) || errors.As(requestError, &rateLimitError)
4✔
1539
}
4✔
1540

1541
func encryptSecret(publicKey *github.PublicKey, secretValue string) (string, error) {
1✔
1542
        publicKeyBytes, err := base64Utils.StdEncoding.DecodeString(publicKey.GetKey())
1✔
1543
        if err != nil {
1✔
NEW
1544
                return "", err
×
NEW
1545
        }
×
1546

1547
        var publicKeyDecoded [32]byte
1✔
1548
        copy(publicKeyDecoded[:], publicKeyBytes)
1✔
1549

1✔
1550
        encrypted, err := box.SealAnonymous(nil, []byte(secretValue), &publicKeyDecoded, rand.Reader)
1✔
1551
        if err != nil {
1✔
NEW
1552
                return "", err
×
NEW
1553
        }
×
1554

1555
        encryptedBase64 := base64Utils.StdEncoding.EncodeToString(encrypted)
1✔
1556
        return encryptedBase64, nil
1✔
1557
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc