mirror of https://github.com/harness/drone.git
feat: [CODE-967]: codeowners service e2e (#695)
parent
3701d4a24f
commit
adbe2f6f97
|
@ -0,0 +1,95 @@
|
|||
// 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/app/api/usererror"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/services/codeowners"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *Controller) CodeOwners(
|
||||
ctx context.Context,
|
||||
session *auth.Session,
|
||||
repoRef string,
|
||||
pullreqNum int64,
|
||||
) (types.CodeOwnerEvaluation, error) {
|
||||
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
|
||||
if err != nil {
|
||||
return types.CodeOwnerEvaluation{}, fmt.Errorf("failed to acquire access to repo: %w", err)
|
||||
}
|
||||
pr, err := c.pullreqStore.FindByNumber(ctx, repo.ID, pullreqNum)
|
||||
if err != nil {
|
||||
return types.CodeOwnerEvaluation{}, fmt.Errorf("failed to get pull request by number: %w", err)
|
||||
}
|
||||
|
||||
filteredCodeOwners, err := c.codeOwners.GetApplicableCodeOwnersForPR(ctx, repo, pr)
|
||||
if errors.Is(codeowners.ErrNotFound, err) {
|
||||
return types.CodeOwnerEvaluation{}, usererror.ErrNotFound
|
||||
}
|
||||
if codeowners.IsTooLargeError(err) {
|
||||
return types.CodeOwnerEvaluation{}, usererror.UnprocessableEntityf(err.Error())
|
||||
}
|
||||
if err != nil {
|
||||
return types.CodeOwnerEvaluation{}, fmt.Errorf("failed to get codeOwners: %w", err)
|
||||
}
|
||||
|
||||
if len(filteredCodeOwners.Entries) == 0 {
|
||||
return types.CodeOwnerEvaluation{
|
||||
EvaluationEntries: nil,
|
||||
FileSha: filteredCodeOwners.FileSHA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
reviewers, err := c.reviewerStore.List(ctx, pullreqNum)
|
||||
if err != nil {
|
||||
return types.CodeOwnerEvaluation{}, fmt.Errorf("failed to get reviewers by pr: %w", err)
|
||||
}
|
||||
|
||||
ownerEvaluation, err := c.codeOwners.Evaluate(ctx, filteredCodeOwners, reviewers)
|
||||
if err != nil {
|
||||
return types.CodeOwnerEvaluation{}, err
|
||||
}
|
||||
|
||||
return types.CodeOwnerEvaluation{
|
||||
EvaluationEntries: mapCodeOwnerEvaluation(ownerEvaluation),
|
||||
FileSha: ownerEvaluation.FileSha,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapCodeOwnerEvaluation(ownerEvaluation *codeowners.Evaluation) []types.CodeOwnerEvaluationEntry {
|
||||
codeOwnerEvaluationEntries := make([]types.CodeOwnerEvaluationEntry, len(ownerEvaluation.EvaluationEntries))
|
||||
for i, entry := range ownerEvaluation.EvaluationEntries {
|
||||
ownerEvaluations := make([]types.OwnerEvaluation, len(entry.OwnerEvaluations))
|
||||
for j, owner := range entry.OwnerEvaluations {
|
||||
ownerEvaluations[j] = types.OwnerEvaluation{
|
||||
Owner: owner.Owner,
|
||||
ReviewDecision: owner.ReviewDecision,
|
||||
ReviewSHA: owner.ReviewSHA,
|
||||
}
|
||||
}
|
||||
codeOwnerEvaluationEntries[i] = types.CodeOwnerEvaluationEntry{
|
||||
Pattern: entry.Pattern,
|
||||
OwnerEvaluations: ownerEvaluations,
|
||||
}
|
||||
}
|
||||
return codeOwnerEvaluationEntries
|
||||
}
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/harness/gitness/app/auth/authz"
|
||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||
"github.com/harness/gitness/app/services/codecomments"
|
||||
"github.com/harness/gitness/app/services/codeowners"
|
||||
"github.com/harness/gitness/app/services/protection"
|
||||
"github.com/harness/gitness/app/services/pullreq"
|
||||
"github.com/harness/gitness/app/sse"
|
||||
|
@ -58,6 +59,7 @@ type Controller struct {
|
|||
pullreqService *pullreq.Service
|
||||
protectionManager *protection.Manager
|
||||
sseStreamer sse.Streamer
|
||||
codeOwners *codeowners.Service
|
||||
}
|
||||
|
||||
func NewController(
|
||||
|
@ -81,6 +83,7 @@ func NewController(
|
|||
pullreqService *pullreq.Service,
|
||||
protectionManager *protection.Manager,
|
||||
sseStreamer sse.Streamer,
|
||||
codeowners *codeowners.Service,
|
||||
) *Controller {
|
||||
return &Controller{
|
||||
tx: tx,
|
||||
|
@ -103,6 +106,7 @@ func NewController(
|
|||
pullreqService: pullreqService,
|
||||
protectionManager: protectionManager,
|
||||
sseStreamer: sseStreamer,
|
||||
codeOwners: codeowners,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/bootstrap"
|
||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||
"github.com/harness/gitness/app/services/codeowners"
|
||||
"github.com/harness/gitness/app/services/protection"
|
||||
"github.com/harness/gitness/gitrpc"
|
||||
gitrpcenum "github.com/harness/gitness/gitrpc/enum"
|
||||
|
@ -142,6 +143,19 @@ func (c *Controller) Merge(
|
|||
return nil, nil, fmt.Errorf("failed to fetch protection rules for the repository: %w", err)
|
||||
}
|
||||
|
||||
ownersForPR, err := c.codeOwners.GetApplicableCodeOwnersForPR(ctx, sourceRepo, pr)
|
||||
if codeowners.IsTooLargeError(err) {
|
||||
return nil, nil, usererror.UnprocessableEntityf(err.Error())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find codeOwners for PR: %w", err)
|
||||
}
|
||||
|
||||
codeOwnerWithApproval, err := c.codeOwners.Evaluate(ctx, ownersForPR, reviewers)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get code owners with approval: %w", err)
|
||||
}
|
||||
|
||||
ruleOut, violations, err := protectionRules.CanMerge(ctx, protection.CanMergeInput{
|
||||
Actor: &session.Principal,
|
||||
IsSpaceOwner: isSpaceOwner,
|
||||
|
@ -151,6 +165,7 @@ func (c *Controller) Merge(
|
|||
Reviewers: reviewers,
|
||||
Method: in.Method,
|
||||
CheckResults: checkResults,
|
||||
CodeOwners: codeOwnerWithApproval,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to verify protection rules: %w", err)
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/harness/gitness/app/auth/authz"
|
||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||
"github.com/harness/gitness/app/services/codecomments"
|
||||
"github.com/harness/gitness/app/services/codeowners"
|
||||
"github.com/harness/gitness/app/services/protection"
|
||||
"github.com/harness/gitness/app/services/pullreq"
|
||||
"github.com/harness/gitness/app/sse"
|
||||
|
@ -45,6 +46,7 @@ func ProvideController(tx dbtx.Transactor, urlProvider url.Provider, authorizer
|
|||
rpcClient gitrpc.Interface, eventReporter *pullreqevents.Reporter,
|
||||
mtxManager lock.MutexManager, codeCommentMigrator *codecomments.Migrator,
|
||||
pullreqService *pullreq.Service, ruleManager *protection.Manager, sseStreamer sse.Streamer,
|
||||
codeOwners *codeowners.Service,
|
||||
) *Controller {
|
||||
return NewController(tx, urlProvider, authorizer,
|
||||
pullReqStore, pullReqActivityStore,
|
||||
|
@ -55,5 +57,5 @@ func ProvideController(tx dbtx.Transactor, urlProvider url.Provider, authorizer
|
|||
checkStore,
|
||||
rpcClient, eventReporter,
|
||||
mtxManager, codeCommentMigrator,
|
||||
pullreqService, ruleManager, sseStreamer)
|
||||
pullreqService, ruleManager, sseStreamer, codeOwners)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// 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 (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/app/api/controller/pullreq"
|
||||
"github.com/harness/gitness/app/api/render"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
)
|
||||
|
||||
func HandleCodeOwner(pullreqCtrl *pullreq.Controller) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
|
||||
repoRef, err := request.GetRepoRefFromPath(r)
|
||||
if err != nil {
|
||||
render.TranslatedUserError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
pullreqNumber, err := request.GetPullReqNumberFromPath(r)
|
||||
if err != nil {
|
||||
render.TranslatedUserError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
owners, err := pullreqCtrl.CodeOwners(ctx, session, repoRef, pullreqNumber)
|
||||
if err != nil {
|
||||
render.TranslatedUserError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.JSON(w, http.StatusOK, owners)
|
||||
}
|
||||
}
|
|
@ -541,4 +541,18 @@ func pullReqOperations(reflector *openapi3.Reflector) {
|
|||
_ = reflector.SetJSONResponse(&fileViewDelete, new(usererror.Error), http.StatusForbidden)
|
||||
_ = reflector.Spec.AddOperation(http.MethodDelete,
|
||||
"/repos/{repo_ref}/pullreq/{pullreq_number}/file-views/{file_path}", fileViewDelete)
|
||||
|
||||
codeOwners := openapi3.Operation{}
|
||||
codeOwners.WithTags("pullreq")
|
||||
codeOwners.WithMapOfAnything(map[string]interface{}{"operationId": "codeownersPullReq"})
|
||||
_ = reflector.SetRequest(&codeOwners, new(pullReqRequest), http.MethodGet)
|
||||
_ = reflector.SetJSONResponse(&codeOwners, types.CodeOwnerEvaluation{}, http.StatusOK)
|
||||
_ = reflector.SetJSONResponse(&codeOwners, new(usererror.Error), http.StatusUnprocessableEntity)
|
||||
_ = reflector.SetJSONResponse(&codeOwners, new(usererror.Error), http.StatusNotFound)
|
||||
_ = reflector.SetJSONResponse(&codeOwners, new(usererror.Error), http.StatusBadRequest)
|
||||
_ = reflector.SetJSONResponse(&codeOwners, new(usererror.Error), http.StatusInternalServerError)
|
||||
_ = reflector.SetJSONResponse(&codeOwners, new(usererror.Error), http.StatusUnauthorized)
|
||||
_ = reflector.SetJSONResponse(&codeOwners, new(usererror.Error), http.StatusForbidden)
|
||||
_ = reflector.Spec.AddOperation(http.MethodGet,
|
||||
"/repos/{repo_ref}/pullreq/{pullreq_number}/codeowners", codeOwners)
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ func Translate(err error) *Error {
|
|||
case errors.Is(err, blob.ErrNotFound):
|
||||
return ErrNotFound
|
||||
case errors.As(err, &maxBytesErr):
|
||||
return ErrRequestTooLargeF("The request is too large. maximum allowed size is %d bytes", maxBytesErr.Limit)
|
||||
return RequestTooLargef("The request is too large. maximum allowed size is %d bytes", maxBytesErr.Limit)
|
||||
|
||||
// gitrpc errors
|
||||
case errors.As(err, &gitrpcError):
|
||||
|
|
|
@ -121,10 +121,16 @@ func BadRequestf(format string, args ...any) *Error {
|
|||
return Newf(http.StatusBadRequest, format, args...)
|
||||
}
|
||||
|
||||
func ErrRequestTooLargeF(format string, args ...any) *Error {
|
||||
// RequestTooLargef returns a new user facing request too large error.
|
||||
func RequestTooLargef(format string, args ...any) *Error {
|
||||
return Newf(http.StatusRequestEntityTooLarge, format, args...)
|
||||
}
|
||||
|
||||
// UnprocessableEntityf returns a new user facing unprocessable entity error.
|
||||
func UnprocessableEntityf(format string, args ...any) *Error {
|
||||
return Newf(http.StatusUnprocessableEntity, format, args...)
|
||||
}
|
||||
|
||||
// BadRequestWithPayload returns a new user facing bad request error with payload.
|
||||
func BadRequestWithPayload(message string, values ...map[string]any) *Error {
|
||||
return NewWithPayload(http.StatusBadRequest, message, values...)
|
||||
|
|
|
@ -500,6 +500,7 @@ func SetupPullReq(r chi.Router, pullreqCtrl *pullreq.Controller) {
|
|||
r.Get("/", handlerpullreq.HandleFileViewList(pullreqCtrl))
|
||||
r.Delete("/*", handlerpullreq.HandleFileViewDelete(pullreqCtrl))
|
||||
})
|
||||
r.Get("/codeowners", handlerpullreq.HandleCodeOwner(pullreqCtrl))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,34 +17,70 @@ package codeowners
|
|||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/app/store"
|
||||
"github.com/harness/gitness/gitrpc"
|
||||
gitness_store "github.com/harness/gitness/store"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
oneMegabyte = 1048576
|
||||
// maxGetContentFileSize specifies the maximum number of bytes a file content response contains.
|
||||
// If a file is any larger, the content is truncated.
|
||||
maxGetContentFileSize = 1 << 20 // 1 MB
|
||||
maxGetContentFileSize = oneMegabyte * 4 // 4 MB
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("file not found")
|
||||
)
|
||||
|
||||
// TooLargeError represents an error if codeowners file is too large.
|
||||
type TooLargeError struct {
|
||||
FileSize int64
|
||||
}
|
||||
|
||||
func IsTooLargeError(err error) bool {
|
||||
return errors.Is(err, &TooLargeError{})
|
||||
}
|
||||
|
||||
func (e *TooLargeError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"The repository's CODEOWNERS file size %.2fMB exceeds the maximum supported size of %dMB",
|
||||
float32(e.FileSize)/oneMegabyte,
|
||||
maxGetContentFileSize/oneMegabyte,
|
||||
)
|
||||
}
|
||||
|
||||
//nolint:errorlint // the purpose of this method is to check whether the target itself if of this type.
|
||||
func (e *TooLargeError) Is(target error) bool {
|
||||
_, ok := target.(*TooLargeError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
FilePath string
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
repoStore store.RepoStore
|
||||
git gitrpc.Interface
|
||||
config Config
|
||||
repoStore store.RepoStore
|
||||
git gitrpc.Interface
|
||||
principalStore store.PrincipalStore
|
||||
config Config
|
||||
}
|
||||
|
||||
type codeOwnerFile struct {
|
||||
Content string
|
||||
SHA string
|
||||
type File struct {
|
||||
Content string
|
||||
SHA string
|
||||
TotalSize int64
|
||||
}
|
||||
|
||||
type CodeOwners struct {
|
||||
|
@ -57,28 +93,52 @@ type Entry struct {
|
|||
Owners []string
|
||||
}
|
||||
|
||||
type Evaluation struct {
|
||||
EvaluationEntries []EvaluationEntry
|
||||
FileSha string
|
||||
}
|
||||
|
||||
type EvaluationEntry struct {
|
||||
Pattern string
|
||||
OwnerEvaluations []OwnerEvaluation
|
||||
}
|
||||
|
||||
type OwnerEvaluation struct {
|
||||
Owner types.PrincipalInfo
|
||||
ReviewDecision enum.PullReqReviewDecision
|
||||
ReviewSHA string
|
||||
}
|
||||
|
||||
func New(
|
||||
repoStore store.RepoStore,
|
||||
git gitrpc.Interface,
|
||||
config Config,
|
||||
) (*Service, error) {
|
||||
principalStore store.PrincipalStore,
|
||||
) *Service {
|
||||
service := &Service{
|
||||
repoStore: repoStore,
|
||||
git: git,
|
||||
config: config,
|
||||
repoStore: repoStore,
|
||||
git: git,
|
||||
config: config,
|
||||
principalStore: principalStore,
|
||||
}
|
||||
return service, nil
|
||||
return service
|
||||
}
|
||||
|
||||
func (s *Service) Get(ctx context.Context,
|
||||
repoID int64) (*CodeOwners, error) {
|
||||
repo, err := s.repoStore.Find(ctx, repoID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to retrieve repo %w", err)
|
||||
func (s *Service) get(
|
||||
ctx context.Context,
|
||||
repo *types.Repository,
|
||||
ref string,
|
||||
) (*CodeOwners, error) {
|
||||
codeOwnerFile, err := s.getCodeOwnerFile(ctx, repo, ref)
|
||||
// no codeowner file
|
||||
if gitrpc.ErrorStatus(err) == gitrpc.StatusPathNotFound {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
codeOwnerFile, err := s.getCodeOwnerFile(ctx, repo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get codeowner file %w", err)
|
||||
return nil, fmt.Errorf("unable to get codeowner file: %w", err)
|
||||
}
|
||||
if codeOwnerFile.TotalSize > maxGetContentFileSize {
|
||||
return nil, &TooLargeError{FileSize: codeOwnerFile.TotalSize}
|
||||
}
|
||||
|
||||
owner, err := s.parseCodeOwner(codeOwnerFile.Content)
|
||||
|
@ -124,17 +184,21 @@ func (s *Service) parseCodeOwner(codeOwnersContent string) ([]Entry, error) {
|
|||
return codeOwners, nil
|
||||
}
|
||||
|
||||
func (s *Service) getCodeOwnerFile(ctx context.Context,
|
||||
func (s *Service) getCodeOwnerFile(
|
||||
ctx context.Context,
|
||||
repo *types.Repository,
|
||||
) (*codeOwnerFile, error) {
|
||||
ref string,
|
||||
) (*File, error) {
|
||||
params := gitrpc.CreateRPCReadParams(repo)
|
||||
if ref == "" {
|
||||
ref = "refs/heads/" + repo.DefaultBranch
|
||||
}
|
||||
node, err := s.git.GetTreeNode(ctx, &gitrpc.GetTreeNodeParams{
|
||||
ReadParams: params,
|
||||
GitREF: "refs/heads/" + repo.DefaultBranch,
|
||||
GitREF: ref,
|
||||
Path: s.config.FilePath,
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: check for path not found and return empty codeowners
|
||||
return nil, fmt.Errorf("unable to retrieve codeowner file %w", err)
|
||||
}
|
||||
|
||||
|
@ -160,8 +224,118 @@ func (s *Service) getCodeOwnerFile(ctx context.Context,
|
|||
return nil, fmt.Errorf("failed to read blob content: %w", err)
|
||||
}
|
||||
|
||||
return &codeOwnerFile{
|
||||
Content: string(content),
|
||||
SHA: output.SHA,
|
||||
return &File{
|
||||
Content: string(content),
|
||||
SHA: output.SHA,
|
||||
TotalSize: output.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetApplicableCodeOwnersForPR(
|
||||
ctx context.Context,
|
||||
repo *types.Repository,
|
||||
pr *types.PullReq,
|
||||
) (*CodeOwners, error) {
|
||||
codeOwners, err := s.get(ctx, repo, pr.TargetBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var filteredEntries []Entry
|
||||
diffFileStats, err := s.git.DiffFileNames(ctx, &gitrpc.DiffParams{
|
||||
ReadParams: gitrpc.CreateRPCReadParams(repo),
|
||||
BaseRef: pr.MergeBaseSHA,
|
||||
HeadRef: pr.SourceSHA,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get diff file stat: %w", err)
|
||||
}
|
||||
|
||||
for _, entry := range codeOwners.Entries {
|
||||
ok, err := contains(diffFileStats.Files, entry.Pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
filteredEntries = append(filteredEntries, entry)
|
||||
}
|
||||
}
|
||||
return &CodeOwners{
|
||||
FileSHA: codeOwners.FileSHA,
|
||||
Entries: filteredEntries,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (s *Service) Evaluate(
|
||||
ctx context.Context,
|
||||
owners *CodeOwners,
|
||||
reviewer []*types.PullReqReviewer,
|
||||
) (*Evaluation, error) {
|
||||
flattenedReviewers := flattenReviewers(reviewer)
|
||||
evaluationEntries := make([]EvaluationEntry, len(owners.Entries))
|
||||
|
||||
for i, entry := range owners.Entries {
|
||||
ownerEvaluations := make([]OwnerEvaluation, len(entry.Owners))
|
||||
for j, owner := range entry.Owners {
|
||||
if pullreqReviewer, ok := flattenedReviewers[owner]; ok {
|
||||
ownerEvaluations[j] = OwnerEvaluation{
|
||||
Owner: pullreqReviewer.Reviewer,
|
||||
ReviewDecision: pullreqReviewer.ReviewDecision,
|
||||
ReviewSHA: pullreqReviewer.SHA,
|
||||
}
|
||||
continue
|
||||
}
|
||||
principal, err := s.principalStore.FindByEmail(ctx, owner)
|
||||
if errors.Is(err, gitness_store.ErrResourceNotFound) {
|
||||
log.Ctx(ctx).Info().Msgf("user %s not found in database hence skipping for code owner: %v",
|
||||
owner, err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return &Evaluation{}, fmt.Errorf("error finding user by email: %w", err)
|
||||
}
|
||||
ownerEvaluations[j] = OwnerEvaluation{
|
||||
Owner: *principal.ToPrincipalInfo(),
|
||||
}
|
||||
}
|
||||
evaluationEntries[i] = EvaluationEntry{
|
||||
Pattern: entry.Pattern,
|
||||
OwnerEvaluations: ownerEvaluations,
|
||||
}
|
||||
}
|
||||
|
||||
return &Evaluation{
|
||||
EvaluationEntries: evaluationEntries,
|
||||
FileSha: owners.FileSHA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func flattenReviewers(reviewers []*types.PullReqReviewer) map[string]*types.PullReqReviewer {
|
||||
r := make(map[string]*types.PullReqReviewer)
|
||||
for _, reviewer := range reviewers {
|
||||
r[reviewer.Reviewer.Email] = reviewer
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// We match a pattern list against a target
|
||||
// doubleStar match allows to match / separated path wisely.
|
||||
// A path foo/bar will match against pattern ** or foo/*
|
||||
// Also, for a directory ending with / we have to return true for all files in that directory,
|
||||
// hence we append ** for it.
|
||||
func contains(patterns []string, target string) (bool, error) {
|
||||
for _, pattern := range patterns {
|
||||
// in case of / ending rule, owner owns the whole directory hence append **
|
||||
if strings.HasSuffix(pattern, "/") {
|
||||
pattern += "**"
|
||||
}
|
||||
match, err := doublestar.PathMatch(pattern, target)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to match pattern due to error: %w", err)
|
||||
}
|
||||
if match {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package codeowners
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -23,14 +24,11 @@ import (
|
|||
)
|
||||
|
||||
func TestService_ParseCodeOwner(t *testing.T) {
|
||||
content1 := `**/contracts/openapi/v1/ mankrit.singh@harness.io ashish.sanodia@harness.io
|
||||
`
|
||||
content2 := `**/contracts/openapi/v1/ mankrit.singh@harness.io ashish.sanodia@harness.io
|
||||
/scripts/api mankrit.singh@harness.io ashish.sanodia@harness.io`
|
||||
content3 := `# codeowner file
|
||||
**/contracts/openapi/v1/ mankrit.singh@harness.io ashish.sanodia@harness.io
|
||||
#
|
||||
/scripts/api mankrit.singh@harness.io ashish.sanodia@harness.io`
|
||||
content1 := "**/contracts/openapi/v1/ mankrit.singh@harness.io ashish.sanodia@harness.io\n"
|
||||
content2 := "**/contracts/openapi/v1/ mankrit.singh@harness.io ashish.sanodia@harness.io\n" +
|
||||
"/scripts/api mankrit.singh@harness.io ashish.sanodia@harness.io"
|
||||
content3 := "# codeowner file \n**/contracts/openapi/v1/ mankrit.singh@harness.io ashish.sanodia@harness.io\n" +
|
||||
"#\n/scripts/api mankrit.singh@harness.io ashish.sanodia@harness.io"
|
||||
type fields struct {
|
||||
repoStore store.RepoStore
|
||||
git gitrpc.Interface
|
||||
|
@ -100,3 +98,209 @@ func TestService_ParseCodeOwner(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_contains(t *testing.T) {
|
||||
pattern1 := [1]string{"*"}
|
||||
pattern2 := [1]string{"**"}
|
||||
pattern3 := [1]string{"abc/xyz"}
|
||||
pattern4 := [2]string{"abc/xyz", "*"}
|
||||
pattern5 := [2]string{"abc/xyz", "**"}
|
||||
pattern6 := [1]string{"doc/frotz"}
|
||||
pattern7 := [1]string{"?ilename"}
|
||||
pattern8 := [1]string{"**/foo"}
|
||||
pattern9 := [1]string{"foo/**"}
|
||||
pattern10 := [1]string{"a/**/b"}
|
||||
pattern11 := [1]string{"foo/*"}
|
||||
pattern12 := [1]string{"*.txt"}
|
||||
pattern13 := [1]string{"/scripts/"}
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
slice []string
|
||||
target string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Test * pattern",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern1[:],
|
||||
target: "random",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test ** pattern",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern2[:],
|
||||
target: "random/xyz",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test ** pattern on fixed path",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern1[:],
|
||||
target: "abhinav/path",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test abc/xyz pattern",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern3[:],
|
||||
target: "abc/xyz",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test abc/xyz pattern negative",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern3[:],
|
||||
target: "abc/xy",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test incorrect pattern negative",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern4[:],
|
||||
target: "random/path",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test * pattern with bigger slice",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern4[:],
|
||||
target: "random",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test file path with **",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern5[:],
|
||||
target: "path/to/file",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test / pattern",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern6[:],
|
||||
target: "doc/frotz",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test ? pattern",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern7[:],
|
||||
target: "filename",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test /** pattern",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern8[:],
|
||||
target: "foo",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test /** pattern with slash",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern8[:],
|
||||
target: "foo/bar",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test **/ with deep nesting",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern8[:],
|
||||
target: "path/to/foo",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test **/ pattern",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern9[:],
|
||||
target: "foo/bar",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test a/**/b pattern",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern10[:],
|
||||
target: "a/x/y/b",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test /* pattern positive",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern11[:],
|
||||
target: "foo/getting-started.md",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test /* pattern negative",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern11[:],
|
||||
target: "foo/build-app/troubleshooting.md",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Test * for files",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern12[:],
|
||||
target: "foo.txt",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Test /a/",
|
||||
args: args{
|
||||
ctx: nil,
|
||||
slice: pattern13[:],
|
||||
target: "/scripts/filename.txt",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got, _ := contains(tt.args.slice, tt.args.target); got != tt.want {
|
||||
t.Errorf("contains() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,11 +29,7 @@ func ProvideCodeOwners(
|
|||
gitRPCClient gitrpc.Interface,
|
||||
repoStore store.RepoStore,
|
||||
config Config,
|
||||
) (*Service, error) {
|
||||
service, err := New(repoStore, gitRPCClient, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return service, nil
|
||||
principalStore store.PrincipalStore,
|
||||
) *Service {
|
||||
return New(repoStore, gitRPCClient, config, principalStore)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/harness/gitness/app/services/codeowners"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
@ -47,7 +48,7 @@ type (
|
|||
Reviewers []*types.PullReqReviewer
|
||||
Method enum.MergeMethod
|
||||
CheckResults []types.CheckResult
|
||||
// TODO: Add code owners
|
||||
CodeOwners *codeowners.Evaluation
|
||||
}
|
||||
|
||||
CanMergeOutput struct {
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/app/services/codeowners"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
|
@ -34,6 +35,11 @@ type Branch struct {
|
|||
Lifecycle DefLifecycle `json:"lifecycle"`
|
||||
}
|
||||
|
||||
type Review struct {
|
||||
ReviewSHA string
|
||||
Decision enum.PullReqReviewDecision
|
||||
}
|
||||
|
||||
var _ Definition = (*Branch)(nil) // ensures that the Branch type implements Definition interface.
|
||||
|
||||
//nolint:gocognit // well aware of this
|
||||
|
@ -69,7 +75,40 @@ func (v *Branch) CanMerge(_ context.Context, in CanMergeInput) (CanMergeOutput,
|
|||
len(approvedBy), v.PullReq.Approvals.RequireMinimumCount)
|
||||
}
|
||||
|
||||
// TODO: implement v.PullReq.Approvals.RequireCodeOwners
|
||||
//nolint:nestif
|
||||
if v.PullReq.Approvals.RequireCodeOwners {
|
||||
for _, entry := range in.CodeOwners.EvaluationEntries {
|
||||
reviewDecision, approvers := getCodeOwnerApprovalStatus(entry.OwnerEvaluations)
|
||||
|
||||
if reviewDecision == enum.PullReqReviewDecisionPending {
|
||||
violations.Addf("pullreq.approvals.require_code_owners",
|
||||
"Code owners approval pending for %s", entry.Pattern)
|
||||
continue
|
||||
}
|
||||
|
||||
if reviewDecision == enum.PullReqReviewDecisionChangeReq {
|
||||
violations.Addf("pullreq.approvals.require_code_owners",
|
||||
"Code owners requested changes for %s", entry.Pattern)
|
||||
continue
|
||||
}
|
||||
// pull req approved. check other settings
|
||||
if !v.PullReq.Approvals.RequireLatestCommit {
|
||||
continue
|
||||
}
|
||||
// check for latest commit approved or not
|
||||
latestSHAApproved := false
|
||||
for _, approver := range approvers {
|
||||
if approver.ReviewSHA == in.PullReq.SourceSHA {
|
||||
latestSHAApproved = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !latestSHAApproved {
|
||||
violations.Addf("pullreq.approvals.require_code_owners",
|
||||
"Code owners approval pending on latest commit for %s", entry.Pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pullreq.comments
|
||||
|
||||
|
@ -273,3 +312,21 @@ func (v DefPullReq) Validate() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCodeOwnerApprovalStatus(
|
||||
ownerStatus []codeowners.OwnerEvaluation,
|
||||
) (enum.PullReqReviewDecision, []codeowners.OwnerEvaluation) {
|
||||
approvers := make([]codeowners.OwnerEvaluation, 0)
|
||||
for _, o := range ownerStatus {
|
||||
if o.ReviewDecision == enum.PullReqReviewDecisionChangeReq {
|
||||
return enum.PullReqReviewDecisionChangeReq, nil
|
||||
}
|
||||
if o.ReviewDecision == enum.PullReqReviewDecisionApproved {
|
||||
approvers = append(approvers, o)
|
||||
}
|
||||
}
|
||||
if len(approvers) > 0 {
|
||||
return enum.PullReqReviewDecisionApproved, approvers
|
||||
}
|
||||
return enum.PullReqReviewDecisionPending, nil
|
||||
}
|
||||
|
|
|
@ -153,10 +153,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
return nil, err
|
||||
}
|
||||
codeownersConfig := server.ProvideCodeOwnerConfig(config)
|
||||
codeownersService, err := codeowners.ProvideCodeOwners(gitrpcInterface, repoStore, codeownersConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codeownersService := codeowners.ProvideCodeOwners(gitrpcInterface, repoStore, codeownersConfig, principalStore)
|
||||
repoController := repo.ProvideController(config, transactor, provider, pathUID, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, protectionManager, gitrpcInterface, repository, codeownersService)
|
||||
executionStore := database.ProvideExecutionStore(db)
|
||||
checkStore := database.ProvideCheckStore(db, principalInfoCache)
|
||||
|
@ -219,7 +216,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, pullReqFileViewStore, membershipStore, checkStore, gitrpcInterface, reporter, mutexManager, migrator, pullreqService, protectionManager, streamer)
|
||||
pullreqController := pullreq2.ProvideController(transactor, provider, authorizer, pullReqStore, pullReqActivityStore, codeCommentView, pullReqReviewStore, pullReqReviewerStore, repoStore, principalStore, pullReqFileViewStore, membershipStore, checkStore, gitrpcInterface, reporter, mutexManager, migrator, pullreqService, protectionManager, streamer, codeownersService)
|
||||
webhookConfig := server.ProvideWebhookConfig(config)
|
||||
webhookStore := database.ProvideWebhookStore(db)
|
||||
webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
|
||||
|
|
|
@ -127,26 +127,26 @@ func (c *Client) DiffShortStat(ctx context.Context, params *DiffParams) (DiffSho
|
|||
}, nil
|
||||
}
|
||||
|
||||
type DiffFileStatOutput struct {
|
||||
type DiffFileNamesOutput struct {
|
||||
Files []string
|
||||
}
|
||||
|
||||
func (c *Client) DiffFileStat(ctx context.Context, params *DiffParams) (DiffFileStatOutput, error) {
|
||||
func (c *Client) DiffFileNames(ctx context.Context, params *DiffParams) (DiffFileNamesOutput, error) {
|
||||
if err := params.Validate(); err != nil {
|
||||
return DiffFileStatOutput{}, err
|
||||
return DiffFileNamesOutput{}, err
|
||||
}
|
||||
fileStat, err := c.diffService.DiffFileStat(ctx, &rpc.DiffRequest{
|
||||
fileNames, err := c.diffService.DiffFileNames(ctx, &rpc.DiffRequest{
|
||||
Base: mapToRPCReadRequest(params.ReadParams),
|
||||
BaseRef: params.BaseRef,
|
||||
HeadRef: params.HeadRef,
|
||||
MergeBase: params.MergeBase,
|
||||
})
|
||||
if err != nil {
|
||||
return DiffFileStatOutput{}, processRPCErrorf(err, "failed to get diff file data between '%s' and '%s'",
|
||||
return DiffFileNamesOutput{}, processRPCErrorf(err, "failed to get diff file data between '%s' and '%s'",
|
||||
params.BaseRef, params.HeadRef)
|
||||
}
|
||||
return DiffFileStatOutput{
|
||||
Files: fileStat.GetFiles(),
|
||||
return DiffFileNamesOutput{
|
||||
Files: fileNames.GetFiles(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ type Interface interface {
|
|||
*/
|
||||
RawDiff(ctx context.Context, in *DiffParams, w io.Writer) error
|
||||
Diff(ctx context.Context, in *DiffParams) (<-chan *FileDiff, <-chan error)
|
||||
DiffFileStat(ctx context.Context, in *DiffParams) (DiffFileStatOutput, error)
|
||||
DiffFileNames(ctx context.Context, in *DiffParams) (DiffFileNamesOutput, error)
|
||||
CommitDiff(ctx context.Context, params *GetCommitParams, w io.Writer) error
|
||||
DiffShortStat(ctx context.Context, params *DiffParams) (DiffShortStatOutput, error)
|
||||
DiffStats(ctx context.Context, params *DiffParams) (DiffStatsOutput, error)
|
||||
|
|
|
@ -188,14 +188,19 @@ func (g Adapter) DiffCut(
|
|||
return diffCutHeader, linesHunk, nil
|
||||
}
|
||||
|
||||
func (g Adapter) DiffFileStat(
|
||||
ctx context.Context,
|
||||
func (g Adapter) DiffFileName(ctx context.Context,
|
||||
repoPath string,
|
||||
baseRef string,
|
||||
headRef string,
|
||||
mergeBase bool,
|
||||
) ([]string, error) {
|
||||
cmd := git.NewCommand(ctx,
|
||||
"diff", "--name-only", headRef, baseRef)
|
||||
args := make([]string, 0, 8)
|
||||
args = append(args, "diff", "--name-only")
|
||||
if mergeBase {
|
||||
args = append(args, "--merge-base")
|
||||
}
|
||||
args = append(args, baseRef, headRef)
|
||||
cmd := git.NewCommand(ctx, args...)
|
||||
stdout, _, runErr := cmd.RunStdBytes(&git.RunOpts{Dir: repoPath})
|
||||
if runErr != nil {
|
||||
return nil, processGiteaErrorf(runErr, "failed to trigger diff command")
|
||||
|
|
|
@ -151,18 +151,18 @@ func (s DiffService) GetDiffHunkHeaders(
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s DiffService) DiffFileStat(
|
||||
func (s DiffService) DiffFileNames(
|
||||
ctx context.Context,
|
||||
r *rpc.DiffRequest,
|
||||
) (*rpc.DiffFileStatResponse, error) {
|
||||
) (*rpc.DiffFileNameResponse, error) {
|
||||
base := r.GetBase()
|
||||
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
|
||||
|
||||
files, err := s.adapter.DiffFileStat(ctx, repoPath, r.BaseRef, r.HeadRef)
|
||||
files, err := s.adapter.DiffFileName(ctx, repoPath, r.BaseRef, r.HeadRef, r.MergeBase)
|
||||
if err != nil {
|
||||
return nil, processGitErrorf(err, "failed to get diff file stat")
|
||||
}
|
||||
return &rpc.DiffFileStatResponse{Files: files}, nil
|
||||
return &rpc.DiffFileNameResponse{Files: files}, nil
|
||||
}
|
||||
|
||||
func (s DiffService) DiffCut(
|
||||
|
|
|
@ -111,9 +111,9 @@ type GitAdapter interface {
|
|||
regExpDef string,
|
||||
maxSize int) ([]types.FileContent, error)
|
||||
|
||||
DiffFileStat(
|
||||
ctx context.Context,
|
||||
DiffFileName(ctx context.Context,
|
||||
repoPath string,
|
||||
baseRef string,
|
||||
headRef string) ([]string, error)
|
||||
headRef string,
|
||||
mergeBase bool) ([]string, error)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ service DiffService {
|
|||
rpc DiffShortStat(DiffRequest) returns (DiffShortStatResponse) {}
|
||||
rpc GetDiffHunkHeaders(GetDiffHunkHeadersRequest) returns (GetDiffHunkHeadersResponse) {}
|
||||
rpc DiffCut(DiffCutRequest) returns (DiffCutResponse) {}
|
||||
rpc DiffFileStat(DiffRequest) returns (DiffFileStatResponse) {}
|
||||
rpc DiffFileNames(DiffRequest) returns (DiffFileNameResponse) {}
|
||||
}
|
||||
|
||||
message DiffRequest {
|
||||
|
@ -138,6 +138,6 @@ message CommitDiffResponse {
|
|||
bytes data = 1;
|
||||
}
|
||||
|
||||
message DiffFileStatResponse {
|
||||
message DiffFileNameResponse {
|
||||
repeated string files = 1;
|
||||
}
|
|
@ -1022,7 +1022,7 @@ func (x *CommitDiffResponse) GetData() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
type DiffFileStatResponse struct {
|
||||
type DiffFileNameResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
@ -1030,8 +1030,8 @@ type DiffFileStatResponse struct {
|
|||
Files []string `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DiffFileStatResponse) Reset() {
|
||||
*x = DiffFileStatResponse{}
|
||||
func (x *DiffFileNameResponse) Reset() {
|
||||
*x = DiffFileNameResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_diff_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
|
@ -1039,13 +1039,13 @@ func (x *DiffFileStatResponse) Reset() {
|
|||
}
|
||||
}
|
||||
|
||||
func (x *DiffFileStatResponse) String() string {
|
||||
func (x *DiffFileNameResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DiffFileStatResponse) ProtoMessage() {}
|
||||
func (*DiffFileNameResponse) ProtoMessage() {}
|
||||
|
||||
func (x *DiffFileStatResponse) ProtoReflect() protoreflect.Message {
|
||||
func (x *DiffFileNameResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_diff_proto_msgTypes[13]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
|
@ -1057,12 +1057,12 @@ func (x *DiffFileStatResponse) ProtoReflect() protoreflect.Message {
|
|||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DiffFileStatResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DiffFileStatResponse) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use DiffFileNameResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DiffFileNameResponse) Descriptor() ([]byte, []int) {
|
||||
return file_diff_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *DiffFileStatResponse) GetFiles() []string {
|
||||
func (x *DiffFileNameResponse) GetFiles() []string {
|
||||
if x != nil {
|
||||
return x.Files
|
||||
}
|
||||
|
@ -1209,9 +1209,9 @@ var file_diff_proto_rawDesc = []byte{
|
|||
0x09, 0x52, 0x03, 0x73, 0x68, 0x61, 0x22, 0x28, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
|
||||
0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
|
||||
0x22, 0x2c, 0x0a, 0x14, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74,
|
||||
0x22, 0x2c, 0x0a, 0x14, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65,
|
||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x32, 0xc7,
|
||||
0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x32, 0xc8,
|
||||
0x03, 0x0a, 0x0b, 0x44, 0x69, 0x66, 0x66, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x35,
|
||||
0x0a, 0x07, 0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x70,
|
||||
|
@ -1236,14 +1236,14 @@ var file_diff_proto_rawDesc = []byte{
|
|||
0x00, 0x12, 0x36, 0x0a, 0x07, 0x44, 0x69, 0x66, 0x66, 0x43, 0x75, 0x74, 0x12, 0x13, 0x2e, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x43, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x14, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x43, 0x75, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x44, 0x69, 0x66,
|
||||
0x66, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e,
|
||||
0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70,
|
||||
0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67,
|
||||
0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x70,
|
||||
0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x44, 0x69, 0x66,
|
||||
0x66, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63,
|
||||
0x2e, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72,
|
||||
0x70, 0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74,
|
||||
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2f,
|
||||
0x67, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72,
|
||||
0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -1275,7 +1275,7 @@ var file_diff_proto_goTypes = []interface{}{
|
|||
(*DiffResponse)(nil), // 11: rpc.DiffResponse
|
||||
(*CommitDiffRequest)(nil), // 12: rpc.CommitDiffRequest
|
||||
(*CommitDiffResponse)(nil), // 13: rpc.CommitDiffResponse
|
||||
(*DiffFileStatResponse)(nil), // 14: rpc.DiffFileStatResponse
|
||||
(*DiffFileNameResponse)(nil), // 14: rpc.DiffFileNameResponse
|
||||
nil, // 15: rpc.DiffFileHeader.ExtensionsEntry
|
||||
(*ReadRequest)(nil), // 16: rpc.ReadRequest
|
||||
}
|
||||
|
@ -1296,14 +1296,14 @@ var file_diff_proto_depIdxs = []int32{
|
|||
1, // 13: rpc.DiffService.DiffShortStat:input_type -> rpc.DiffRequest
|
||||
7, // 14: rpc.DiffService.GetDiffHunkHeaders:input_type -> rpc.GetDiffHunkHeadersRequest
|
||||
9, // 15: rpc.DiffService.DiffCut:input_type -> rpc.DiffCutRequest
|
||||
1, // 16: rpc.DiffService.DiffFileStat:input_type -> rpc.DiffRequest
|
||||
1, // 16: rpc.DiffService.DiffFileNames:input_type -> rpc.DiffRequest
|
||||
2, // 17: rpc.DiffService.RawDiff:output_type -> rpc.RawDiffResponse
|
||||
11, // 18: rpc.DiffService.Diff:output_type -> rpc.DiffResponse
|
||||
13, // 19: rpc.DiffService.CommitDiff:output_type -> rpc.CommitDiffResponse
|
||||
3, // 20: rpc.DiffService.DiffShortStat:output_type -> rpc.DiffShortStatResponse
|
||||
8, // 21: rpc.DiffService.GetDiffHunkHeaders:output_type -> rpc.GetDiffHunkHeadersResponse
|
||||
10, // 22: rpc.DiffService.DiffCut:output_type -> rpc.DiffCutResponse
|
||||
14, // 23: rpc.DiffService.DiffFileStat:output_type -> rpc.DiffFileStatResponse
|
||||
14, // 23: rpc.DiffService.DiffFileNames:output_type -> rpc.DiffFileNameResponse
|
||||
17, // [17:24] is the sub-list for method output_type
|
||||
10, // [10:17] is the sub-list for method input_type
|
||||
10, // [10:10] is the sub-list for extension type_name
|
||||
|
@ -1475,7 +1475,7 @@ func file_diff_proto_init() {
|
|||
}
|
||||
}
|
||||
file_diff_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DiffFileStatResponse); i {
|
||||
switch v := v.(*DiffFileNameResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
|
|
@ -29,7 +29,7 @@ type DiffServiceClient interface {
|
|||
DiffShortStat(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffShortStatResponse, error)
|
||||
GetDiffHunkHeaders(ctx context.Context, in *GetDiffHunkHeadersRequest, opts ...grpc.CallOption) (*GetDiffHunkHeadersResponse, error)
|
||||
DiffCut(ctx context.Context, in *DiffCutRequest, opts ...grpc.CallOption) (*DiffCutResponse, error)
|
||||
DiffFileStat(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffFileStatResponse, error)
|
||||
DiffFileNames(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffFileNameResponse, error)
|
||||
}
|
||||
|
||||
type diffServiceClient struct {
|
||||
|
@ -163,9 +163,9 @@ func (c *diffServiceClient) DiffCut(ctx context.Context, in *DiffCutRequest, opt
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *diffServiceClient) DiffFileStat(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffFileStatResponse, error) {
|
||||
out := new(DiffFileStatResponse)
|
||||
err := c.cc.Invoke(ctx, "/rpc.DiffService/DiffFileStat", in, out, opts...)
|
||||
func (c *diffServiceClient) DiffFileNames(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffFileNameResponse, error) {
|
||||
out := new(DiffFileNameResponse)
|
||||
err := c.cc.Invoke(ctx, "/rpc.DiffService/DiffFileNames", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ type DiffServiceServer interface {
|
|||
DiffShortStat(context.Context, *DiffRequest) (*DiffShortStatResponse, error)
|
||||
GetDiffHunkHeaders(context.Context, *GetDiffHunkHeadersRequest) (*GetDiffHunkHeadersResponse, error)
|
||||
DiffCut(context.Context, *DiffCutRequest) (*DiffCutResponse, error)
|
||||
DiffFileStat(context.Context, *DiffRequest) (*DiffFileStatResponse, error)
|
||||
DiffFileNames(context.Context, *DiffRequest) (*DiffFileNameResponse, error)
|
||||
mustEmbedUnimplementedDiffServiceServer()
|
||||
}
|
||||
|
||||
|
@ -208,8 +208,8 @@ func (UnimplementedDiffServiceServer) GetDiffHunkHeaders(context.Context, *GetDi
|
|||
func (UnimplementedDiffServiceServer) DiffCut(context.Context, *DiffCutRequest) (*DiffCutResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DiffCut not implemented")
|
||||
}
|
||||
func (UnimplementedDiffServiceServer) DiffFileStat(context.Context, *DiffRequest) (*DiffFileStatResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DiffFileStat not implemented")
|
||||
func (UnimplementedDiffServiceServer) DiffFileNames(context.Context, *DiffRequest) (*DiffFileNameResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DiffFileNames not implemented")
|
||||
}
|
||||
func (UnimplementedDiffServiceServer) mustEmbedUnimplementedDiffServiceServer() {}
|
||||
|
||||
|
@ -341,20 +341,20 @@ func _DiffService_DiffCut_Handler(srv interface{}, ctx context.Context, dec func
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DiffService_DiffFileStat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
func _DiffService_DiffFileNames_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DiffRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DiffServiceServer).DiffFileStat(ctx, in)
|
||||
return srv.(DiffServiceServer).DiffFileNames(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpc.DiffService/DiffFileStat",
|
||||
FullMethod: "/rpc.DiffService/DiffFileNames",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DiffServiceServer).DiffFileStat(ctx, req.(*DiffRequest))
|
||||
return srv.(DiffServiceServer).DiffFileNames(ctx, req.(*DiffRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
@ -379,8 +379,8 @@ var DiffService_ServiceDesc = grpc.ServiceDesc{
|
|||
Handler: _DiffService_DiffCut_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DiffFileStat",
|
||||
Handler: _DiffService_DiffFileStat_Handler,
|
||||
MethodName: "DiffFileNames",
|
||||
Handler: _DiffService_DiffFileNames_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
|
|
|
@ -152,6 +152,7 @@ type ServicePackRequest struct {
|
|||
// Depending on the service the matching base type has to be passed
|
||||
//
|
||||
// Types that are assignable to Base:
|
||||
//
|
||||
// *ServicePackRequest_ReadBase
|
||||
// *ServicePackRequest_WriteBase
|
||||
Base isServicePackRequest_Base `protobuf_oneof:"base"`
|
||||
|
|
|
@ -264,6 +264,7 @@ type CommitFilesAction struct {
|
|||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Payload:
|
||||
//
|
||||
// *CommitFilesAction_Header
|
||||
// *CommitFilesAction_Content
|
||||
Payload isCommitFilesAction_Payload `protobuf_oneof:"payload"`
|
||||
|
@ -347,6 +348,7 @@ type CommitFilesRequest struct {
|
|||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Payload:
|
||||
//
|
||||
// *CommitFilesRequest_Header
|
||||
// *CommitFilesRequest_Action
|
||||
Payload isCommitFilesRequest_Payload `protobuf_oneof:"payload"`
|
||||
|
|
|
@ -217,6 +217,7 @@ type CreateRepositoryRequest struct {
|
|||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Data:
|
||||
//
|
||||
// *CreateRepositoryRequest_Header
|
||||
// *CreateRepositoryRequest_File
|
||||
Data isCreateRepositoryRequest_Data `protobuf_oneof:"data"`
|
||||
|
@ -1312,6 +1313,7 @@ type GetBlobResponse struct {
|
|||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Data:
|
||||
//
|
||||
// *GetBlobResponse_Header
|
||||
// *GetBlobResponse_Content
|
||||
Data isGetBlobResponse_Data `protobuf_oneof:"data"`
|
||||
|
|
|
@ -299,6 +299,7 @@ type FileUpload struct {
|
|||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to Data:
|
||||
//
|
||||
// *FileUpload_Header
|
||||
// *FileUpload_Chunk
|
||||
Data isFileUpload_Data `protobuf_oneof:"data"`
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// 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 types
|
||||
|
||||
import "github.com/harness/gitness/types/enum"
|
||||
|
||||
type CodeOwnerEvaluation struct {
|
||||
EvaluationEntries []CodeOwnerEvaluationEntry `json:"evaluation_entries"`
|
||||
FileSha string `json:"file_sha"`
|
||||
}
|
||||
|
||||
type CodeOwnerEvaluationEntry struct {
|
||||
Pattern string `json:"pattern"`
|
||||
OwnerEvaluations []OwnerEvaluation `json:"owner_evaluations"`
|
||||
}
|
||||
|
||||
type OwnerEvaluation struct {
|
||||
Owner PrincipalInfo `json:"owner"`
|
||||
ReviewDecision enum.PullReqReviewDecision `json:"review_decision,omitempty"`
|
||||
ReviewSHA string `json:"review_sha,omitempty"`
|
||||
}
|
|
@ -283,6 +283,6 @@ type Config struct {
|
|||
}
|
||||
|
||||
CodeOwners struct {
|
||||
FilePath string `envconfig:"GITNESS_CODEOWNERS_FILEPATH" default:".gitness/CODEOWNERS"`
|
||||
FilePath string `envconfig:"GITNESS_CODEOWNERS_FILEPATH" default:".harness/CODEOWNERS"`
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue