mirror of https://github.com/harness/drone.git
Add repo summary endpoint (#2037)
parent
c365ef246a
commit
adf041d747
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1154,4 +1154,17 @@ func repoOperations(reflector *openapi3.Reflector) {
|
||||||
_ = reflector.SetJSONResponse(&opArchive, new(usererror.Error), http.StatusForbidden)
|
_ = reflector.SetJSONResponse(&opArchive, new(usererror.Error), http.StatusForbidden)
|
||||||
_ = reflector.SetJSONResponse(&opArchive, new(usererror.Error), http.StatusNotFound)
|
_ = reflector.SetJSONResponse(&opArchive, new(usererror.Error), http.StatusNotFound)
|
||||||
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/archive/{git_ref}.{format}", opArchive)
|
_ = 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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,6 +287,8 @@ func setupRepos(r chi.Router,
|
||||||
r.Patch("/general", handlerreposettings.HandleGeneralUpdate(repoSettingsCtrl))
|
r.Patch("/general", handlerreposettings.HandleGeneralUpdate(repoSettingsCtrl))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.Get("/summary", handlerrepo.HandleSummary(repoCtrl))
|
||||||
|
|
||||||
r.Post("/move", handlerrepo.HandleMove(repoCtrl))
|
r.Post("/move", handlerrepo.HandleMove(repoCtrl))
|
||||||
r.Get("/service-accounts", handlerrepo.HandleListServiceAccounts(repoCtrl))
|
r.Get("/service-accounts", handlerrepo.HandleListServiceAccounts(repoCtrl))
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,11 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/git/command"
|
"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
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -398,3 +398,28 @@ l:
|
||||||
}
|
}
|
||||||
return tag, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ var descriptions = map[string]builder{
|
||||||
// git-blame(1) does not support disambiguating options from paths from revisions.
|
// git-blame(1) does not support disambiguating options from paths from revisions.
|
||||||
flags: NoRefUpdates | NoEndOfOptions,
|
flags: NoRefUpdates | NoEndOfOptions,
|
||||||
},
|
},
|
||||||
|
"branch": {},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
flags: NoRefUpdates,
|
flags: NoRefUpdates,
|
||||||
},
|
},
|
||||||
|
|
|
@ -102,4 +102,9 @@ type Interface interface {
|
||||||
*/
|
*/
|
||||||
ScanSecrets(ctx context.Context, param *ScanSecretsParams) (*ScanSecretsOutput, error)
|
ScanSecrets(ctx context.Context, param *ScanSecretsParams) (*ScanSecretsOutput, error)
|
||||||
Archive(ctx context.Context, params ArchiveParams, w io.Writer) error
|
Archive(ctx context.Context, params ArchiveParams, w io.Writer) error
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Repo Summary service
|
||||||
|
*/
|
||||||
|
Summary(ctx context.Context, params *ReadParams) (*SummaryOutput, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,11 @@ func CommitCount(
|
||||||
repoPath string,
|
repoPath string,
|
||||||
start, end string,
|
start, end string,
|
||||||
) (int, error) {
|
) (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)
|
stdout := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -98,3 +98,16 @@ type RepositoryGitInfo struct {
|
||||||
ParentID int64
|
ParentID int64
|
||||||
GitUID string
|
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"`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue