feat: [CODE-967]: codeowners service e2e (#695)

pull/3423/head
Abhinav Singh 2023-10-26 16:50:59 +00:00 committed by Harness
parent 3701d4a24f
commit adbe2f6f97
29 changed files with 769 additions and 109 deletions

View 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
}

View File

@ -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,
}
}

View File

@ -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)

View File

@ -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)
}

View 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)
}
}

View File

@ -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)
}

View File

@ -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):

View File

@ -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...)

View File

@ -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))
})
})
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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")

View File

@ -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(

View File

@ -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)
}

View File

@ -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;
}

View File

@ -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:

View File

@ -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{

View File

@ -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"`

View File

@ -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"`

View File

@ -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"`

View File

@ -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"`

33
types/codeowners.go Normal file
View 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"`
}

View File

@ -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"`
}
}