github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/git/repo_stats.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package git 7 8 import ( 9 "bufio" 10 "context" 11 "fmt" 12 "os" 13 "sort" 14 "strconv" 15 "strings" 16 "time" 17 ) 18 19 // CodeActivityStats represents git statistics data 20 type CodeActivityStats struct { 21 AuthorCount int64 22 CommitCount int64 23 ChangedFiles int64 24 Additions int64 25 Deletions int64 26 CommitCountInAllBranches int64 27 Authors []*CodeActivityAuthor 28 } 29 30 // CodeActivityAuthor represents git statistics data for commit authors 31 type CodeActivityAuthor struct { 32 Name string 33 Email string 34 Commits int64 35 } 36 37 // GetCodeActivityStats returns code statistics for activity page 38 func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string) (*CodeActivityStats, error) { 39 stats := &CodeActivityStats{} 40 41 since := fromTime.Format(time.RFC3339) 42 43 stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)).RunStdString(&RunOpts{Dir: repo.Path}) 44 if runErr != nil { 45 return nil, runErr 46 } 47 48 c, err := strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) 49 if err != nil { 50 return nil, err 51 } 52 stats.CommitCountInAllBranches = c 53 54 stdoutReader, stdoutWriter, err := os.Pipe() 55 if err != nil { 56 return nil, err 57 } 58 defer func() { 59 _ = stdoutReader.Close() 60 _ = stdoutWriter.Close() 61 }() 62 63 args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso", fmt.Sprintf("--since='%s'", since)} 64 if len(branch) == 0 { 65 args = append(args, "--branches=*") 66 } else { 67 args = append(args, "--first-parent", branch) 68 } 69 70 stderr := new(strings.Builder) 71 err = NewCommand(repo.Ctx, args...).Run(&RunOpts{ 72 Env: []string{}, 73 Dir: repo.Path, 74 Stdout: stdoutWriter, 75 Stderr: stderr, 76 PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { 77 _ = stdoutWriter.Close() 78 scanner := bufio.NewScanner(stdoutReader) 79 scanner.Split(bufio.ScanLines) 80 stats.CommitCount = 0 81 stats.Additions = 0 82 stats.Deletions = 0 83 authors := make(map[string]*CodeActivityAuthor) 84 files := make(map[string]bool) 85 var author string 86 p := 0 87 for scanner.Scan() { 88 l := strings.TrimSpace(scanner.Text()) 89 if l == "---" { 90 p = 1 91 } else if p == 0 { 92 continue 93 } else { 94 p++ 95 } 96 if p > 4 && len(l) == 0 { 97 continue 98 } 99 switch p { 100 case 1: // Separator 101 case 2: // Commit sha-1 102 stats.CommitCount++ 103 case 3: // Author 104 author = l 105 case 4: // E-mail 106 email := strings.ToLower(l) 107 if _, ok := authors[email]; !ok { 108 authors[email] = &CodeActivityAuthor{Name: author, Email: email, Commits: 0} 109 } 110 authors[email].Commits++ 111 default: // Changed file 112 if parts := strings.Fields(l); len(parts) >= 3 { 113 if parts[0] != "-" { 114 if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil { 115 stats.Additions += c 116 } 117 } 118 if parts[1] != "-" { 119 if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil { 120 stats.Deletions += c 121 } 122 } 123 if _, ok := files[parts[2]]; !ok { 124 files[parts[2]] = true 125 } 126 } 127 } 128 } 129 a := make([]*CodeActivityAuthor, 0, len(authors)) 130 for _, v := range authors { 131 a = append(a, v) 132 } 133 // Sort authors descending depending on commit count 134 sort.Slice(a, func(i, j int) bool { 135 return a[i].Commits > a[j].Commits 136 }) 137 stats.AuthorCount = int64(len(authors)) 138 stats.ChangedFiles = int64(len(files)) 139 stats.Authors = a 140 _ = stdoutReader.Close() 141 return nil 142 }, 143 }) 144 if err != nil { 145 return nil, fmt.Errorf("Failed to get GetCodeActivityStats for repository.\nError: %w\nStderr: %s", err, stderr) 146 } 147 148 return stats, nil 149 }