feat: [PIPE-22290]: add additional info to list branches API (#2831)

* change type of branch.SHA from string to sha.SHA
* remove code duplication, changed DB query for PRs
* add additional info to list branches API
pull/3576/head
Marko Gaćeša 2024-10-24 13:19:35 +00:00 committed by Harness
parent 3668c43a2a
commit 5eb79b3805
20 changed files with 581 additions and 75 deletions

View File

@ -107,14 +107,14 @@ func (c *Controller) DeleteBranch(ctx context.Context,
if err != nil {
return types.DeleteBranchOutput{}, nil, err
}
if pr.SourceSHA != branch.SHA {
if pr.SourceSHA != branch.SHA.String() {
return types.DeleteBranchOutput{}, nil, errors.Conflict("source branch SHA does not match pull request source SHA")
}
err = c.git.DeleteBranch(ctx, &git.DeleteBranchParams{
WriteParams: writeParams,
BranchName: branchName,
SHA: branch.SHA,
SHA: branch.SHA.String(),
})
if err != nil {
return types.DeleteBranchOutput{}, nil, err
@ -126,7 +126,7 @@ func (c *Controller) DeleteBranch(ctx context.Context,
}
_, err := c.activityStore.CreateWithPayload(ctx, pr, session.Principal.ID,
&types.PullRequestActivityPayloadBranchDelete{SHA: branch.SHA}, nil)
&types.PullRequestActivityPayloadBranchDelete{SHA: branch.SHA.String()}, nil)
return err
}()
if err != nil {

View File

@ -81,6 +81,8 @@ type Controller struct {
executionStore store.ExecutionStore
principalStore store.PrincipalStore
ruleStore store.RuleStore
checkStore store.CheckStore
pullReqStore store.PullReqStore
settings *settings.Service
principalInfoCache store.PrincipalInfoCache
userGroupStore store.UserGroupStore
@ -113,6 +115,8 @@ func NewController(
executionStore store.ExecutionStore,
principalStore store.PrincipalStore,
ruleStore store.RuleStore,
checkStore store.CheckStore,
pullReqStore store.PullReqStore,
settings *settings.Service,
principalInfoCache store.PrincipalInfoCache,
protectionManager *protection.Manager,
@ -144,6 +148,8 @@ func NewController(
executionStore: executionStore,
principalStore: principalStore,
ruleStore: ruleStore,
checkStore: checkStore,
pullReqStore: pullReqStore,
settings: settings,
principalInfoCache: principalInfoCache,
protectionManager: protectionManager,

View File

@ -30,7 +30,8 @@ func (c *Controller) GetBranch(ctx context.Context,
session *auth.Session,
repoRef string,
branchName string,
) (*types.Branch, error) {
options types.BranchMetadataOptions,
) (*types.BranchExtended, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
@ -44,10 +45,22 @@ func (c *Controller) GetBranch(ctx context.Context,
return nil, fmt.Errorf("failed to get branch: %w", err)
}
metadata, err := c.collectBranchMetadata(ctx, repo, []git.Branch{rpcOut.Branch}, options)
if err != nil {
return nil, fmt.Errorf("fail to collect branch metadata: %w", err)
}
branch, err := controller.MapBranch(rpcOut.Branch)
if err != nil {
return nil, fmt.Errorf("failed to map branch: %w", err)
}
return &branch, nil
branchExtended := &types.BranchExtended{
Branch: branch,
IsDefault: branchName == repo.DefaultBranch,
}
metadata.apply(0, branchExtended)
return branchExtended, nil
}

View File

@ -21,6 +21,7 @@ import (
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/git"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
@ -40,20 +41,12 @@ type CommitDivergenceRequest struct {
To string `json:"to"`
}
// CommitDivergence contains the information of the count of converging commits between two refs.
type CommitDivergence struct {
// Ahead is the count of commits the 'From' ref is ahead of the 'To' ref.
Ahead int32 `json:"ahead"`
// Behind is the count of commits the 'From' ref is behind the 'To' ref.
Behind int32 `json:"behind"`
}
// GetCommitDivergences returns the commit divergences between reference pairs.
func (c *Controller) GetCommitDivergences(ctx context.Context,
session *auth.Session,
repoRef string,
in *GetCommitDivergencesInput,
) ([]CommitDivergence, error) {
) ([]types.CommitDivergence, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
@ -61,7 +54,7 @@ func (c *Controller) GetCommitDivergences(ctx context.Context,
// if no requests were provided return an empty list
if in == nil || len(in.Requests) == 0 {
return []CommitDivergence{}, nil
return []types.CommitDivergence{}, nil
}
// if num of requests > page max return error
@ -91,10 +84,9 @@ func (c *Controller) GetCommitDivergences(ctx context.Context,
}
// map to output type
divergences := make([]CommitDivergence, len(rpcOutput.Divergences))
divergences := make([]types.CommitDivergence, len(rpcOutput.Divergences))
for i := range rpcOutput.Divergences {
divergences[i].Ahead = rpcOutput.Divergences[i].Ahead
divergences[i].Behind = rpcOutput.Divergences[i].Behind
divergences[i] = types.CommitDivergence(rpcOutput.Divergences[i])
}
return divergences, nil

View File

@ -20,18 +20,21 @@ import (
"github.com/harness/gitness/app/api/controller"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/git"
"github.com/harness/gitness/git/sha"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/gotidy/ptr"
)
// ListBranches lists the branches of a repo.
func (c *Controller) ListBranches(ctx context.Context,
session *auth.Session,
repoRef string,
includeCommit bool,
filter *types.BranchFilter,
) ([]types.Branch, error) {
) ([]types.BranchExtended, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
@ -39,7 +42,7 @@ func (c *Controller) ListBranches(ctx context.Context,
rpcOut, err := c.git.ListBranches(ctx, &git.ListBranchesParams{
ReadParams: git.CreateReadParams(repo),
IncludeCommit: includeCommit,
IncludeCommit: filter.IncludeCommit,
Query: filter.Query,
Sort: mapToRPCBranchSortOption(filter.Sort),
Order: mapToRPCSortOrder(filter.Order),
@ -47,18 +50,149 @@ func (c *Controller) ListBranches(ctx context.Context,
PageSize: int32(filter.Size),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("fail to get the list of branches from git: %w", err)
}
branches := make([]types.Branch, len(rpcOut.Branches))
for i := range rpcOut.Branches {
branches[i], err = controller.MapBranch(rpcOut.Branches[i])
branches := rpcOut.Branches
metadata, err := c.collectBranchMetadata(ctx, repo, branches, filter.BranchMetadataOptions)
if err != nil {
return nil, fmt.Errorf("fail to collect branch metadata: %w", err)
}
response := make([]types.BranchExtended, len(branches))
for i := range branches {
response[i].Branch, err = controller.MapBranch(branches[i])
if err != nil {
return nil, fmt.Errorf("failed to map branch: %w", err)
}
response[i].IsDefault = repo.DefaultBranch == branches[i].Name
metadata.apply(i, &response[i])
}
return branches, nil
return response, nil
}
// collectBranchMetadata collects the metadata for the provided list of branches.
// The metadata includes check, rules, pull requests, and branch divergences.
// Each of these would be returned only if the corresponding option is true.
func (c *Controller) collectBranchMetadata(
ctx context.Context,
repo *types.Repository,
branches []git.Branch,
options types.BranchMetadataOptions,
) (branchMetadataOutput, error) {
var (
checkSummary map[sha.SHA]types.CheckCountSummary
branchRuleMap map[string][]types.RuleInfo
pullReqMap map[string][]*types.PullReq
divergences *git.GetCommitDivergencesOutput
err error
)
if options.IncludeChecks {
commitSHAs := make([]string, len(branches))
for i := range branches {
commitSHAs[i] = branches[i].SHA.String()
}
checkSummary, err = c.checkStore.ResultSummary(ctx, repo.ID, commitSHAs)
if err != nil {
return branchMetadataOutput{}, fmt.Errorf("fail to fetch check summary for commits: %w", err)
}
}
if options.IncludeRules {
rules, err := c.protectionManager.ForRepository(ctx, repo.ID)
if err != nil {
return branchMetadataOutput{}, fmt.Errorf("failed to fetch protection rules for the repository: %w", err)
}
branchRuleMap = make(map[string][]types.RuleInfo)
for i := range branches {
branchName := branches[i].Name
branchRuleInfos, err := protection.GetRuleInfos(
rules,
repo.DefaultBranch,
branchName,
protection.RuleInfoFilterStatusActive,
protection.RuleInfoFilterTypeBranch)
if err != nil {
return branchMetadataOutput{}, fmt.Errorf("failed get branch rule infos: %w", err)
}
branchRuleMap[branchName] = branchRuleInfos
}
}
if options.IncludePullReqs {
branchNames := make([]string, len(branches))
for i := range branches {
branchNames[i] = branches[i].Name
}
pullReqMap, err = c.pullReqStore.ListOpenByBranchName(ctx, repo.ID, branchNames)
if err != nil {
return branchMetadataOutput{}, fmt.Errorf("fail to fetch pull requests per branch: %w", err)
}
}
if options.MaxDivergence > 0 {
readParams := git.CreateReadParams(repo)
divergenceRequests := make([]git.CommitDivergenceRequest, len(branches))
for i := range branches {
divergenceRequests[i].From = branches[i].Name
divergenceRequests[i].To = repo.DefaultBranch
}
divergences, err = c.git.GetCommitDivergences(ctx, &git.GetCommitDivergencesParams{
ReadParams: readParams,
MaxCount: int32(options.MaxDivergence),
Requests: divergenceRequests,
})
if err != nil {
return branchMetadataOutput{}, fmt.Errorf("fail to fetch commit divergences: %w", err)
}
}
return branchMetadataOutput{
checkSummary: checkSummary,
branchRuleMap: branchRuleMap,
pullReqMap: pullReqMap,
divergences: divergences,
}, nil
}
type branchMetadataOutput struct {
checkSummary map[sha.SHA]types.CheckCountSummary
branchRuleMap map[string][]types.RuleInfo
pullReqMap map[string][]*types.PullReq
divergences *git.GetCommitDivergencesOutput
}
func (metadata branchMetadataOutput) apply(
idx int,
branch *types.BranchExtended,
) {
if metadata.checkSummary != nil {
branch.CheckSummary = ptr.Of(metadata.checkSummary[branch.SHA])
}
if metadata.branchRuleMap != nil {
branch.Rules = metadata.branchRuleMap[branch.Name]
}
if metadata.pullReqMap != nil {
branch.PullRequests = metadata.pullReqMap[branch.Name]
}
if metadata.divergences != nil {
branch.CommitDivergence = ptr.Of(types.CommitDivergence(metadata.divergences.Divergences[idx]))
}
}
func mapToRPCBranchSortOption(o enum.BranchSortOption) git.BranchSortOption {

View File

@ -56,6 +56,8 @@ func ProvideController(
principalStore store.PrincipalStore,
executionStore store.ExecutionStore,
ruleStore store.RuleStore,
checkStore store.CheckStore,
pullReqStore store.PullReqStore,
settings *settings.Service,
principalInfoCache store.PrincipalInfoCache,
protectionManager *protection.Manager,
@ -79,7 +81,8 @@ func ProvideController(
return NewController(config, tx, urlProvider,
authorizer,
repoStore, spaceStore, pipelineStore, executionStore,
principalStore, ruleStore, settings, principalInfoCache, protectionManager, rpcClient, importer,
principalStore, ruleStore, checkStore, pullReqStore, settings,
principalInfoCache, protectionManager, rpcClient, importer,
codeOwners, reporeporter, indexer, limiter, locker, auditService, mtxManager, identifierCheck,
repoChecks, publicAccess, labelSvc, instrumentation, userGroupStore, userGroupService)
}

View File

@ -91,7 +91,7 @@ func MapBranch(b git.Branch) (types.Branch, error) {
}
return types.Branch{
Name: b.Name,
SHA: b.SHA.String(),
SHA: b.SHA,
Commit: commit,
}, nil
}

View File

@ -22,9 +22,7 @@ import (
"github.com/harness/gitness/app/api/request"
)
/*
* Gets a given branch.
*/
// HandleGetBranch returns a given branch.
func HandleGetBranch(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -34,13 +32,20 @@ func HandleGetBranch(repoCtrl *repo.Controller) http.HandlerFunc {
render.TranslatedUserError(ctx, w, err)
return
}
branchName, err := request.GetRemainderFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
branch, err := repoCtrl.GetBranch(ctx, session, repoRef, branchName)
options, err := request.ParseBranchMetadataOptions(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
branch, err := repoCtrl.GetBranch(ctx, session, repoRef, branchName, options)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -22,9 +22,7 @@ import (
"github.com/harness/gitness/app/api/request"
)
/*
* Writes json-encoded branch information to the http response body.
*/
// HandleListBranches writes json-encoded branch list to the http response body.
func HandleListBranches(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@ -35,15 +33,13 @@ func HandleListBranches(repoCtrl *repo.Controller) http.HandlerFunc {
return
}
includeCommit, err := request.GetIncludeCommitFromQueryOrDefault(r, false)
filter, err := request.ParseBranchFilter(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
filter := request.ParseBranchFilter(r)
branches, err := repoCtrl.ListBranches(ctx, session, repoRef, includeCommit, filter)
branches, err := repoCtrl.ListBranches(ctx, session, repoRef, filter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -312,6 +312,71 @@ var queryParameterIncludeCommit = openapi3.ParameterOrRef{
},
}
var queryParameterIncludeChecks = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamIncludeChecks,
In: openapi3.ParameterInQuery,
Description: ptr.String(
"If true, the summary of check for the branch commit SHA would be included in the response."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeBoolean),
Default: ptrptr(false),
},
},
},
}
var queryParameterIncludeRules = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamIncludeRules,
In: openapi3.ParameterInQuery,
Description: ptr.String(
"If true, a list of rules that apply to this branch would be included in the response."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeBoolean),
Default: ptrptr(false),
},
},
},
}
var queryParameterIncludePullReqs = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamIncludePullReqs,
In: openapi3.ParameterInQuery,
Description: ptr.String(
"If true, a list of pull requests from the branch would be included in the response."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeBoolean),
Default: ptrptr(false),
},
},
},
}
var queryParameterMaxDivergence = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamMaxDivergence,
In: openapi3.ParameterInQuery,
Description: ptr.String(
"If greater than zero, branch divergence from the default branch will be included in the response. " +
"The divergence would be calculated up the this many commits."),
Required: ptr.Bool(false),
Schema: &openapi3.SchemaOrRef{
Schema: &openapi3.Schema{
Type: ptrSchemaType(openapi3.SchemaTypeInteger),
Default: ptrptr(0),
},
},
},
}
var queryParameterIncludeDirectories = openapi3.ParameterOrRef{
Parameter: &openapi3.Parameter{
Name: request.QueryParamIncludeDirectories,
@ -888,7 +953,7 @@ func repoOperations(reflector *openapi3.Reflector) {
opCalulateCommitDivergence.WithTags("repository")
opCalulateCommitDivergence.WithMapOfAnything(map[string]interface{}{"operationId": "calculateCommitDivergence"})
_ = reflector.SetRequest(&opCalulateCommitDivergence, new(calculateCommitDivergenceRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opCalulateCommitDivergence, []repo.CommitDivergence{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opCalulateCommitDivergence, []types.CommitDivergence{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opCalulateCommitDivergence, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opCalulateCommitDivergence, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opCalulateCommitDivergence, new(usererror.Error), http.StatusForbidden)
@ -911,8 +976,12 @@ func repoOperations(reflector *openapi3.Reflector) {
opGetBranch := openapi3.Operation{}
opGetBranch.WithTags("repository")
opGetBranch.WithMapOfAnything(map[string]interface{}{"operationId": "getBranch"})
opGetBranch.WithParameters(
queryParameterIncludeChecks, queryParameterIncludeRules, queryParameterIncludePullReqs,
queryParameterMaxDivergence,
)
_ = reflector.SetRequest(&opGetBranch, new(getBranchRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opGetBranch, new(types.Branch), http.StatusOK)
_ = reflector.SetJSONResponse(&opGetBranch, new(types.BranchExtended), http.StatusOK)
_ = reflector.SetJSONResponse(&opGetBranch, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opGetBranch, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opGetBranch, new(usererror.Error), http.StatusForbidden)
@ -935,11 +1004,15 @@ func repoOperations(reflector *openapi3.Reflector) {
opListBranches := openapi3.Operation{}
opListBranches.WithTags("repository")
opListBranches.WithMapOfAnything(map[string]interface{}{"operationId": "listBranches"})
opListBranches.WithParameters(queryParameterIncludeCommit,
opListBranches.WithParameters(
queryParameterQueryBranches, queryParameterOrder, queryParameterSortBranch,
QueryParameterPage, QueryParameterLimit)
QueryParameterPage, QueryParameterLimit,
queryParameterIncludeCommit,
queryParameterIncludeChecks, queryParameterIncludeRules, queryParameterIncludePullReqs,
queryParameterMaxDivergence,
)
_ = reflector.SetRequest(&opListBranches, new(listBranchesRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opListBranches, []types.Branch{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opListBranches, []types.BranchExtended{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opListBranches, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opListBranches, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opListBranches, new(usererror.Error), http.StatusForbidden)

View File

@ -44,6 +44,11 @@ const (
QueryParamInternal = "internal"
QueryParamService = "service"
QueryParamCommitSHA = "commit_sha"
QueryParamIncludeChecks = "include_checks"
QueryParamIncludeRules = "include_rules"
QueryParamIncludePullReqs = "include_pullreqs"
QueryParamMaxDivergence = "max_divergence"
)
func GetGitRefFromQueryOrDefault(r *http.Request, deflt string) string {
@ -54,6 +59,22 @@ func GetIncludeCommitFromQueryOrDefault(r *http.Request, deflt bool) (bool, erro
return QueryParamAsBoolOrDefault(r, QueryParamIncludeCommit, deflt)
}
func GetIncludeChecksFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
return QueryParamAsBoolOrDefault(r, QueryParamIncludeChecks, deflt)
}
func GetIncludeRulesFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
return QueryParamAsBoolOrDefault(r, QueryParamIncludeRules, deflt)
}
func GetIncludePullReqsFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
return QueryParamAsBoolOrDefault(r, QueryParamIncludePullReqs, deflt)
}
func GetMaxDivergenceFromQueryOrDefault(r *http.Request, deflt int64) (int64, error) {
return QueryParamAsPositiveInt64OrDefault(r, QueryParamMaxDivergence, deflt)
}
func GetIncludeDirectoriesFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
return QueryParamAsBoolOrDefault(r, QueryParamIncludeDirectories, deflt)
}
@ -69,15 +90,56 @@ func ParseSortBranch(r *http.Request) enum.BranchSortOption {
)
}
// ParseBranchFilter extracts the branch filter from the url.
func ParseBranchFilter(r *http.Request) *types.BranchFilter {
return &types.BranchFilter{
Query: ParseQuery(r),
Sort: ParseSortBranch(r),
Order: ParseOrder(r),
Page: ParsePage(r),
Size: ParseLimit(r),
func ParseBranchMetadataOptions(r *http.Request) (types.BranchMetadataOptions, error) {
includeChecks, err := GetIncludeChecksFromQueryOrDefault(r, false)
if err != nil {
return types.BranchMetadataOptions{}, err
}
includeRules, err := GetIncludeRulesFromQueryOrDefault(r, false)
if err != nil {
return types.BranchMetadataOptions{}, err
}
includePullReqs, err := GetIncludePullReqsFromQueryOrDefault(r, false)
if err != nil {
return types.BranchMetadataOptions{}, err
}
maxDivergence, err := GetMaxDivergenceFromQueryOrDefault(r, 0)
if err != nil {
return types.BranchMetadataOptions{}, err
}
return types.BranchMetadataOptions{
IncludeChecks: includeChecks,
IncludeRules: includeRules,
IncludePullReqs: includePullReqs,
MaxDivergence: int(maxDivergence),
}, nil
}
// ParseBranchFilter extracts the branch filter from the url.
func ParseBranchFilter(r *http.Request) (*types.BranchFilter, error) {
includeCommit, err := GetIncludeCommitFromQueryOrDefault(r, false)
if err != nil {
return nil, err
}
metadataOptions, err := ParseBranchMetadataOptions(r)
if err != nil {
return nil, err
}
return &types.BranchFilter{
Query: ParseQuery(r),
Sort: ParseSortBranch(r),
Order: ParseOrder(r),
Page: ParsePage(r),
Size: ParseLimit(r),
IncludeCommit: includeCommit,
BranchMetadataOptions: metadataOptions,
}, nil
}
// ParseSortTag extracts the tag sort parameter from the url.

View File

@ -0,0 +1,68 @@
// 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 protection
import (
"fmt"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
var RuleInfoFilterTypeBranch = func(r *types.RuleInfoInternal) (bool, error) {
return r.Type == TypeBranch, nil
}
var RuleInfoFilterStatusActive = func(r *types.RuleInfoInternal) (bool, error) {
return r.State == enum.RuleStateActive, nil
}
func GetRuleInfos(
protection Protection,
defaultBranch string,
branchName string,
filterFns ...func(*types.RuleInfoInternal) (bool, error),
) (ruleInfos []types.RuleInfo, err error) {
v, ok := protection.(ruleSet)
if !ok {
return ruleInfos, nil
}
err = v.forEachRuleMatchBranch(
defaultBranch,
branchName,
func(r *types.RuleInfoInternal, _ Protection) error {
for _, filterFn := range filterFns {
allow, err := filterFn(r)
if err != nil {
return fmt.Errorf("rule info filter function error: %w", err)
}
if !allow {
return nil
}
}
ruleInfos = append(ruleInfos, r.RuleInfo)
return nil
},
)
if err != nil {
return nil, fmt.Errorf("failed to process each rule in ruleSet: %w", err)
}
return ruleInfos, nil
}

View File

@ -62,7 +62,7 @@ func (s ruleSet) MergeVerify(
return nil
})
if err != nil {
return out, nil, fmt.Errorf("failed to merge verify: %w", err)
return out, nil, fmt.Errorf("failed to process each rule in ruleSet: %w", err)
}
return out, violations, nil
@ -95,7 +95,7 @@ func (s ruleSet) RequiredChecks(
return nil
})
if err != nil {
return RequiredChecksOutput{}, err
return RequiredChecksOutput{}, fmt.Errorf("failed to process each rule in ruleSet: %w", err)
}
return RequiredChecksOutput{
@ -122,7 +122,7 @@ func (s ruleSet) RefChangeVerify(ctx context.Context, in RefChangeVerifyInput) (
return nil
})
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to process each rule in ruleSet: %w", err)
}
return violations, nil
@ -143,7 +143,7 @@ func (s ruleSet) UserIDs() ([]int64, error) {
return nil
})
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to process each rule in ruleSet: %w", err)
}
result := make([]int64, 0, len(mapIDs))
@ -169,7 +169,7 @@ func (s ruleSet) UserGroupIDs() ([]int64, error) {
return nil
})
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to process each rule in ruleSet: %w", err)
}
result := make([]int64, 0, len(mapIDs))

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"time"
"github.com/harness/gitness/git/sha"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
@ -405,6 +406,9 @@ type (
// Stream returns streams pull requests from repositories.
Stream(ctx context.Context, opts *types.PullReqFilter) (<-chan *types.PullReq, <-chan error)
// ListOpenByBranchName returns open pull requests for each branch.
ListOpenByBranchName(ctx context.Context, repoID int64, branchNames []string) (map[string][]*types.PullReq, error)
}
PullReqActivityStore interface {
@ -633,6 +637,13 @@ type (
// ListResults returns a list of status check results for a specific commit in a repo.
ListResults(ctx context.Context, repoID int64, commitSHA string) ([]types.CheckResult, error)
// ResultSummary returns a list of status check result summaries for the provided list of commits in a repo.
ResultSummary(
ctx context.Context,
repoID int64,
commitSHAs []string,
) (map[sha.SHA]types.CheckCountSummary, error)
}
GitspaceConfigStore interface {

View File

@ -21,6 +21,7 @@ import (
"strings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/git/sha"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
@ -28,7 +29,6 @@ import (
"github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
var _ store.CheckStore = (*CheckStore)(nil)
@ -194,7 +194,7 @@ func (s *CheckStore) Count(ctx context.Context,
sql, args, err := stmt.ToSql()
if err != nil {
return 0, errors.Wrap(err, "Failed to convert query to sql")
return 0, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
@ -229,7 +229,7 @@ func (s *CheckStore) List(ctx context.Context,
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
dst := make([]*check, 0)
@ -265,7 +265,7 @@ func (s *CheckStore) ListRecent(ctx context.Context,
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert list recent status checks query to sql")
return nil, fmt.Errorf("failed to convert list recent status checks query to sql: %w", err)
}
dst := make([]string, 0)
@ -293,7 +293,7 @@ func (s *CheckStore) ListResults(ctx context.Context,
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
result := make([]types.CheckResult, 0)
@ -307,6 +307,77 @@ func (s *CheckStore) ListResults(ctx context.Context,
return result, nil
}
// ResultSummary returns a list of status check result summaries for the provided list of commits in a repo.
func (s *CheckStore) ResultSummary(ctx context.Context,
repoID int64,
commitSHAs []string,
) (map[sha.SHA]types.CheckCountSummary, error) {
const selectColumns = `
check_commit_sha,
COUNT(check_status = 'pending') as "count_pending",
COUNT(check_status = 'running') as "count_running",
COUNT(check_status = 'success') as "count_success",
COUNT(check_status = 'failure') as "count_failure",
COUNT(check_status = 'error') as "count_error"`
stmt := database.Builder.
Select(selectColumns).
From("checks").
Where("check_repo_id = ?", repoID).
Where(squirrel.Eq{"check_commit_sha": commitSHAs}).
GroupBy("check_commit_sha")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
rows, err := db.QueryxContext(ctx, sql, args...)
if err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to execute status check summary query")
}
defer func() {
_ = rows.Close()
}()
result := make(map[sha.SHA]types.CheckCountSummary)
for rows.Next() {
var commitSHAStr string
var countPending int
var countRunning int
var countSuccess int
var countFailure int
var countError int
err := rows.Scan(&commitSHAStr, &countPending, &countRunning, &countSuccess, &countFailure, &countError)
if err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to scan values of status check summary query")
}
commitSHA, err := sha.New(commitSHAStr)
if err != nil {
return nil, fmt.Errorf("invalid commit SHA read from DB: %s", commitSHAStr)
}
result[commitSHA] = types.CheckCountSummary{
Pending: countPending,
Running: countRunning,
Success: countSuccess,
Failure: countFailure,
Error: countError,
}
}
if err := rows.Err(); err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to read status chek summary")
}
return result, nil
}
func (*CheckStore) applyOpts(stmt squirrel.SelectBuilder, query string) squirrel.SelectBuilder {
if query != "" {
stmt = stmt.Where("LOWER(check_uid) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(query)))

View File

@ -21,6 +21,7 @@ import (
"time"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/errors"
gitness_store "github.com/harness/gitness/store"
"github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
@ -30,7 +31,6 @@ import (
"github.com/Masterminds/squirrel"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
@ -457,7 +457,7 @@ func (s *PullReqStore) Count(ctx context.Context, opts *types.PullReqFilter) (in
sql, args, err := stmt.ToSql()
if err != nil {
return 0, errors.Wrap(err, "Failed to convert query to sql")
return 0, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
@ -486,7 +486,7 @@ func (s *PullReqStore) List(ctx context.Context, opts *types.PullReqFilter) ([]*
sql, args, err := stmt.ToSql()
if err != nil {
return nil, errors.Wrap(err, "Failed to convert query to sql")
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
dst := make([]*pullReq, 0)
@ -520,7 +520,7 @@ func (s *PullReqStore) Stream(ctx context.Context, opts *types.PullReqFilter) (<
sql, args, err := stmt.ToSql()
if err != nil {
chErr <- errors.Wrap(err, "Failed to convert query to sql")
chErr <- fmt.Errorf("failed to convert query to sql: %w", err)
return
}
@ -553,6 +553,42 @@ func (s *PullReqStore) Stream(ctx context.Context, opts *types.PullReqFilter) (<
return chPRs, chErr
}
func (s *PullReqStore) ListOpenByBranchName(
ctx context.Context,
repoID int64,
branchNames []string,
) (map[string][]*types.PullReq, error) {
columns := pullReqColumnsNoDescription
stmt := database.Builder.Select(columns)
stmt = stmt.From("pullreqs")
stmt = stmt.Where("pullreq_source_repo_id = ?", repoID)
stmt = stmt.Where("pullreq_state = ?", enum.PullReqStateOpen)
stmt = stmt.Where(squirrel.Eq{"pullreq_source_branch": branchNames})
stmt = stmt.OrderBy("pullreq_updated desc")
sql, args, err := stmt.ToSql()
if err != nil {
return nil, fmt.Errorf("failed to convert query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, s.db)
dst := make([]*pullReq, 0)
err = db.SelectContext(ctx, &dst, sql, args...)
if err != nil {
return nil, database.ProcessSQLErrorf(ctx, err, "Failed to fetch list of PRs by branch")
}
prMap := make(map[string][]*types.PullReq)
for _, prDB := range dst {
pr := s.mapPullReq(ctx, prDB)
prMap[prDB.SourceBranch] = append(prMap[prDB.SourceBranch], pr)
}
return prMap, nil
}
func (s *PullReqStore) listQuery(opts *types.PullReqFilter) squirrel.SelectBuilder {
var stmt squirrel.SelectBuilder

View File

@ -174,6 +174,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
pipelineStore := database.ProvidePipelineStore(db)
executionStore := database.ProvideExecutionStore(db)
ruleStore := database.ProvideRuleStore(db, principalInfoCache)
checkStore := database.ProvideCheckStore(db, principalInfoCache)
pullReqStore := database.ProvidePullReqStore(db, principalInfoCache)
settingsStore := database.ProvideSettingsStore(db)
settingsService := settings.ProvideService(settingsStore)
protectionManager, err := protection.ProvideManager(ruleStore)
@ -249,9 +251,8 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
instrumentService := instrument.ProvideService()
userGroupStore := database.ProvideUserGroupStore(db)
searchService := usergroup.ProvideSearchService()
repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, executionStore, ruleStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck, publicaccessService, labelService, instrumentService, userGroupStore, searchService)
repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, executionStore, ruleStore, checkStore, pullReqStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck, publicaccessService, labelService, instrumentService, userGroupStore, searchService)
reposettingsController := reposettings.ProvideController(authorizer, repoStore, settingsService, auditService)
checkStore := database.ProvideCheckStore(db, principalInfoCache)
stageStore := database.ProvideStageStore(db)
schedulerScheduler, err := scheduler.ProvideScheduler(stageStore, mutexManager)
if err != nil {
@ -274,7 +275,6 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
connectorStore := database.ProvideConnectorStore(db, secretStore)
repoGitInfoView := database.ProvideRepoGitInfoView(db)
repoGitInfoCache := cache.ProvideRepoGitInfoCache(repoGitInfoView)
pullReqStore := database.ProvidePullReqStore(db, principalInfoCache)
listService := pullreq.ProvideListService(transactor, gitInterface, authorizer, spaceStore, repoStore, repoGitInfoCache, pullReqStore, labelService)
exporterRepository, err := exporter.ProvideSpaceExporter(provider, gitInterface, repoStore, jobScheduler, executor, encrypter, streamer)
if err != nil {

View File

@ -14,12 +14,23 @@
package types
import "github.com/harness/gitness/git/sha"
type Branch struct {
Name string `json:"name"`
SHA string `json:"sha"`
SHA sha.SHA `json:"sha"`
Commit *Commit `json:"commit,omitempty"`
}
type BranchExtended struct {
Branch
IsDefault bool `json:"is_default"`
CheckSummary *CheckCountSummary `json:"check_summary,omitempty"`
Rules []RuleInfo `json:"rules,omitempty"`
PullRequests []*PullReq `json:"pull_requests,omitempty"`
CommitDivergence *CommitDivergence `json:"commit_divergence,omitempty"`
}
type CreateBranchOutput struct {
Branch
DryRunRulesOutput
@ -28,3 +39,11 @@ type CreateBranchOutput struct {
type DeleteBranchOutput struct {
DryRunRulesOutput
}
// CommitDivergence contains the information of the count of converging commits between two refs.
type CommitDivergence struct {
// Ahead is the count of commits the 'From' ref is ahead of the 'To' ref.
Ahead int32 `json:"ahead"`
// Behind is the count of commits the 'From' ref is behind the 'To' ref.
Behind int32 `json:"behind"`
}

View File

@ -109,3 +109,11 @@ type PullReqCheck struct {
Bypassable bool `json:"bypassable"`
Check Check `json:"check"`
}
type CheckCountSummary struct {
Pending int `json:"pending"`
Running int `json:"running"`
Success int `json:"success"`
Failure int `json:"failure"`
Error int `json:"error"`
}

View File

@ -40,13 +40,22 @@ type CommitFilter struct {
IncludeStats bool `json:"include_stats"`
}
type BranchMetadataOptions struct {
IncludeChecks bool `json:"include_checks"`
IncludeRules bool `json:"include_rules"`
IncludePullReqs bool `json:"include_pullreqs"`
MaxDivergence int `json:"max_divergence"`
}
// BranchFilter stores branch query parameters.
type BranchFilter struct {
Query string `json:"query"`
Sort enum.BranchSortOption `json:"sort"`
Order enum.Order `json:"order"`
Page int `json:"page"`
Size int `json:"size"`
Query string `json:"query"`
Sort enum.BranchSortOption `json:"sort"`
Order enum.Order `json:"order"`
Page int `json:"page"`
Size int `json:"size"`
IncludeCommit bool `json:"include_commit"`
BranchMetadataOptions
}
// TagFilter stores commit tag query parameters.