mirror of https://github.com/harness/drone.git
234 lines
6.7 KiB
Go
234 lines
6.7 KiB
Go
// Copyright 2023 Harness, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pullreq
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
apiauth "github.com/harness/gitness/app/api/auth"
|
|
"github.com/harness/gitness/app/auth"
|
|
"github.com/harness/gitness/app/auth/authz"
|
|
"github.com/harness/gitness/app/services/label"
|
|
"github.com/harness/gitness/app/store"
|
|
"github.com/harness/gitness/errors"
|
|
"github.com/harness/gitness/git"
|
|
gitness_store "github.com/harness/gitness/store"
|
|
"github.com/harness/gitness/store/database/dbtx"
|
|
"github.com/harness/gitness/types"
|
|
"github.com/harness/gitness/types/enum"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type ListService struct {
|
|
tx dbtx.Transactor
|
|
git git.Interface
|
|
authorizer authz.Authorizer
|
|
spaceStore store.SpaceStore
|
|
repoStore store.RepoStore
|
|
repoGitInfoCache store.RepoGitInfoCache
|
|
pullreqStore store.PullReqStore
|
|
labelSvc *label.Service
|
|
}
|
|
|
|
func NewListService(
|
|
tx dbtx.Transactor,
|
|
git git.Interface,
|
|
authorizer authz.Authorizer,
|
|
spaceStore store.SpaceStore,
|
|
repoStore store.RepoStore,
|
|
repoGitInfoCache store.RepoGitInfoCache,
|
|
pullreqStore store.PullReqStore,
|
|
labelSvc *label.Service,
|
|
) *ListService {
|
|
return &ListService{
|
|
tx: tx,
|
|
git: git,
|
|
authorizer: authorizer,
|
|
spaceStore: spaceStore,
|
|
repoStore: repoStore,
|
|
repoGitInfoCache: repoGitInfoCache,
|
|
pullreqStore: pullreqStore,
|
|
labelSvc: labelSvc,
|
|
}
|
|
}
|
|
|
|
// ListForSpace returns a list of pull requests and their respective repositories for a specific space.
|
|
//
|
|
//nolint:gocognit
|
|
func (c *ListService) ListForSpace(
|
|
ctx context.Context,
|
|
session *auth.Session,
|
|
space *types.Space,
|
|
includeSubspaces bool,
|
|
filter *types.PullReqFilter,
|
|
) ([]types.PullReqRepo, error) {
|
|
// list of unsupported filter options
|
|
filter.Sort = enum.PullReqSortUpdated // the only supported option, hardcoded in the SQL query
|
|
filter.Order = enum.OrderDesc // the only supported option, hardcoded in the SQL query
|
|
filter.Page = 0 // unsupported, pagination should be done with the UpdatedLt parameter
|
|
filter.UpdatedGt = 0 // unsupported
|
|
|
|
if includeSubspaces {
|
|
subspaces, err := c.spaceStore.GetDescendantsData(ctx, space.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get space descendant data: %w", err)
|
|
}
|
|
|
|
filter.SpaceIDs = make([]int64, 0, len(subspaces))
|
|
for i := range subspaces {
|
|
filter.SpaceIDs = append(filter.SpaceIDs, subspaces[i].ID)
|
|
}
|
|
} else {
|
|
filter.SpaceIDs = []int64{space.ID}
|
|
}
|
|
|
|
repoWhitelist := make(map[int64]struct{})
|
|
|
|
list := make([]*types.PullReq, 0, 16)
|
|
repoMap := make(map[int64]*types.Repository)
|
|
|
|
for loadMore := true; loadMore; {
|
|
const prLimit = 100
|
|
const repoLimit = 10
|
|
|
|
pullReqs, repoUnchecked, err := c.streamPullReqs(ctx, filter, prLimit, repoLimit, repoWhitelist)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load pull requests: %w", err)
|
|
}
|
|
|
|
loadMore = len(pullReqs) == prLimit || len(repoUnchecked) == repoLimit
|
|
if loadMore && len(pullReqs) > 0 {
|
|
filter.UpdatedLt = pullReqs[len(pullReqs)-1].Updated
|
|
}
|
|
|
|
for repoID := range repoUnchecked {
|
|
repo, err := c.repoStore.Find(ctx, repoID)
|
|
if errors.Is(err, gitness_store.ErrResourceNotFound) {
|
|
filter.RepoIDBlacklist = append(filter.RepoIDBlacklist, repoID)
|
|
continue
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("failed to find repo: %w", err)
|
|
}
|
|
|
|
err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView)
|
|
switch {
|
|
case err == nil:
|
|
repoWhitelist[repoID] = struct{}{}
|
|
repoMap[repoID] = repo
|
|
case errors.Is(err, apiauth.ErrNotAuthorized):
|
|
filter.RepoIDBlacklist = append(filter.RepoIDBlacklist, repoID)
|
|
default:
|
|
return nil, fmt.Errorf("failed to check access check: %w", err)
|
|
}
|
|
}
|
|
|
|
for _, pullReq := range pullReqs {
|
|
if _, ok := repoWhitelist[pullReq.TargetRepoID]; ok {
|
|
list = append(list, pullReq)
|
|
}
|
|
}
|
|
|
|
if len(list) >= filter.Size {
|
|
list = list[:filter.Size]
|
|
loadMore = false
|
|
}
|
|
}
|
|
|
|
if err := c.labelSvc.BackfillMany(ctx, list); err != nil {
|
|
return nil, fmt.Errorf("failed to backfill labels assigned to pull requests: %w", err)
|
|
}
|
|
|
|
for _, pr := range list {
|
|
if err := c.BackfillStats(ctx, pr); err != nil {
|
|
log.Ctx(ctx).Warn().Err(err).Msg("failed to backfill PR stats")
|
|
}
|
|
}
|
|
|
|
response := make([]types.PullReqRepo, len(list))
|
|
for i := range list {
|
|
response[i] = types.PullReqRepo{
|
|
PullRequest: list[i],
|
|
Repository: repoMap[list[i].TargetRepoID],
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// streamPullReqs loads pull requests until it gets either pullReqLimit pull requests
|
|
// or newRepoLimit distinct repositories.
|
|
func (c *ListService) streamPullReqs(
|
|
ctx context.Context,
|
|
opts *types.PullReqFilter,
|
|
pullReqLimit, newRepoLimit int,
|
|
repoWhitelist map[int64]struct{},
|
|
) ([]*types.PullReq, map[int64]struct{}, error) {
|
|
ctx, cancelFn := context.WithCancel(ctx)
|
|
defer cancelFn()
|
|
|
|
repoUnchecked := map[int64]struct{}{}
|
|
|
|
pullReqs := make([]*types.PullReq, 0, opts.Size)
|
|
ch, chErr := c.pullreqStore.Stream(ctx, opts)
|
|
for pr := range ch {
|
|
if pr == nil {
|
|
return pullReqs, repoUnchecked, nil
|
|
}
|
|
|
|
if _, ok := repoWhitelist[pr.TargetRepoID]; !ok {
|
|
repoUnchecked[pr.TargetRepoID] = struct{}{}
|
|
}
|
|
|
|
pullReqs = append(pullReqs, pr)
|
|
|
|
if len(pullReqs) >= pullReqLimit || len(repoUnchecked) >= newRepoLimit {
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := <-chErr; err != nil {
|
|
return nil, nil, fmt.Errorf("failed to stream pull requests: %w", err)
|
|
}
|
|
|
|
return pullReqs, repoUnchecked, nil
|
|
}
|
|
|
|
func (c *ListService) BackfillStats(ctx context.Context, pr *types.PullReq) error {
|
|
s := pr.Stats.DiffStats
|
|
if s.Commits != nil && s.FilesChanged != nil && s.Additions != nil && s.Deletions != nil {
|
|
return nil
|
|
}
|
|
|
|
repoGitInfo, err := c.repoGitInfoCache.Get(ctx, pr.TargetRepoID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed get repo git info to fetch diff stats: %w", err)
|
|
}
|
|
|
|
output, err := c.git.DiffStats(ctx, &git.DiffParams{
|
|
ReadParams: git.CreateReadParams(repoGitInfo),
|
|
BaseRef: pr.MergeBaseSHA,
|
|
HeadRef: pr.SourceSHA,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed get diff stats: %w", err)
|
|
}
|
|
|
|
pr.Stats.DiffStats = types.NewDiffStats(output.Commits, output.FilesChanged, output.Additions, output.Deletions)
|
|
|
|
return nil
|
|
}
|