diff --git a/app/api/controller/repo/commit.go b/app/api/controller/repo/commit.go index 9aa1b9f9d..5d7f1a403 100644 --- a/app/api/controller/repo/commit.go +++ b/app/api/controller/repo/commit.go @@ -160,5 +160,6 @@ func (c *Controller) CommitFiles(ctx context.Context, return types.CommitFilesResponse{ CommitID: commit.CommitID, RuleViolations: violations, + Stats: commit.Stats, }, nil, nil } diff --git a/app/api/controller/space/export.go b/app/api/controller/space/export.go index 6892f2ba8..f8cf5d28a 100644 --- a/app/api/controller/space/export.go +++ b/app/api/controller/space/export.go @@ -60,7 +60,8 @@ func (c *Controller) Export(ctx context.Context, session *auth.Session, spaceRef var repos []*types.Repository page := 1 for { - reposInPage, err := c.repoStore.List(ctx, space.ID, &types.RepoFilter{Size: 200, Page: page, Order: enum.OrderDesc}) + reposInPage, err := c.repoStore.List( + ctx, space.ID, &types.RepoFilter{Size: 200, Page: page, Order: enum.OrderDesc}) if err != nil { return err } diff --git a/git/diff.go b/git/diff.go index 134a38bb5..9eeef696d 100644 --- a/git/diff.go +++ b/git/diff.go @@ -20,9 +20,12 @@ import ( "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" @@ -30,6 +33,26 @@ 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 @@ -177,6 +200,69 @@ 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 42d529f15..74ca62cca 100644 --- a/git/interface.go +++ b/git/interface.go @@ -74,6 +74,7 @@ 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/operations.go b/git/operations.go index c57631f06..1f2029f26 100644 --- a/git/operations.go +++ b/git/operations.go @@ -87,7 +87,14 @@ func (p *CommitFilesParams) Validate() error { return p.WriteParams.Validate() } +type CommitFileStat struct { + Total int + Additions int + Deletions int +} + type CommitFilesResponse struct { + Stats CommitFileStat CommitID string } @@ -235,6 +242,14 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C log.Debug().Msg("update ref") + stats, err := s.CommitShortStat(ctx, &CommitShortStatParams{ + Path: repo.Path, + Ref: newCommitSHA, + }) + if err != nil { + return CommitFilesResponse{}, fmt.Errorf("failed to get diff stats: %w", err) + } + branchRef := adapter.GetReferenceFromBranchName(params.Branch) if params.Branch != params.NewBranch { // we are creating a new branch, rather than updating the existing one @@ -264,6 +279,11 @@ func (s *Service) CommitFiles(ctx context.Context, params *CommitFilesParams) (C return CommitFilesResponse{ CommitID: commit.ID.String(), + Stats: CommitFileStat{ + Total: stats.Additions + stats.Deletions, + Additions: stats.Additions, + Deletions: stats.Deletions, + }, }, nil } diff --git a/types/commit.go b/types/commit.go index a56f6c1a9..0bbafa9e5 100644 --- a/types/commit.go +++ b/types/commit.go @@ -14,9 +14,12 @@ package types +import "github.com/harness/gitness/git" + // CommitFilesResponse holds commit id. type CommitFilesResponse struct { - DryRunRules bool `json:"dry_run_rules,omitempty"` - CommitID string `json:"commit_id"` - RuleViolations []RuleViolations `json:"rule_violations,omitempty"` + DryRunRules bool `json:"dry_run_rules,omitempty"` + CommitID string `json:"commit_id"` + RuleViolations []RuleViolations `json:"rule_violations,omitempty"` + Stats git.CommitFileStat `json:"stats,omitempty"` }