Add repo summary endpoint (#2037)

ui/diff-word-wrap
Darko Draskovic 2024-05-23 18:45:28 +00:00 committed by Harness
parent c365ef246a
commit adf041d747
11 changed files with 278 additions and 1 deletions

View File

@ -0,0 +1,53 @@
// 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 repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/git"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// Summary returns commit, branch, tag and pull req count for a repo.
func (c *Controller) Summary(
ctx context.Context,
session *auth.Session,
repoRef string,
) (*types.RepositorySummary, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
summary, err := c.git.Summary(ctx, &git.ReadParams{RepoUID: repo.GitUID})
if err != nil {
return nil, fmt.Errorf("failed to get repo summary: %w", err)
}
return &types.RepositorySummary{
DefaultBranchCommitCount: summary.CommitCount,
BranchCount: summary.BranchCount,
TagCount: summary.TagCount,
PullReqSummary: types.RepositoryPullReqSummary{
OpenCount: repo.NumOpenPulls,
ClosedCount: repo.NumClosedPulls,
MergedCount: repo.NumMergedPulls,
},
}, nil
}

View File

@ -0,0 +1,44 @@
// 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 repo
import (
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleSummary writes json-encoded repository summary information to the http response body.
func HandleSummary(repoCtrl *repo.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(ctx, w, err)
return
}
summary, err := repoCtrl.Summary(ctx, session, repoRef)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, summary)
}
}

View File

@ -1154,4 +1154,17 @@ func repoOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opArchive, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opArchive, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/archive/{git_ref}.{format}", opArchive)
opSummary := openapi3.Operation{}
opSummary.WithTags("repository")
opSummary.WithMapOfAnything(
map[string]interface{}{"operationId": "summary"})
_ = reflector.SetRequest(&opSummary, new(repoRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opSummary, new(types.RepositorySummary), http.StatusOK)
_ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opSummary, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/summary", opSummary)
}

View File

@ -287,6 +287,8 @@ func setupRepos(r chi.Router,
r.Patch("/general", handlerreposettings.HandleGeneralUpdate(repoSettingsCtrl))
})
r.Get("/summary", handlerrepo.HandleSummary(repoCtrl))
r.Post("/move", handlerrepo.HandleMove(repoCtrl))
r.Get("/service-accounts", handlerrepo.HandleListServiceAccounts(repoCtrl))

View File

@ -15,9 +15,11 @@
package api
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/harness/gitness/git/command"
@ -104,3 +106,42 @@ func (g *Git) IsBranchExist(ctx context.Context, repoPath, name string) (bool, e
}
return true, nil
}
func (g *Git) GetBranchCount(
ctx context.Context,
repoPath string,
) (int, error) {
if repoPath == "" {
return 0, ErrRepositoryPathEmpty
}
pipeOut, pipeIn := io.Pipe()
defer pipeOut.Close()
cmd := command.New("branch",
command.WithFlag("--list"),
command.WithFlag("--format=%(refname:short)"),
)
var err error
go func() {
defer pipeIn.Close()
err = cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(pipeIn))
}()
if err != nil {
return 0, processGitErrorf(err, "failed to trigger branch command")
}
return countLines(pipeOut), nil
}
func countLines(pipe io.Reader) int {
scanner := bufio.NewScanner(pipe)
count := 0
for scanner.Scan() {
count++
}
return count
}

View File

@ -398,3 +398,28 @@ l:
}
return tag, nil
}
func (g *Git) GetTagCount(
ctx context.Context,
repoPath string,
) (int, error) {
if repoPath == "" {
return 0, ErrRepositoryPathEmpty
}
pipeOut, pipeIn := io.Pipe()
defer pipeOut.Close()
cmd := command.New("tag")
var err error
go func() {
defer pipeIn.Close()
err = cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(pipeIn))
}()
if err != nil {
return 0, processGitErrorf(err, "failed to trigger branch command")
}
return countLines(pipeOut), nil
}

View File

@ -67,6 +67,7 @@ var descriptions = map[string]builder{
// git-blame(1) does not support disambiguating options from paths from revisions.
flags: NoRefUpdates | NoEndOfOptions,
},
"branch": {},
"bundle": {
flags: NoRefUpdates,
},

View File

@ -102,4 +102,9 @@ type Interface interface {
*/
ScanSecrets(ctx context.Context, param *ScanSecretsParams) (*ScanSecretsOutput, error)
Archive(ctx context.Context, params ArchiveParams, w io.Writer) error
/*
* Repo Summary service
*/
Summary(ctx context.Context, params *ReadParams) (*SummaryOutput, error)
}

View File

@ -89,7 +89,11 @@ func CommitCount(
repoPath string,
start, end string,
) (int, error) {
cmd := command.New("rev-list", command.WithFlag("--count"), command.WithArg(start+".."+end))
arg := command.WithArg(end)
if len(start) > 0 {
arg = command.WithArg(start + ".." + end)
}
cmd := command.New("rev-list", command.WithFlag("--count"), arg)
stdout := bytes.NewBuffer(nil)

76
git/summary.go Normal file
View File

@ -0,0 +1,76 @@
// 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 git
import (
"context"
"fmt"
"strings"
"github.com/harness/gitness/git/merge"
"golang.org/x/sync/errgroup"
)
type SummaryOutput struct {
CommitCount int
BranchCount int
TagCount int
}
func (s *Service) Summary(
ctx context.Context,
params *ReadParams,
) (*SummaryOutput, error) {
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
defaultBranch, err := s.git.GetDefaultBranch(ctx, repoPath)
if err != nil {
return nil, err
}
defaultBranch = strings.TrimSpace(defaultBranch)
g, ctx := errgroup.WithContext(ctx)
var commitCount, branchCount, tagCount int
g.Go(func() error {
var err error
commitCount, err = merge.CommitCount(ctx, repoPath, "", defaultBranch)
return err
})
g.Go(func() error {
var err error
branchCount, err = s.git.GetBranchCount(ctx, repoPath)
return err
})
g.Go(func() error {
var err error
tagCount, err = s.git.GetTagCount(ctx, repoPath)
return err
})
if err := g.Wait(); err != nil {
return nil, fmt.Errorf("failed to get repo summary: %w", err)
}
return &SummaryOutput{
CommitCount: commitCount,
BranchCount: branchCount,
TagCount: tagCount,
}, nil
}

View File

@ -98,3 +98,16 @@ type RepositoryGitInfo struct {
ParentID int64
GitUID string
}
type RepositoryPullReqSummary struct {
OpenCount int `json:"open_count"`
ClosedCount int `json:"closed_count"`
MergedCount int `json:"merged_count"`
}
type RepositorySummary struct {
DefaultBranchCommitCount int `json:"default_branch_commit_count"`
BranchCount int `json:"branch_count"`
TagCount int `json:"tag_count"`
PullReqSummary RepositoryPullReqSummary `json:"pull_req_summary"`
}