mirror of
https://github.com/harness/drone.git
synced 2025-05-07 05:29:08 +08:00
feat: [CODE-967]: codeowners service e2e (#695)
This commit is contained in:
parent
3701d4a24f
commit
adbe2f6f97
95
app/api/controller/pullreq/codeowner.go
Normal file
95
app/api/controller/pullreq/codeowner.go
Normal file
@ -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"
|
"github.com/harness/gitness/app/auth/authz"
|
||||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||||
"github.com/harness/gitness/app/services/codecomments"
|
"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/protection"
|
||||||
"github.com/harness/gitness/app/services/pullreq"
|
"github.com/harness/gitness/app/services/pullreq"
|
||||||
"github.com/harness/gitness/app/sse"
|
"github.com/harness/gitness/app/sse"
|
||||||
@ -58,6 +59,7 @@ type Controller struct {
|
|||||||
pullreqService *pullreq.Service
|
pullreqService *pullreq.Service
|
||||||
protectionManager *protection.Manager
|
protectionManager *protection.Manager
|
||||||
sseStreamer sse.Streamer
|
sseStreamer sse.Streamer
|
||||||
|
codeOwners *codeowners.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController(
|
func NewController(
|
||||||
@ -81,6 +83,7 @@ func NewController(
|
|||||||
pullreqService *pullreq.Service,
|
pullreqService *pullreq.Service,
|
||||||
protectionManager *protection.Manager,
|
protectionManager *protection.Manager,
|
||||||
sseStreamer sse.Streamer,
|
sseStreamer sse.Streamer,
|
||||||
|
codeowners *codeowners.Service,
|
||||||
) *Controller {
|
) *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
tx: tx,
|
tx: tx,
|
||||||
@ -103,6 +106,7 @@ func NewController(
|
|||||||
pullreqService: pullreqService,
|
pullreqService: pullreqService,
|
||||||
protectionManager: protectionManager,
|
protectionManager: protectionManager,
|
||||||
sseStreamer: sseStreamer,
|
sseStreamer: sseStreamer,
|
||||||
|
codeOwners: codeowners,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/harness/gitness/app/auth"
|
"github.com/harness/gitness/app/auth"
|
||||||
"github.com/harness/gitness/app/bootstrap"
|
"github.com/harness/gitness/app/bootstrap"
|
||||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
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/app/services/protection"
|
||||||
"github.com/harness/gitness/gitrpc"
|
"github.com/harness/gitness/gitrpc"
|
||||||
gitrpcenum "github.com/harness/gitness/gitrpc/enum"
|
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)
|
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{
|
ruleOut, violations, err := protectionRules.CanMerge(ctx, protection.CanMergeInput{
|
||||||
Actor: &session.Principal,
|
Actor: &session.Principal,
|
||||||
IsSpaceOwner: isSpaceOwner,
|
IsSpaceOwner: isSpaceOwner,
|
||||||
@ -151,6 +165,7 @@ func (c *Controller) Merge(
|
|||||||
Reviewers: reviewers,
|
Reviewers: reviewers,
|
||||||
Method: in.Method,
|
Method: in.Method,
|
||||||
CheckResults: checkResults,
|
CheckResults: checkResults,
|
||||||
|
CodeOwners: codeOwnerWithApproval,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to verify protection rules: %w", err)
|
return nil, nil, fmt.Errorf("failed to verify protection rules: %w", err)
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/harness/gitness/app/auth/authz"
|
"github.com/harness/gitness/app/auth/authz"
|
||||||
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
pullreqevents "github.com/harness/gitness/app/events/pullreq"
|
||||||
"github.com/harness/gitness/app/services/codecomments"
|
"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/protection"
|
||||||
"github.com/harness/gitness/app/services/pullreq"
|
"github.com/harness/gitness/app/services/pullreq"
|
||||||
"github.com/harness/gitness/app/sse"
|
"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,
|
rpcClient gitrpc.Interface, eventReporter *pullreqevents.Reporter,
|
||||||
mtxManager lock.MutexManager, codeCommentMigrator *codecomments.Migrator,
|
mtxManager lock.MutexManager, codeCommentMigrator *codecomments.Migrator,
|
||||||
pullreqService *pullreq.Service, ruleManager *protection.Manager, sseStreamer sse.Streamer,
|
pullreqService *pullreq.Service, ruleManager *protection.Manager, sseStreamer sse.Streamer,
|
||||||
|
codeOwners *codeowners.Service,
|
||||||
) *Controller {
|
) *Controller {
|
||||||
return NewController(tx, urlProvider, authorizer,
|
return NewController(tx, urlProvider, authorizer,
|
||||||
pullReqStore, pullReqActivityStore,
|
pullReqStore, pullReqActivityStore,
|
||||||
@ -55,5 +57,5 @@ func ProvideController(tx dbtx.Transactor, urlProvider url.Provider, authorizer
|
|||||||
checkStore,
|
checkStore,
|
||||||
rpcClient, eventReporter,
|
rpcClient, eventReporter,
|
||||||
mtxManager, codeCommentMigrator,
|
mtxManager, codeCommentMigrator,
|
||||||
pullreqService, ruleManager, sseStreamer)
|
pullreqService, ruleManager, sseStreamer, codeOwners)
|
||||||
}
|
}
|
||||||
|
50
app/api/handler/pullreq/codeowner.go
Normal file
50
app/api/handler/pullreq/codeowner.go
Normal file
@ -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.SetJSONResponse(&fileViewDelete, new(usererror.Error), http.StatusForbidden)
|
||||||
_ = reflector.Spec.AddOperation(http.MethodDelete,
|
_ = reflector.Spec.AddOperation(http.MethodDelete,
|
||||||
"/repos/{repo_ref}/pullreq/{pullreq_number}/file-views/{file_path}", fileViewDelete)
|
"/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):
|
case errors.Is(err, blob.ErrNotFound):
|
||||||
return ErrNotFound
|
return ErrNotFound
|
||||||
case errors.As(err, &maxBytesErr):
|
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
|
// gitrpc errors
|
||||||
case errors.As(err, &gitrpcError):
|
case errors.As(err, &gitrpcError):
|
||||||
|
@ -121,10 +121,16 @@ func BadRequestf(format string, args ...any) *Error {
|
|||||||
return Newf(http.StatusBadRequest, format, args...)
|
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...)
|
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.
|
// BadRequestWithPayload returns a new user facing bad request error with payload.
|
||||||
func BadRequestWithPayload(message string, values ...map[string]any) *Error {
|
func BadRequestWithPayload(message string, values ...map[string]any) *Error {
|
||||||
return NewWithPayload(http.StatusBadRequest, message, values...)
|
return NewWithPayload(http.StatusBadRequest, message, values...)
|
||||||
|
@ -500,6 +500,7 @@ func SetupPullReq(r chi.Router, pullreqCtrl *pullreq.Controller) {
|
|||||||
r.Get("/", handlerpullreq.HandleFileViewList(pullreqCtrl))
|
r.Get("/", handlerpullreq.HandleFileViewList(pullreqCtrl))
|
||||||
r.Delete("/*", handlerpullreq.HandleFileViewDelete(pullreqCtrl))
|
r.Delete("/*", handlerpullreq.HandleFileViewDelete(pullreqCtrl))
|
||||||
})
|
})
|
||||||
|
r.Get("/codeowners", handlerpullreq.HandleCodeOwner(pullreqCtrl))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -17,34 +17,70 @@ package codeowners
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/app/store"
|
"github.com/harness/gitness/app/store"
|
||||||
"github.com/harness/gitness/gitrpc"
|
"github.com/harness/gitness/gitrpc"
|
||||||
|
gitness_store "github.com/harness/gitness/store"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
oneMegabyte = 1048576
|
||||||
// maxGetContentFileSize specifies the maximum number of bytes a file content response contains.
|
// maxGetContentFileSize specifies the maximum number of bytes a file content response contains.
|
||||||
// If a file is any larger, the content is truncated.
|
// 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 {
|
type Config struct {
|
||||||
FilePath string
|
FilePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
repoStore store.RepoStore
|
repoStore store.RepoStore
|
||||||
git gitrpc.Interface
|
git gitrpc.Interface
|
||||||
config Config
|
principalStore store.PrincipalStore
|
||||||
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type codeOwnerFile struct {
|
type File struct {
|
||||||
Content string
|
Content string
|
||||||
SHA string
|
SHA string
|
||||||
|
TotalSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type CodeOwners struct {
|
type CodeOwners struct {
|
||||||
@ -57,28 +93,52 @@ type Entry struct {
|
|||||||
Owners []string
|
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(
|
func New(
|
||||||
repoStore store.RepoStore,
|
repoStore store.RepoStore,
|
||||||
git gitrpc.Interface,
|
git gitrpc.Interface,
|
||||||
config Config,
|
config Config,
|
||||||
) (*Service, error) {
|
principalStore store.PrincipalStore,
|
||||||
|
) *Service {
|
||||||
service := &Service{
|
service := &Service{
|
||||||
repoStore: repoStore,
|
repoStore: repoStore,
|
||||||
git: git,
|
git: git,
|
||||||
config: config,
|
config: config,
|
||||||
|
principalStore: principalStore,
|
||||||
}
|
}
|
||||||
return service, nil
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Get(ctx context.Context,
|
func (s *Service) get(
|
||||||
repoID int64) (*CodeOwners, error) {
|
ctx context.Context,
|
||||||
repo, err := s.repoStore.Find(ctx, repoID)
|
repo *types.Repository,
|
||||||
if err != nil {
|
ref string,
|
||||||
return nil, fmt.Errorf("unable to retrieve repo %w", err)
|
) (*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 {
|
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)
|
owner, err := s.parseCodeOwner(codeOwnerFile.Content)
|
||||||
@ -124,17 +184,21 @@ func (s *Service) parseCodeOwner(codeOwnersContent string) ([]Entry, error) {
|
|||||||
return codeOwners, nil
|
return codeOwners, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) getCodeOwnerFile(ctx context.Context,
|
func (s *Service) getCodeOwnerFile(
|
||||||
|
ctx context.Context,
|
||||||
repo *types.Repository,
|
repo *types.Repository,
|
||||||
) (*codeOwnerFile, error) {
|
ref string,
|
||||||
|
) (*File, error) {
|
||||||
params := gitrpc.CreateRPCReadParams(repo)
|
params := gitrpc.CreateRPCReadParams(repo)
|
||||||
|
if ref == "" {
|
||||||
|
ref = "refs/heads/" + repo.DefaultBranch
|
||||||
|
}
|
||||||
node, err := s.git.GetTreeNode(ctx, &gitrpc.GetTreeNodeParams{
|
node, err := s.git.GetTreeNode(ctx, &gitrpc.GetTreeNodeParams{
|
||||||
ReadParams: params,
|
ReadParams: params,
|
||||||
GitREF: "refs/heads/" + repo.DefaultBranch,
|
GitREF: ref,
|
||||||
Path: s.config.FilePath,
|
Path: s.config.FilePath,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: check for path not found and return empty codeowners
|
|
||||||
return nil, fmt.Errorf("unable to retrieve codeowner file %w", err)
|
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 nil, fmt.Errorf("failed to read blob content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &codeOwnerFile{
|
return &File{
|
||||||
Content: string(content),
|
Content: string(content),
|
||||||
SHA: output.SHA,
|
SHA: output.SHA,
|
||||||
|
TotalSize: output.Size,
|
||||||
}, nil
|
}, 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
|
package codeowners
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -23,14 +24,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestService_ParseCodeOwner(t *testing.T) {
|
func TestService_ParseCodeOwner(t *testing.T) {
|
||||||
content1 := `**/contracts/openapi/v1/ 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" +
|
||||||
content2 := `**/contracts/openapi/v1/ mankrit.singh@harness.io ashish.sanodia@harness.io
|
"/scripts/api mankrit.singh@harness.io ashish.sanodia@harness.io"
|
||||||
/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" +
|
||||||
content3 := `# codeowner file
|
"#\n/scripts/api mankrit.singh@harness.io ashish.sanodia@harness.io"
|
||||||
**/contracts/openapi/v1/ mankrit.singh@harness.io ashish.sanodia@harness.io
|
|
||||||
#
|
|
||||||
/scripts/api mankrit.singh@harness.io ashish.sanodia@harness.io`
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
repoStore store.RepoStore
|
repoStore store.RepoStore
|
||||||
git gitrpc.Interface
|
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,
|
gitRPCClient gitrpc.Interface,
|
||||||
repoStore store.RepoStore,
|
repoStore store.RepoStore,
|
||||||
config Config,
|
config Config,
|
||||||
) (*Service, error) {
|
principalStore store.PrincipalStore,
|
||||||
service, err := New(repoStore, gitRPCClient, config)
|
) *Service {
|
||||||
if err != nil {
|
return New(repoStore, gitRPCClient, config, principalStore)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return service, nil
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/app/services/codeowners"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
)
|
)
|
||||||
@ -47,7 +48,7 @@ type (
|
|||||||
Reviewers []*types.PullReqReviewer
|
Reviewers []*types.PullReqReviewer
|
||||||
Method enum.MergeMethod
|
Method enum.MergeMethod
|
||||||
CheckResults []types.CheckResult
|
CheckResults []types.CheckResult
|
||||||
// TODO: Add code owners
|
CodeOwners *codeowners.Evaluation
|
||||||
}
|
}
|
||||||
|
|
||||||
CanMergeOutput struct {
|
CanMergeOutput struct {
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/app/services/codeowners"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
|
|
||||||
@ -34,6 +35,11 @@ type Branch struct {
|
|||||||
Lifecycle DefLifecycle `json:"lifecycle"`
|
Lifecycle DefLifecycle `json:"lifecycle"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Review struct {
|
||||||
|
ReviewSHA string
|
||||||
|
Decision enum.PullReqReviewDecision
|
||||||
|
}
|
||||||
|
|
||||||
var _ Definition = (*Branch)(nil) // ensures that the Branch type implements Definition interface.
|
var _ Definition = (*Branch)(nil) // ensures that the Branch type implements Definition interface.
|
||||||
|
|
||||||
//nolint:gocognit // well aware of this
|
//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)
|
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
|
// pullreq.comments
|
||||||
|
|
||||||
@ -273,3 +312,21 @@ func (v DefPullReq) Validate() error {
|
|||||||
|
|
||||||
return nil
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
codeownersConfig := server.ProvideCodeOwnerConfig(config)
|
codeownersConfig := server.ProvideCodeOwnerConfig(config)
|
||||||
codeownersService, err := codeowners.ProvideCodeOwners(gitrpcInterface, repoStore, codeownersConfig)
|
codeownersService := codeowners.ProvideCodeOwners(gitrpcInterface, repoStore, codeownersConfig, principalStore)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
repoController := repo.ProvideController(config, transactor, provider, pathUID, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, protectionManager, gitrpcInterface, repository, codeownersService)
|
repoController := repo.ProvideController(config, transactor, provider, pathUID, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, protectionManager, gitrpcInterface, repository, codeownersService)
|
||||||
executionStore := database.ProvideExecutionStore(db)
|
executionStore := database.ProvideExecutionStore(db)
|
||||||
checkStore := database.ProvideCheckStore(db, principalInfoCache)
|
checkStore := database.ProvideCheckStore(db, principalInfoCache)
|
||||||
@ -219,7 +216,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
webhookConfig := server.ProvideWebhookConfig(config)
|
||||||
webhookStore := database.ProvideWebhookStore(db)
|
webhookStore := database.ProvideWebhookStore(db)
|
||||||
webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
|
webhookExecutionStore := database.ProvideWebhookExecutionStore(db)
|
||||||
|
@ -127,26 +127,26 @@ func (c *Client) DiffShortStat(ctx context.Context, params *DiffParams) (DiffSho
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiffFileStatOutput struct {
|
type DiffFileNamesOutput struct {
|
||||||
Files []string
|
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 {
|
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),
|
Base: mapToRPCReadRequest(params.ReadParams),
|
||||||
BaseRef: params.BaseRef,
|
BaseRef: params.BaseRef,
|
||||||
HeadRef: params.HeadRef,
|
HeadRef: params.HeadRef,
|
||||||
MergeBase: params.MergeBase,
|
MergeBase: params.MergeBase,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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)
|
params.BaseRef, params.HeadRef)
|
||||||
}
|
}
|
||||||
return DiffFileStatOutput{
|
return DiffFileNamesOutput{
|
||||||
Files: fileStat.GetFiles(),
|
Files: fileNames.GetFiles(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ type Interface interface {
|
|||||||
*/
|
*/
|
||||||
RawDiff(ctx context.Context, in *DiffParams, w io.Writer) error
|
RawDiff(ctx context.Context, in *DiffParams, w io.Writer) error
|
||||||
Diff(ctx context.Context, in *DiffParams) (<-chan *FileDiff, <-chan 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
|
CommitDiff(ctx context.Context, params *GetCommitParams, w io.Writer) error
|
||||||
DiffShortStat(ctx context.Context, params *DiffParams) (DiffShortStatOutput, error)
|
DiffShortStat(ctx context.Context, params *DiffParams) (DiffShortStatOutput, error)
|
||||||
DiffStats(ctx context.Context, params *DiffParams) (DiffStatsOutput, error)
|
DiffStats(ctx context.Context, params *DiffParams) (DiffStatsOutput, error)
|
||||||
|
@ -188,14 +188,19 @@ func (g Adapter) DiffCut(
|
|||||||
return diffCutHeader, linesHunk, nil
|
return diffCutHeader, linesHunk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Adapter) DiffFileStat(
|
func (g Adapter) DiffFileName(ctx context.Context,
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
repoPath string,
|
||||||
baseRef string,
|
baseRef string,
|
||||||
headRef string,
|
headRef string,
|
||||||
|
mergeBase bool,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
cmd := git.NewCommand(ctx,
|
args := make([]string, 0, 8)
|
||||||
"diff", "--name-only", headRef, baseRef)
|
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})
|
stdout, _, runErr := cmd.RunStdBytes(&git.RunOpts{Dir: repoPath})
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
return nil, processGiteaErrorf(runErr, "failed to trigger diff command")
|
return nil, processGiteaErrorf(runErr, "failed to trigger diff command")
|
||||||
|
@ -151,18 +151,18 @@ func (s DiffService) GetDiffHunkHeaders(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s DiffService) DiffFileStat(
|
func (s DiffService) DiffFileNames(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
r *rpc.DiffRequest,
|
r *rpc.DiffRequest,
|
||||||
) (*rpc.DiffFileStatResponse, error) {
|
) (*rpc.DiffFileNameResponse, error) {
|
||||||
base := r.GetBase()
|
base := r.GetBase()
|
||||||
repoPath := getFullPathForRepo(s.reposRoot, base.GetRepoUid())
|
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 {
|
if err != nil {
|
||||||
return nil, processGitErrorf(err, "failed to get diff file stat")
|
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(
|
func (s DiffService) DiffCut(
|
||||||
|
@ -111,9 +111,9 @@ type GitAdapter interface {
|
|||||||
regExpDef string,
|
regExpDef string,
|
||||||
maxSize int) ([]types.FileContent, error)
|
maxSize int) ([]types.FileContent, error)
|
||||||
|
|
||||||
DiffFileStat(
|
DiffFileName(ctx context.Context,
|
||||||
ctx context.Context,
|
|
||||||
repoPath string,
|
repoPath string,
|
||||||
baseRef 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 DiffShortStat(DiffRequest) returns (DiffShortStatResponse) {}
|
||||||
rpc GetDiffHunkHeaders(GetDiffHunkHeadersRequest) returns (GetDiffHunkHeadersResponse) {}
|
rpc GetDiffHunkHeaders(GetDiffHunkHeadersRequest) returns (GetDiffHunkHeadersResponse) {}
|
||||||
rpc DiffCut(DiffCutRequest) returns (DiffCutResponse) {}
|
rpc DiffCut(DiffCutRequest) returns (DiffCutResponse) {}
|
||||||
rpc DiffFileStat(DiffRequest) returns (DiffFileStatResponse) {}
|
rpc DiffFileNames(DiffRequest) returns (DiffFileNameResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
message DiffRequest {
|
message DiffRequest {
|
||||||
@ -138,6 +138,6 @@ message CommitDiffResponse {
|
|||||||
bytes data = 1;
|
bytes data = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DiffFileStatResponse {
|
message DiffFileNameResponse {
|
||||||
repeated string files = 1;
|
repeated string files = 1;
|
||||||
}
|
}
|
@ -1022,7 +1022,7 @@ func (x *CommitDiffResponse) GetData() []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiffFileStatResponse struct {
|
type DiffFileNameResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
@ -1030,8 +1030,8 @@ type DiffFileStatResponse struct {
|
|||||||
Files []string `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"`
|
Files []string `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *DiffFileStatResponse) Reset() {
|
func (x *DiffFileNameResponse) Reset() {
|
||||||
*x = DiffFileStatResponse{}
|
*x = DiffFileNameResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_diff_proto_msgTypes[13]
|
mi := &file_diff_proto_msgTypes[13]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
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)
|
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]
|
mi := &file_diff_proto_msgTypes[13]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@ -1057,12 +1057,12 @@ func (x *DiffFileStatResponse) ProtoReflect() protoreflect.Message {
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use DiffFileStatResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use DiffFileNameResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*DiffFileStatResponse) Descriptor() ([]byte, []int) {
|
func (*DiffFileNameResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_diff_proto_rawDescGZIP(), []int{13}
|
return file_diff_proto_rawDescGZIP(), []int{13}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *DiffFileStatResponse) GetFiles() []string {
|
func (x *DiffFileNameResponse) GetFiles() []string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Files
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x44, 0x69, 0x66,
|
||||||
0x66, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63, 0x2e,
|
0x66, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x10, 0x2e, 0x72, 0x70, 0x63,
|
||||||
0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70,
|
0x2e, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72,
|
||||||
0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x52, 0x65,
|
0x70, 0x63, 0x2e, 0x44, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74,
|
||||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67,
|
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x2f,
|
||||||
0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x70,
|
0x67, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72,
|
||||||
0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -1275,7 +1275,7 @@ var file_diff_proto_goTypes = []interface{}{
|
|||||||
(*DiffResponse)(nil), // 11: rpc.DiffResponse
|
(*DiffResponse)(nil), // 11: rpc.DiffResponse
|
||||||
(*CommitDiffRequest)(nil), // 12: rpc.CommitDiffRequest
|
(*CommitDiffRequest)(nil), // 12: rpc.CommitDiffRequest
|
||||||
(*CommitDiffResponse)(nil), // 13: rpc.CommitDiffResponse
|
(*CommitDiffResponse)(nil), // 13: rpc.CommitDiffResponse
|
||||||
(*DiffFileStatResponse)(nil), // 14: rpc.DiffFileStatResponse
|
(*DiffFileNameResponse)(nil), // 14: rpc.DiffFileNameResponse
|
||||||
nil, // 15: rpc.DiffFileHeader.ExtensionsEntry
|
nil, // 15: rpc.DiffFileHeader.ExtensionsEntry
|
||||||
(*ReadRequest)(nil), // 16: rpc.ReadRequest
|
(*ReadRequest)(nil), // 16: rpc.ReadRequest
|
||||||
}
|
}
|
||||||
@ -1296,14 +1296,14 @@ var file_diff_proto_depIdxs = []int32{
|
|||||||
1, // 13: rpc.DiffService.DiffShortStat:input_type -> rpc.DiffRequest
|
1, // 13: rpc.DiffService.DiffShortStat:input_type -> rpc.DiffRequest
|
||||||
7, // 14: rpc.DiffService.GetDiffHunkHeaders:input_type -> rpc.GetDiffHunkHeadersRequest
|
7, // 14: rpc.DiffService.GetDiffHunkHeaders:input_type -> rpc.GetDiffHunkHeadersRequest
|
||||||
9, // 15: rpc.DiffService.DiffCut:input_type -> rpc.DiffCutRequest
|
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
|
2, // 17: rpc.DiffService.RawDiff:output_type -> rpc.RawDiffResponse
|
||||||
11, // 18: rpc.DiffService.Diff:output_type -> rpc.DiffResponse
|
11, // 18: rpc.DiffService.Diff:output_type -> rpc.DiffResponse
|
||||||
13, // 19: rpc.DiffService.CommitDiff:output_type -> rpc.CommitDiffResponse
|
13, // 19: rpc.DiffService.CommitDiff:output_type -> rpc.CommitDiffResponse
|
||||||
3, // 20: rpc.DiffService.DiffShortStat:output_type -> rpc.DiffShortStatResponse
|
3, // 20: rpc.DiffService.DiffShortStat:output_type -> rpc.DiffShortStatResponse
|
||||||
8, // 21: rpc.DiffService.GetDiffHunkHeaders:output_type -> rpc.GetDiffHunkHeadersResponse
|
8, // 21: rpc.DiffService.GetDiffHunkHeaders:output_type -> rpc.GetDiffHunkHeadersResponse
|
||||||
10, // 22: rpc.DiffService.DiffCut:output_type -> rpc.DiffCutResponse
|
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
|
17, // [17:24] is the sub-list for method output_type
|
||||||
10, // [10:17] is the sub-list for method input_type
|
10, // [10:17] is the sub-list for method input_type
|
||||||
10, // [10:10] is the sub-list for extension type_name
|
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{} {
|
file_diff_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*DiffFileStatResponse); i {
|
switch v := v.(*DiffFileNameResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -29,7 +29,7 @@ type DiffServiceClient interface {
|
|||||||
DiffShortStat(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffShortStatResponse, error)
|
DiffShortStat(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffShortStatResponse, error)
|
||||||
GetDiffHunkHeaders(ctx context.Context, in *GetDiffHunkHeadersRequest, opts ...grpc.CallOption) (*GetDiffHunkHeadersResponse, error)
|
GetDiffHunkHeaders(ctx context.Context, in *GetDiffHunkHeadersRequest, opts ...grpc.CallOption) (*GetDiffHunkHeadersResponse, error)
|
||||||
DiffCut(ctx context.Context, in *DiffCutRequest, opts ...grpc.CallOption) (*DiffCutResponse, 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 {
|
type diffServiceClient struct {
|
||||||
@ -163,9 +163,9 @@ func (c *diffServiceClient) DiffCut(ctx context.Context, in *DiffCutRequest, opt
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *diffServiceClient) DiffFileStat(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffFileStatResponse, error) {
|
func (c *diffServiceClient) DiffFileNames(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (*DiffFileNameResponse, error) {
|
||||||
out := new(DiffFileStatResponse)
|
out := new(DiffFileNameResponse)
|
||||||
err := c.cc.Invoke(ctx, "/rpc.DiffService/DiffFileStat", in, out, opts...)
|
err := c.cc.Invoke(ctx, "/rpc.DiffService/DiffFileNames", in, out, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -182,7 +182,7 @@ type DiffServiceServer interface {
|
|||||||
DiffShortStat(context.Context, *DiffRequest) (*DiffShortStatResponse, error)
|
DiffShortStat(context.Context, *DiffRequest) (*DiffShortStatResponse, error)
|
||||||
GetDiffHunkHeaders(context.Context, *GetDiffHunkHeadersRequest) (*GetDiffHunkHeadersResponse, error)
|
GetDiffHunkHeaders(context.Context, *GetDiffHunkHeadersRequest) (*GetDiffHunkHeadersResponse, error)
|
||||||
DiffCut(context.Context, *DiffCutRequest) (*DiffCutResponse, error)
|
DiffCut(context.Context, *DiffCutRequest) (*DiffCutResponse, error)
|
||||||
DiffFileStat(context.Context, *DiffRequest) (*DiffFileStatResponse, error)
|
DiffFileNames(context.Context, *DiffRequest) (*DiffFileNameResponse, error)
|
||||||
mustEmbedUnimplementedDiffServiceServer()
|
mustEmbedUnimplementedDiffServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,8 +208,8 @@ func (UnimplementedDiffServiceServer) GetDiffHunkHeaders(context.Context, *GetDi
|
|||||||
func (UnimplementedDiffServiceServer) DiffCut(context.Context, *DiffCutRequest) (*DiffCutResponse, error) {
|
func (UnimplementedDiffServiceServer) DiffCut(context.Context, *DiffCutRequest) (*DiffCutResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DiffCut not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method DiffCut not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedDiffServiceServer) DiffFileStat(context.Context, *DiffRequest) (*DiffFileStatResponse, error) {
|
func (UnimplementedDiffServiceServer) DiffFileNames(context.Context, *DiffRequest) (*DiffFileNameResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DiffFileStat not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method DiffFileNames not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedDiffServiceServer) mustEmbedUnimplementedDiffServiceServer() {}
|
func (UnimplementedDiffServiceServer) mustEmbedUnimplementedDiffServiceServer() {}
|
||||||
|
|
||||||
@ -341,20 +341,20 @@ func _DiffService_DiffCut_Handler(srv interface{}, ctx context.Context, dec func
|
|||||||
return interceptor(ctx, in, info, handler)
|
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)
|
in := new(DiffRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if interceptor == nil {
|
if interceptor == nil {
|
||||||
return srv.(DiffServiceServer).DiffFileStat(ctx, in)
|
return srv.(DiffServiceServer).DiffFileNames(ctx, in)
|
||||||
}
|
}
|
||||||
info := &grpc.UnaryServerInfo{
|
info := &grpc.UnaryServerInfo{
|
||||||
Server: srv,
|
Server: srv,
|
||||||
FullMethod: "/rpc.DiffService/DiffFileStat",
|
FullMethod: "/rpc.DiffService/DiffFileNames",
|
||||||
}
|
}
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
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)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
@ -379,8 +379,8 @@ var DiffService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _DiffService_DiffCut_Handler,
|
Handler: _DiffService_DiffCut_Handler,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MethodName: "DiffFileStat",
|
MethodName: "DiffFileNames",
|
||||||
Handler: _DiffService_DiffFileStat_Handler,
|
Handler: _DiffService_DiffFileNames_Handler,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
|
@ -152,6 +152,7 @@ type ServicePackRequest struct {
|
|||||||
// Depending on the service the matching base type has to be passed
|
// Depending on the service the matching base type has to be passed
|
||||||
//
|
//
|
||||||
// Types that are assignable to Base:
|
// Types that are assignable to Base:
|
||||||
|
//
|
||||||
// *ServicePackRequest_ReadBase
|
// *ServicePackRequest_ReadBase
|
||||||
// *ServicePackRequest_WriteBase
|
// *ServicePackRequest_WriteBase
|
||||||
Base isServicePackRequest_Base `protobuf_oneof:"base"`
|
Base isServicePackRequest_Base `protobuf_oneof:"base"`
|
||||||
|
@ -264,6 +264,7 @@ type CommitFilesAction struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// Types that are assignable to Payload:
|
// Types that are assignable to Payload:
|
||||||
|
//
|
||||||
// *CommitFilesAction_Header
|
// *CommitFilesAction_Header
|
||||||
// *CommitFilesAction_Content
|
// *CommitFilesAction_Content
|
||||||
Payload isCommitFilesAction_Payload `protobuf_oneof:"payload"`
|
Payload isCommitFilesAction_Payload `protobuf_oneof:"payload"`
|
||||||
@ -347,6 +348,7 @@ type CommitFilesRequest struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// Types that are assignable to Payload:
|
// Types that are assignable to Payload:
|
||||||
|
//
|
||||||
// *CommitFilesRequest_Header
|
// *CommitFilesRequest_Header
|
||||||
// *CommitFilesRequest_Action
|
// *CommitFilesRequest_Action
|
||||||
Payload isCommitFilesRequest_Payload `protobuf_oneof:"payload"`
|
Payload isCommitFilesRequest_Payload `protobuf_oneof:"payload"`
|
||||||
|
@ -217,6 +217,7 @@ type CreateRepositoryRequest struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// Types that are assignable to Data:
|
// Types that are assignable to Data:
|
||||||
|
//
|
||||||
// *CreateRepositoryRequest_Header
|
// *CreateRepositoryRequest_Header
|
||||||
// *CreateRepositoryRequest_File
|
// *CreateRepositoryRequest_File
|
||||||
Data isCreateRepositoryRequest_Data `protobuf_oneof:"data"`
|
Data isCreateRepositoryRequest_Data `protobuf_oneof:"data"`
|
||||||
@ -1312,6 +1313,7 @@ type GetBlobResponse struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// Types that are assignable to Data:
|
// Types that are assignable to Data:
|
||||||
|
//
|
||||||
// *GetBlobResponse_Header
|
// *GetBlobResponse_Header
|
||||||
// *GetBlobResponse_Content
|
// *GetBlobResponse_Content
|
||||||
Data isGetBlobResponse_Data `protobuf_oneof:"data"`
|
Data isGetBlobResponse_Data `protobuf_oneof:"data"`
|
||||||
|
@ -299,6 +299,7 @@ type FileUpload struct {
|
|||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
// Types that are assignable to Data:
|
// Types that are assignable to Data:
|
||||||
|
//
|
||||||
// *FileUpload_Header
|
// *FileUpload_Header
|
||||||
// *FileUpload_Chunk
|
// *FileUpload_Chunk
|
||||||
Data isFileUpload_Data `protobuf_oneof:"data"`
|
Data isFileUpload_Data `protobuf_oneof:"data"`
|
||||||
|
33
types/codeowners.go
Normal file
33
types/codeowners.go
Normal file
@ -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 {
|
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
Block a user