mirror of https://github.com/harness/drone.git
Add support for listing commit related per file stats (#1116)
parent
dae465a111
commit
564a0fef61
|
@ -43,15 +43,16 @@ func (c *Controller) ListCommits(ctx context.Context,
|
|||
}
|
||||
|
||||
rpcOut, err := c.git.ListCommits(ctx, &git.ListCommitsParams{
|
||||
ReadParams: git.CreateReadParams(repo),
|
||||
GitREF: gitRef,
|
||||
After: filter.After,
|
||||
Page: int32(filter.Page),
|
||||
Limit: int32(filter.Limit),
|
||||
Path: filter.Path,
|
||||
Since: filter.Since,
|
||||
Until: filter.Until,
|
||||
Committer: filter.Committer,
|
||||
ReadParams: git.CreateReadParams(repo),
|
||||
GitREF: gitRef,
|
||||
After: filter.After,
|
||||
Page: int32(filter.Page),
|
||||
Limit: int32(filter.Limit),
|
||||
Path: filter.Path,
|
||||
Since: filter.Since,
|
||||
Until: filter.Until,
|
||||
Committer: filter.Committer,
|
||||
IncludeStats: filter.IncludeStats,
|
||||
})
|
||||
if err != nil {
|
||||
return types.ListCommitResponse{}, err
|
||||
|
|
|
@ -94,6 +94,13 @@ func MapCommit(c *git.Commit) (*types.Commit, error) {
|
|||
return nil, fmt.Errorf("failed to map committer: %w", err)
|
||||
}
|
||||
|
||||
var insertions int64
|
||||
var deletions int64
|
||||
for _, stat := range c.FileStats {
|
||||
insertions += stat.Insertions
|
||||
deletions += stat.Deletions
|
||||
}
|
||||
|
||||
return &types.Commit{
|
||||
SHA: c.SHA,
|
||||
ParentSHAs: c.ParentSHAs,
|
||||
|
@ -101,14 +108,36 @@ func MapCommit(c *git.Commit) (*types.Commit, error) {
|
|||
Message: c.Message,
|
||||
Author: *author,
|
||||
Committer: *committer,
|
||||
DiffStats: types.CommitDiffStats{
|
||||
Additions: c.DiffStats.Additions,
|
||||
Deletions: c.DiffStats.Deletions,
|
||||
Total: c.DiffStats.Additions + c.DiffStats.Deletions,
|
||||
Stats: types.CommitStats{
|
||||
Total: types.ChangeStats{
|
||||
Insertions: insertions,
|
||||
Deletions: deletions,
|
||||
Changes: insertions + deletions,
|
||||
},
|
||||
Files: mapFileStats(c),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapFileStats(c *git.Commit) []types.CommitFileStats {
|
||||
fileStats := make([]types.CommitFileStats, len(c.FileStats))
|
||||
|
||||
for i, fStat := range c.FileStats {
|
||||
fileStats[i] = types.CommitFileStats{
|
||||
Path: fStat.Path,
|
||||
OldPath: fStat.OldPath,
|
||||
Status: fStat.ChangeType,
|
||||
ChangeStats: types.ChangeStats{
|
||||
Insertions: fStat.Insertions,
|
||||
Deletions: fStat.Deletions,
|
||||
Changes: fStat.Insertions + fStat.Deletions,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return fileStats
|
||||
}
|
||||
|
||||
func MapRenameDetails(c *git.RenameDetails) *types.RenameDetails {
|
||||
if c == nil {
|
||||
return nil
|
||||
|
|
|
@ -273,6 +273,21 @@ var queryParameterIncludeCommit = openapi3.ParameterOrRef{
|
|||
},
|
||||
}
|
||||
|
||||
var QueryParamIncludeStats = openapi3.ParameterOrRef{
|
||||
Parameter: &openapi3.Parameter{
|
||||
Name: request.QueryParamIncludeStats,
|
||||
In: openapi3.ParameterInQuery,
|
||||
Description: ptr.String("Indicates whether optional stats should be included in the response."),
|
||||
Required: ptr.Bool(false),
|
||||
Schema: &openapi3.SchemaOrRef{
|
||||
Schema: &openapi3.Schema{
|
||||
Type: ptrSchemaType(openapi3.SchemaTypeBoolean),
|
||||
Default: ptrptr(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var queryParameterLineFrom = openapi3.ParameterOrRef{
|
||||
Parameter: &openapi3.Parameter{
|
||||
Name: request.QueryParamLineFrom,
|
||||
|
@ -633,7 +648,8 @@ func repoOperations(reflector *openapi3.Reflector) {
|
|||
opListCommits.WithTags("repository")
|
||||
opListCommits.WithMapOfAnything(map[string]interface{}{"operationId": "listCommits"})
|
||||
opListCommits.WithParameters(queryParameterGitRef, queryParameterAfterCommits, queryParameterPath,
|
||||
queryParameterSince, queryParameterUntil, queryParameterCommitter, queryParameterPage, queryParameterLimit)
|
||||
queryParameterSince, queryParameterUntil, queryParameterCommitter,
|
||||
queryParameterPage, queryParameterLimit, QueryParamIncludeStats)
|
||||
_ = reflector.SetRequest(&opListCommits, new(listCommitsRequest), http.MethodGet)
|
||||
_ = reflector.SetJSONResponse(&opListCommits, []types.ListCommitResponse{}, http.StatusOK)
|
||||
_ = reflector.SetJSONResponse(&opListCommits, new(usererror.Error), http.StatusInternalServerError)
|
||||
|
|
|
@ -36,6 +36,7 @@ const (
|
|||
QueryParamSince = "since"
|
||||
QueryParamUntil = "until"
|
||||
QueryParamCommitter = "committer"
|
||||
QueryParamIncludeStats = "include_stats"
|
||||
QueryParamInternal = "internal"
|
||||
QueryParamService = "service"
|
||||
HeaderParamGitProtocol = "Git-Protocol"
|
||||
|
@ -101,16 +102,22 @@ func ParseCommitFilter(r *http.Request) (*types.CommitFilter, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
includeStats, err := QueryParamAsBoolOrDefault(r, QueryParamIncludeStats, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.CommitFilter{
|
||||
After: QueryParamOrDefault(r, QueryParamAfter, ""),
|
||||
PaginationFilter: types.PaginationFilter{
|
||||
Page: ParsePage(r),
|
||||
Limit: ParseLimit(r),
|
||||
},
|
||||
Path: QueryParamOrDefault(r, QueryParamPath, ""),
|
||||
Since: since,
|
||||
Until: until,
|
||||
Committer: QueryParamOrDefault(r, QueryParamCommitter, ""),
|
||||
Path: QueryParamOrDefault(r, QueryParamPath, ""),
|
||||
Since: since,
|
||||
Until: until,
|
||||
Committer: QueryParamOrDefault(r, QueryParamCommitter, ""),
|
||||
IncludeStats: includeStats,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -179,12 +179,12 @@ func (s *Service) fetchCommitsInfoForEvent(
|
|||
newSHA string,
|
||||
) ([]CommitInfo, int, error) {
|
||||
listCommitsParams := git.ListCommitsParams{
|
||||
ReadParams: git.ReadParams{RepoUID: repoUID},
|
||||
GitREF: newSHA,
|
||||
After: oldSHA,
|
||||
Page: 0,
|
||||
Limit: MaxWebhookCommitFileStats,
|
||||
IncludeFileStats: true,
|
||||
ReadParams: git.ReadParams{RepoUID: repoUID},
|
||||
GitREF: newSHA,
|
||||
After: oldSHA,
|
||||
Page: 0,
|
||||
Limit: MaxWebhookCommitFileStats,
|
||||
IncludeStats: true,
|
||||
}
|
||||
listCommitsOutput, err := s.git.ListCommits(ctx, &listCommitsParams)
|
||||
|
||||
|
|
|
@ -20,8 +20,11 @@ import (
|
|||
|
||||
"github.com/harness/gitness/app/url"
|
||||
"github.com/harness/gitness/git"
|
||||
gitenum "github.com/harness/gitness/git/enum"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -183,14 +186,35 @@ type CommitInfo struct {
|
|||
|
||||
// commitInfoFrom gets the CommitInfo from a git.Commit.
|
||||
func commitInfoFrom(commit git.Commit) CommitInfo {
|
||||
var added []string
|
||||
var removed []string
|
||||
var modified []string
|
||||
|
||||
for _, stat := range commit.FileStats {
|
||||
switch {
|
||||
case stat.ChangeType == gitenum.FileDiffStatusModified:
|
||||
modified = append(modified, stat.Path)
|
||||
case stat.ChangeType == gitenum.FileDiffStatusRenamed:
|
||||
added = append(added, stat.Path)
|
||||
removed = append(removed, stat.OldPath)
|
||||
case stat.ChangeType == gitenum.FileDiffStatusDeleted:
|
||||
removed = append(removed, stat.Path)
|
||||
case stat.ChangeType == gitenum.FileDiffStatusAdded || stat.ChangeType == gitenum.FileDiffStatusCopied:
|
||||
added = append(added, stat.Path)
|
||||
case stat.ChangeType == gitenum.FileDiffStatusUndefined:
|
||||
default:
|
||||
log.Warn().Msgf("unknown change type %q for path %q", stat.ChangeType, stat.Path)
|
||||
}
|
||||
}
|
||||
|
||||
return CommitInfo{
|
||||
SHA: commit.SHA,
|
||||
Message: commit.Message,
|
||||
Author: signatureInfoFrom(commit.Author),
|
||||
Committer: signatureInfoFrom(commit.Committer),
|
||||
Added: commit.FileStats.Added,
|
||||
Removed: commit.FileStats.Removed,
|
||||
Modified: commit.FileStats.Modified,
|
||||
Added: added,
|
||||
Removed: removed,
|
||||
Modified: modified,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ type Adapter interface {
|
|||
ref string,
|
||||
page int,
|
||||
limit int,
|
||||
includeFileStats bool,
|
||||
includeStats bool,
|
||||
filter types.CommitFilter) ([]types.Commit, []types.PathRenameDetails, error)
|
||||
ListCommitSHAs(ctx context.Context, repoPath string,
|
||||
ref string, page int, limit int, filter types.CommitFilter) ([]string, error)
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -143,7 +144,7 @@ func (a Adapter) ListCommits(
|
|||
ref string,
|
||||
page int,
|
||||
limit int,
|
||||
includeFileStats bool,
|
||||
includeStats bool,
|
||||
filter types.CommitFilter,
|
||||
) ([]types.Commit, []types.PathRenameDetails, error) {
|
||||
if repoPath == "" {
|
||||
|
@ -172,15 +173,16 @@ func (a Adapter) ListCommits(
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
commits[i] = *commit
|
||||
|
||||
if includeFileStats {
|
||||
fileStat, err := getFileStats(ctx, giteaRepo, commit.SHA)
|
||||
if includeStats {
|
||||
fileStats, err := getCommitFileStats(ctx, giteaRepo, commit.SHA)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("encountered error getting file stat: %w", err)
|
||||
return nil, nil, fmt.Errorf("encountered error getting commit file stats: %w", err)
|
||||
}
|
||||
commit.FileStats = fileStat
|
||||
commit.FileStats = fileStats
|
||||
}
|
||||
|
||||
commits[i] = *commit
|
||||
}
|
||||
|
||||
if len(filter.Path) != 0 {
|
||||
|
@ -195,34 +197,35 @@ func (a Adapter) ListCommits(
|
|||
return commits, nil, nil
|
||||
}
|
||||
|
||||
func getFileStats(
|
||||
func getCommitFileStats(
|
||||
ctx context.Context,
|
||||
giteaRepo *gitea.Repository,
|
||||
sha string,
|
||||
) (types.CommitFileStats, error) {
|
||||
changeInfos, err := getChangeInfos(ctx, giteaRepo, sha)
|
||||
) ([]types.CommitFileStats, error) {
|
||||
var changeInfoTypes map[string]changeInfoType
|
||||
changeInfoTypes, err := getChangeInfoTypes(ctx, giteaRepo, sha)
|
||||
if err != nil {
|
||||
return types.CommitFileStats{}, fmt.Errorf("failed to get change infos: %w", err)
|
||||
return nil, fmt.Errorf("failed to get change infos: %w", err)
|
||||
}
|
||||
fileStats := types.CommitFileStats{
|
||||
Added: make([]string, 0),
|
||||
Removed: make([]string, 0),
|
||||
Modified: make([]string, 0),
|
||||
|
||||
changeInfoChanges, err := getChangeInfoChanges(giteaRepo, sha)
|
||||
if err != nil {
|
||||
return []types.CommitFileStats{}, fmt.Errorf("failed to get change infos: %w", err)
|
||||
}
|
||||
for _, c := range changeInfos {
|
||||
switch {
|
||||
case c.ChangeType == enum.FileDiffStatusModified || c.ChangeType == enum.FileDiffStatusRenamed:
|
||||
fileStats.Modified = append(fileStats.Modified, c.Path)
|
||||
case c.ChangeType == enum.FileDiffStatusDeleted:
|
||||
fileStats.Removed = append(fileStats.Removed, c.Path)
|
||||
case c.ChangeType == enum.FileDiffStatusAdded || c.ChangeType == enum.FileDiffStatusCopied:
|
||||
fileStats.Added = append(fileStats.Added, c.Path)
|
||||
case c.ChangeType == enum.FileDiffStatusUndefined:
|
||||
default:
|
||||
log.Ctx(ctx).Warn().Msgf("unknown change type %q for path %q",
|
||||
c.ChangeType, c.Path)
|
||||
|
||||
fileStats := make([]types.CommitFileStats, len(changeInfoChanges))
|
||||
i := 0
|
||||
for path, info := range changeInfoChanges {
|
||||
fileStats[i] = types.CommitFileStats{
|
||||
Path: changeInfoTypes[path].Path,
|
||||
OldPath: changeInfoTypes[path].OldPath,
|
||||
Status: changeInfoTypes[path].ChangeType,
|
||||
Insertions: info.Insertions,
|
||||
Deletions: info.Deletions,
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return fileStats, nil
|
||||
}
|
||||
|
||||
|
@ -261,7 +264,7 @@ func getRenameDetails(
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if renameDetails.NewPath != "" || renameDetails.OldPath != "" {
|
||||
if renameDetails.Path != "" || renameDetails.OldPath != "" {
|
||||
renameDetails.CommitSHABefore = commits[0].SHA
|
||||
renameDetailsList = append(renameDetailsList, *renameDetails)
|
||||
}
|
||||
|
@ -275,7 +278,7 @@ func getRenameDetails(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if renameDetailsLast.NewPath != "" || renameDetailsLast.OldPath != "" {
|
||||
if renameDetailsLast.Path != "" || renameDetailsLast.OldPath != "" {
|
||||
renameDetailsLast.CommitSHAAfter = commits[len(commits)-1].SHA
|
||||
renameDetailsList = append(renameDetailsList, *renameDetailsLast)
|
||||
}
|
||||
|
@ -288,16 +291,16 @@ func giteaGetRenameDetails(
|
|||
ref string,
|
||||
path string,
|
||||
) (*types.PathRenameDetails, error) {
|
||||
changeInfos, err := getChangeInfos(ctx, giteaRepo, ref)
|
||||
changeInfos, err := getChangeInfoTypes(ctx, giteaRepo, ref)
|
||||
if err != nil {
|
||||
return &types.PathRenameDetails{}, fmt.Errorf("failed to get change infos %w", err)
|
||||
}
|
||||
|
||||
for _, c := range changeInfos {
|
||||
if c.ChangeType == enum.FileDiffStatusRenamed && (c.Path == path || c.NewPath == path) {
|
||||
if c.ChangeType == enum.FileDiffStatusRenamed && (c.OldPath == path || c.Path == path) {
|
||||
return &types.PathRenameDetails{
|
||||
OldPath: c.Path,
|
||||
NewPath: c.NewPath,
|
||||
OldPath: c.OldPath,
|
||||
Path: c.Path,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
@ -305,59 +308,132 @@ func giteaGetRenameDetails(
|
|||
return &types.PathRenameDetails{}, nil
|
||||
}
|
||||
|
||||
func getChangeInfos(
|
||||
ctx context.Context,
|
||||
giteaRepo *gitea.Repository,
|
||||
ref string,
|
||||
) ([]changeInfo, error) {
|
||||
func gitLogNameStatus(giteaRepo *gitea.Repository, ref string) ([]string, error) {
|
||||
cmd := command.New("log",
|
||||
command.WithArg(ref),
|
||||
command.WithFlag("--name-status"),
|
||||
command.WithFlag("--pretty=format:", "-1"),
|
||||
command.WithFlag("--format="),
|
||||
command.WithArg(ref),
|
||||
command.WithFlag("--max-count=1"),
|
||||
)
|
||||
output := &bytes.Buffer{}
|
||||
err := cmd.Run(giteaRepo.Ctx, command.WithDir(giteaRepo.Path), command.WithStdout(output))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to trigger log command: %w", err)
|
||||
}
|
||||
lines := parseLinesToSlice(output.Bytes())
|
||||
return parseLinesToSlice(output.Bytes()), nil
|
||||
}
|
||||
|
||||
changeInfos, err := getFileChangeTypeFromLog(ctx, lines)
|
||||
func gitShowNumstat(giteaRepo *gitea.Repository, ref string) ([]string, error) {
|
||||
cmd := command.New("show",
|
||||
command.WithFlag("--numstat"),
|
||||
command.WithFlag("--format="),
|
||||
command.WithArg(ref),
|
||||
)
|
||||
output := &bytes.Buffer{}
|
||||
err := cmd.Run(giteaRepo.Ctx, command.WithDir(giteaRepo.Path), command.WithStdout(output))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to trigger show command: %w", err)
|
||||
}
|
||||
return parseLinesToSlice(output.Bytes()), nil
|
||||
}
|
||||
|
||||
// Will match "R100\tREADME.md\tREADME_new.md".
|
||||
// Will extract README.md and README_new.md.
|
||||
var renameRegex = regexp.MustCompile(`\t(.+)\t(.+)`)
|
||||
|
||||
func getChangeInfoTypes(
|
||||
ctx context.Context,
|
||||
giteaRepo *gitea.Repository,
|
||||
ref string,
|
||||
) (map[string]changeInfoType, error) {
|
||||
lines, err := gitLogNameStatus(giteaRepo, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return changeInfos, nil
|
||||
}
|
||||
|
||||
type changeInfo struct {
|
||||
ChangeType enum.FileDiffStatus
|
||||
Path string
|
||||
// populated only in case of renames
|
||||
NewPath string
|
||||
}
|
||||
changeInfoTypes := make(map[string]changeInfoType, len(lines))
|
||||
for _, line := range lines {
|
||||
c := changeInfoType{}
|
||||
|
||||
func getFileChangeTypeFromLog(
|
||||
ctx context.Context,
|
||||
changeStrings []string,
|
||||
) ([]changeInfo, error) {
|
||||
changeInfos := make([]changeInfo, len(changeStrings))
|
||||
for i, changeString := range changeStrings {
|
||||
changeStringSplit := strings.Split(changeString, "\t")
|
||||
if len(changeStringSplit) < 1 {
|
||||
return changeInfos, fmt.Errorf("could not parse changeString %q", changeString)
|
||||
matches := renameRegex.FindStringSubmatch(line) // renamed file
|
||||
if len(matches) > 0 {
|
||||
c.OldPath = matches[1]
|
||||
c.Path = matches[2]
|
||||
} else {
|
||||
lineParts := strings.Split(line, "\t")
|
||||
if len(lineParts) != 2 {
|
||||
return changeInfoTypes, fmt.Errorf("could not parse file change status string %q", line)
|
||||
}
|
||||
c.Path = lineParts[1]
|
||||
}
|
||||
|
||||
c := changeInfo{}
|
||||
c.ChangeType = convertChangeType(ctx, changeStringSplit[0])
|
||||
c.Path = changeStringSplit[1]
|
||||
if len(changeStringSplit) == 3 {
|
||||
c.NewPath = changeStringSplit[2]
|
||||
}
|
||||
changeInfos[i] = c
|
||||
c.ChangeType = convertChangeType(ctx, line)
|
||||
|
||||
changeInfoTypes[c.Path] = c
|
||||
}
|
||||
return changeInfoTypes, nil
|
||||
}
|
||||
|
||||
// Will match "31\t0\t.harness/apidiff.yaml".
|
||||
// Will extract 31, 0 and .harness/apidiff.yaml.
|
||||
var insertionsDeletionsRegex = regexp.MustCompile(`(\d+)\t(\d+)\t(.+)`)
|
||||
|
||||
// Will match "0\t0\tREADME.md => README_new.md".
|
||||
// Will extract README_new.md.
|
||||
var renameRegexWithArrow = regexp.MustCompile(`\d+\t\d+\t.+\s=>\s(.+)`)
|
||||
|
||||
func getChangeInfoChanges(
|
||||
giteaRepo *gitea.Repository,
|
||||
ref string,
|
||||
) (map[string]changeInfoChange, error) {
|
||||
lines, err := gitShowNumstat(giteaRepo, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changeInfos := make(map[string]changeInfoChange, len(lines))
|
||||
for _, line := range lines {
|
||||
matches := insertionsDeletionsRegex.FindStringSubmatch(line)
|
||||
if len(matches) != 4 {
|
||||
return map[string]changeInfoChange{},
|
||||
fmt.Errorf("failed to regex match insertions and deletions for %q", line)
|
||||
}
|
||||
|
||||
path := matches[3]
|
||||
if renMatches := renameRegexWithArrow.FindStringSubmatch(line); len(renMatches) == 2 {
|
||||
path = renMatches[1]
|
||||
}
|
||||
|
||||
insertions, err := strconv.ParseInt(matches[1], 10, 64)
|
||||
if err != nil {
|
||||
return map[string]changeInfoChange{},
|
||||
fmt.Errorf("failed to parse insertions for %q", line)
|
||||
}
|
||||
deletions, err := strconv.ParseInt(matches[2], 10, 64)
|
||||
if err != nil {
|
||||
return map[string]changeInfoChange{},
|
||||
fmt.Errorf("failed to parse deletions for %q", line)
|
||||
}
|
||||
|
||||
changeInfos[path] = changeInfoChange{
|
||||
Insertions: insertions,
|
||||
Deletions: deletions,
|
||||
}
|
||||
}
|
||||
|
||||
return changeInfos, nil
|
||||
}
|
||||
|
||||
type changeInfoType struct {
|
||||
ChangeType enum.FileDiffStatus
|
||||
OldPath string // populated only in case of renames
|
||||
Path string
|
||||
}
|
||||
type changeInfoChange struct {
|
||||
Insertions int64
|
||||
Deletions int64
|
||||
}
|
||||
|
||||
func convertChangeType(ctx context.Context, c string) enum.FileDiffStatus {
|
||||
switch {
|
||||
case strings.HasPrefix(c, "A"):
|
||||
|
|
|
@ -20,9 +20,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/harness/gitness/errors"
|
||||
"github.com/harness/gitness/git/enum"
|
||||
"github.com/harness/gitness/git/types"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type GetCommitParams struct {
|
||||
|
@ -32,14 +31,13 @@ type GetCommitParams struct {
|
|||
}
|
||||
|
||||
type Commit struct {
|
||||
SHA string `json:"sha"`
|
||||
ParentSHAs []string `json:"parent_shas,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Author Signature `json:"author"`
|
||||
Committer Signature `json:"committer"`
|
||||
FileStats CommitFileStats `json:"file_stats,omitempty"`
|
||||
DiffStats CommitDiffStats `json:"diff_stats,omitempty"`
|
||||
SHA string `json:"sha"`
|
||||
ParentSHAs []string `json:"parent_shas,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Author Signature `json:"author"`
|
||||
Committer Signature `json:"committer"`
|
||||
FileStats []CommitFileStats `json:"file_stats,omitempty"`
|
||||
}
|
||||
|
||||
type GetCommitOutput struct {
|
||||
|
@ -111,8 +109,8 @@ type ListCommitsParams struct {
|
|||
// Committer allows to filter for commits based on the committer - Optional, ignored if string is empty.
|
||||
Committer string
|
||||
|
||||
// IncludeFileStats allows you to include information about files changed, added and modified.
|
||||
IncludeFileStats bool
|
||||
// IncludeStats allows to include information about inserted, deletions and status for changed files.
|
||||
IncludeStats bool
|
||||
}
|
||||
|
||||
type RenameDetails struct {
|
||||
|
@ -129,9 +127,11 @@ type ListCommitsOutput struct {
|
|||
}
|
||||
|
||||
type CommitFileStats struct {
|
||||
Added []string
|
||||
Modified []string
|
||||
Removed []string
|
||||
ChangeType enum.FileDiffStatus
|
||||
Path string
|
||||
OldPath string // populated only in case of renames
|
||||
Insertions int64
|
||||
Deletions int64
|
||||
}
|
||||
|
||||
func (s *Service) ListCommits(ctx context.Context, params *ListCommitsParams) (*ListCommitsOutput, error) {
|
||||
|
@ -147,7 +147,7 @@ func (s *Service) ListCommits(ctx context.Context, params *ListCommitsParams) (*
|
|||
params.GitREF,
|
||||
int(params.Page),
|
||||
int(params.Limit),
|
||||
params.IncludeFileStats,
|
||||
params.IncludeStats,
|
||||
types.CommitFilter{
|
||||
AfterRef: params.After,
|
||||
Path: params.Path,
|
||||
|
@ -183,19 +183,6 @@ func (s *Service) ListCommits(ctx context.Context, params *ListCommitsParams) (*
|
|||
return nil, fmt.Errorf("failed to map rpc commit: %w", err)
|
||||
}
|
||||
|
||||
stat, err := s.CommitShortStat(ctx, &CommitShortStatParams{
|
||||
Path: repoPath,
|
||||
Ref: commit.SHA,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn().Msgf("failed to get diff stats: %s", err)
|
||||
}
|
||||
commit.DiffStats = CommitDiffStats{
|
||||
Additions: stat.Additions,
|
||||
Deletions: stat.Deletions,
|
||||
Total: stat.Additions + stat.Deletions,
|
||||
}
|
||||
|
||||
commits[i] = *commit
|
||||
}
|
||||
|
||||
|
|
87
git/diff.go
87
git/diff.go
|
@ -16,16 +16,12 @@ package git
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/harness/gitness/errors"
|
||||
"github.com/harness/gitness/git/command"
|
||||
"github.com/harness/gitness/git/diff"
|
||||
"github.com/harness/gitness/git/enum"
|
||||
"github.com/harness/gitness/git/types"
|
||||
|
@ -33,26 +29,6 @@ import (
|
|||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// Parse "1 file changed, 3 insertions(+), 3 deletions(-)" for nums.
|
||||
var shortStatsRegexp = regexp.MustCompile(
|
||||
`files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?`)
|
||||
|
||||
type CommitShortStatParams struct {
|
||||
Path string
|
||||
Ref string
|
||||
}
|
||||
|
||||
func (p CommitShortStatParams) Validate() error {
|
||||
if p.Path == "" {
|
||||
return errors.InvalidArgument("path cannot be empty")
|
||||
}
|
||||
|
||||
if p.Ref == "" {
|
||||
return errors.InvalidArgument("ref cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DiffParams struct {
|
||||
ReadParams
|
||||
BaseRef string
|
||||
|
@ -200,69 +176,6 @@ func (s *Service) DiffStats(ctx context.Context, params *DiffParams) (DiffStatsO
|
|||
}, nil
|
||||
}
|
||||
|
||||
func parseCommitShortStat(statBuffer *bytes.Buffer) (CommitShortStatOutput, error) {
|
||||
matches := shortStatsRegexp.FindStringSubmatch(statBuffer.String())
|
||||
if len(matches) != 3 {
|
||||
return CommitShortStatOutput{}, errors.Internal(errors.New("failed to match stats line"), "")
|
||||
}
|
||||
|
||||
var stat CommitShortStatOutput
|
||||
|
||||
// if there are insertions; no insertions case: "1 file changed, 3 deletions(-)"
|
||||
if len(matches[1]) > 0 {
|
||||
if value, err := strconv.Atoi(matches[1]); err == nil {
|
||||
stat.Additions = value
|
||||
} else {
|
||||
return CommitShortStatOutput{}, fmt.Errorf("failed to parse additions stats: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// if there are deletions; no deletions case: "1 file changed, 3 insertions(+)"
|
||||
if len(matches[2]) > 0 {
|
||||
if value, err := strconv.Atoi(matches[2]); err == nil {
|
||||
stat.Deletions = value
|
||||
} else {
|
||||
return CommitShortStatOutput{}, fmt.Errorf("failed to parse deletions stats: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
type CommitShortStatOutput struct {
|
||||
Additions int
|
||||
Deletions int
|
||||
}
|
||||
|
||||
func (s *Service) CommitShortStat(
|
||||
ctx context.Context,
|
||||
params *CommitShortStatParams,
|
||||
) (CommitShortStatOutput, error) {
|
||||
if err := params.Validate(); err != nil {
|
||||
return CommitShortStatOutput{}, err
|
||||
}
|
||||
// git log -1 --shortstat --pretty=format:""
|
||||
cmd := command.New(
|
||||
"log",
|
||||
command.WithFlag("-1"),
|
||||
command.WithFlag("--shortstat"),
|
||||
command.WithFlag(`--pretty=format:""`),
|
||||
command.WithArg(params.Ref),
|
||||
)
|
||||
|
||||
stdout := bytes.NewBuffer(nil)
|
||||
if err := cmd.Run(ctx, command.WithDir(params.Path), command.WithStdout(stdout)); err != nil {
|
||||
return CommitShortStatOutput{}, errors.Internal(err, "failed to show stats")
|
||||
}
|
||||
|
||||
stat, err := parseCommitShortStat(stdout)
|
||||
if err != nil {
|
||||
return CommitShortStatOutput{}, errors.Internal(err, "failed to parse stats line")
|
||||
}
|
||||
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
type GetDiffHunkHeadersParams struct {
|
||||
ReadParams
|
||||
SourceCommitSHA string
|
||||
|
|
|
@ -74,7 +74,6 @@ type Interface interface {
|
|||
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)
|
||||
CommitShortStat(ctx context.Context, params *CommitShortStatParams) (CommitShortStatOutput, error)
|
||||
|
||||
GetDiffHunkHeaders(ctx context.Context, params GetDiffHunkHeadersParams) (GetDiffHunkHeadersOutput, error)
|
||||
DiffCut(ctx context.Context, params *DiffCutParams) (DiffCutOutput, error)
|
||||
|
|
|
@ -55,6 +55,7 @@ func mapCommit(c *types.Commit) (*Commit, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to map rpc committer: %w", err)
|
||||
}
|
||||
|
||||
return &Commit{
|
||||
SHA: c.SHA,
|
||||
ParentSHAs: c.ParentSHAs,
|
||||
|
@ -62,16 +63,24 @@ func mapCommit(c *types.Commit) (*Commit, error) {
|
|||
Message: c.Message,
|
||||
Author: *author,
|
||||
Committer: *comitter,
|
||||
FileStats: *mapFileStats(&c.FileStats),
|
||||
FileStats: mapFileStats(c.FileStats),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapFileStats(s *types.CommitFileStats) *CommitFileStats {
|
||||
return &CommitFileStats{
|
||||
Added: s.Added,
|
||||
Modified: s.Modified,
|
||||
Removed: s.Removed,
|
||||
func mapFileStats(typeStats []types.CommitFileStats) []CommitFileStats {
|
||||
var stats = make([]CommitFileStats, len(typeStats))
|
||||
|
||||
for i, tStat := range typeStats {
|
||||
stats[i] = CommitFileStats{
|
||||
ChangeType: tStat.Status,
|
||||
Path: tStat.Path,
|
||||
OldPath: tStat.OldPath,
|
||||
Insertions: tStat.Insertions,
|
||||
Deletions: tStat.Deletions,
|
||||
}
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func mapSignature(s *types.Signature) (*Signature, error) {
|
||||
|
@ -201,7 +210,7 @@ func mapRenameDetails(c []types.PathRenameDetails) []*RenameDetails {
|
|||
for i, detail := range c {
|
||||
renameDetailsList[i] = &RenameDetails{
|
||||
OldPath: detail.OldPath,
|
||||
NewPath: detail.NewPath,
|
||||
NewPath: detail.Path,
|
||||
CommitShaBefore: detail.CommitSHABefore,
|
||||
CommitShaAfter: detail.CommitSHAAfter,
|
||||
}
|
||||
|
|
|
@ -84,12 +84,6 @@ func (p *CommitFilesParams) Validate() error {
|
|||
return p.WriteParams.Validate()
|
||||
}
|
||||
|
||||
type CommitDiffStats struct {
|
||||
Total int
|
||||
Additions int
|
||||
Deletions int
|
||||
}
|
||||
|
||||
type CommitFilesResponse struct {
|
||||
CommitID string
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/harness/gitness/errors"
|
||||
"github.com/harness/gitness/git/enum"
|
||||
)
|
||||
|
||||
const NilSHA = "0000000000000000000000000000000000000000"
|
||||
|
@ -140,20 +141,23 @@ type WalkReferencesOptions struct {
|
|||
MaxWalkDistance int32
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
SHA string `json:"sha"`
|
||||
ParentSHAs []string `json:"parent_shas,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Author Signature `json:"author"`
|
||||
Committer Signature `json:"committer"`
|
||||
FileStats CommitFileStats `json:"file_stats,omitempty"`
|
||||
type CommitFileStats struct {
|
||||
Path string `json:"path"`
|
||||
OldPath string `json:"old_path,omitempty"`
|
||||
Status enum.FileDiffStatus `json:"status"`
|
||||
Insertions int64 `json:"insertions"`
|
||||
Deletions int64 `json:"deletions"`
|
||||
Changes int64 `json:"changes"`
|
||||
}
|
||||
|
||||
type CommitFileStats struct {
|
||||
Added []string
|
||||
Modified []string
|
||||
Removed []string
|
||||
type Commit struct {
|
||||
SHA string `json:"sha"`
|
||||
ParentSHAs []string `json:"parent_shas,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Author Signature `json:"author"`
|
||||
Committer Signature `json:"committer"`
|
||||
FileStats []CommitFileStats `json:"file_stats,omitempty"`
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
|
@ -346,7 +350,7 @@ type BlamePart struct {
|
|||
|
||||
type PathRenameDetails struct {
|
||||
OldPath string
|
||||
NewPath string
|
||||
Path string
|
||||
CommitSHABefore string
|
||||
CommitSHAAfter string
|
||||
}
|
||||
|
|
46
types/git.go
46
types/git.go
|
@ -17,6 +17,7 @@ package types
|
|||
import (
|
||||
"time"
|
||||
|
||||
gitenum "github.com/harness/gitness/git/enum"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
|
@ -31,11 +32,12 @@ type PaginationFilter struct {
|
|||
// CommitFilter stores commit query parameters.
|
||||
type CommitFilter struct {
|
||||
PaginationFilter
|
||||
After string `json:"after"`
|
||||
Path string `json:"path"`
|
||||
Since int64 `json:"since"`
|
||||
Until int64 `json:"until"`
|
||||
Committer string `json:"committer"`
|
||||
After string `json:"after"`
|
||||
Path string `json:"path"`
|
||||
Since int64 `json:"since"`
|
||||
Until int64 `json:"until"`
|
||||
Committer string `json:"committer"`
|
||||
IncludeStats bool `json:"include_stats"`
|
||||
}
|
||||
|
||||
// BranchFilter stores branch query parameters.
|
||||
|
@ -56,20 +58,32 @@ type TagFilter struct {
|
|||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
type CommitDiffStats struct {
|
||||
Total int `json:"total"`
|
||||
Additions int `json:"additions"`
|
||||
Deletions int `json:"deletions"`
|
||||
type ChangeStats struct {
|
||||
Insertions int64 `json:"insertions"`
|
||||
Deletions int64 `json:"deletions"`
|
||||
Changes int64 `json:"changes"`
|
||||
}
|
||||
|
||||
type CommitFileStats struct {
|
||||
Path string `json:"path"`
|
||||
OldPath string `json:"old_path,omitempty"`
|
||||
Status gitenum.FileDiffStatus `json:"status"`
|
||||
ChangeStats
|
||||
}
|
||||
|
||||
type CommitStats struct {
|
||||
Total ChangeStats `json:"total,omitempty"`
|
||||
Files []CommitFileStats `json:"files,omitempty"`
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
SHA string `json:"sha"`
|
||||
ParentSHAs []string `json:"parent_shas,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Author Signature `json:"author"`
|
||||
Committer Signature `json:"committer"`
|
||||
DiffStats CommitDiffStats `json:"diff_stats"`
|
||||
SHA string `json:"sha"`
|
||||
ParentSHAs []string `json:"parent_shas,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Author Signature `json:"author"`
|
||||
Committer Signature `json:"committer"`
|
||||
Stats CommitStats `json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
type Signature struct {
|
||||
|
|
Loading…
Reference in New Issue