diff --git a/app/api/controller/repo/list_commits.go b/app/api/controller/repo/list_commits.go index ef169527f..3e9ec83b8 100644 --- a/app/api/controller/repo/list_commits.go +++ b/app/api/controller/repo/list_commits.go @@ -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 diff --git a/app/api/controller/util.go b/app/api/controller/util.go index 487c63dfb..3e24ce561 100644 --- a/app/api/controller/util.go +++ b/app/api/controller/util.go @@ -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 diff --git a/app/api/openapi/repo.go b/app/api/openapi/repo.go index 0ef3387eb..57cbe5b9d 100644 --- a/app/api/openapi/repo.go +++ b/app/api/openapi/repo.go @@ -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) diff --git a/app/api/request/git.go b/app/api/request/git.go index 18193c80d..0b4d1e59a 100644 --- a/app/api/request/git.go +++ b/app/api/request/git.go @@ -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 } diff --git a/app/services/webhook/handler_branch.go b/app/services/webhook/handler_branch.go index 6de526fc1..247883e16 100644 --- a/app/services/webhook/handler_branch.go +++ b/app/services/webhook/handler_branch.go @@ -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) diff --git a/app/services/webhook/types.go b/app/services/webhook/types.go index 0137cd725..3f2aa58ba 100644 --- a/app/services/webhook/types.go +++ b/app/services/webhook/types.go @@ -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, } } diff --git a/git/adapter.go b/git/adapter.go index 1b13c127d..c8fddcd60 100644 --- a/git/adapter.go +++ b/git/adapter.go @@ -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) diff --git a/git/adapter/commit.go b/git/adapter/commit.go index 7bb41a491..9c65c9bc0 100644 --- a/git/adapter/commit.go +++ b/git/adapter/commit.go @@ -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"): diff --git a/git/commit.go b/git/commit.go index dc60f9c68..59f329332 100644 --- a/git/commit.go +++ b/git/commit.go @@ -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 } diff --git a/git/diff.go b/git/diff.go index c26260f9f..a6bf2b24f 100644 --- a/git/diff.go +++ b/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 diff --git a/git/interface.go b/git/interface.go index 74ca62cca..42d529f15 100644 --- a/git/interface.go +++ b/git/interface.go @@ -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) diff --git a/git/mapping.go b/git/mapping.go index ac35ba57d..ccc0baa23 100644 --- a/git/mapping.go +++ b/git/mapping.go @@ -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, } diff --git a/git/operations.go b/git/operations.go index 837c5f633..28f1f41bb 100644 --- a/git/operations.go +++ b/git/operations.go @@ -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 } diff --git a/git/types/types.go b/git/types/types.go index 676c840bf..c5e272163 100644 --- a/git/types/types.go +++ b/git/types/types.go @@ -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 } diff --git a/types/git.go b/types/git.go index b4d57d55e..852596507 100644 --- a/types/git.go +++ b/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 {