1
// Copyright 2019 The Gitea Authors. All rights reserved.
2
// SPDX-License-Identifier: MIT
16
"code.gitea.io/gitea/modules/container"
19
// CodeActivityStats represents git statistics data
20
type CodeActivityStats struct {
26
CommitCountInAllBranches int64
27
Authors []*CodeActivityAuthor
30
// CodeActivityAuthor represents git statistics data for commit authors
31
type CodeActivityAuthor struct {
37
// GetCodeActivityStats returns code statistics for activity page
38
func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) (*CodeActivityStats, error) {
39
stats := &CodeActivityStats{}
41
since := fromTime.Format(time.RFC3339)
43
stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(&RunOpts{Dir: repo.Path})
48
c, err := strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
52
stats.CommitCountInAllBranches = c
54
stdoutReader, stdoutWriter, err := os.Pipe()
59
_ = stdoutReader.Close()
60
_ = stdoutWriter.Close()
63
gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since)
65
gitCmd.AddArguments("--branches=*")
67
gitCmd.AddArguments("--first-parent").AddDynamicArguments(branch)
70
stderr := new(strings.Builder)
71
err = gitCmd.Run(&RunOpts{
76
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
77
_ = stdoutWriter.Close()
78
scanner := bufio.NewScanner(stdoutReader)
79
scanner.Split(bufio.ScanLines)
83
authors := make(map[string]*CodeActivityAuthor)
84
files := make(container.Set[string])
88
l := strings.TrimSpace(scanner.Text())
96
if p > 4 && len(l) == 0 {
101
case 2: // Commit sha-1
106
email := strings.ToLower(l)
107
if _, ok := authors[email]; !ok {
108
authors[email] = &CodeActivityAuthor{Name: author, Email: email, Commits: 0}
110
authors[email].Commits++
111
default: // Changed file
112
if parts := strings.Fields(l); len(parts) >= 3 {
114
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
119
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
127
if err = scanner.Err(); err != nil {
128
_ = stdoutReader.Close()
129
return fmt.Errorf("GetCodeActivityStats scan: %w", err)
131
a := make([]*CodeActivityAuthor, 0, len(authors))
132
for _, v := range authors {
135
// Sort authors descending depending on commit count
136
sort.Slice(a, func(i, j int) bool {
137
return a[i].Commits > a[j].Commits
139
stats.AuthorCount = int64(len(authors))
140
stats.ChangedFiles = int64(len(files))
142
_ = stdoutReader.Close()
147
return nil, fmt.Errorf("Failed to get GetCodeActivityStats for repository.\nError: %w\nStderr: %s", err, stderr)