Skip to content

Commit dd732dd

Browse files
committed
Create top user by commit count endpoint
1 parent 1a6342a commit dd732dd

File tree

3 files changed

+172
-61
lines changed

3 files changed

+172
-61
lines changed

models/repo_activity.go

Lines changed: 133 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package models
77
import (
88
"bufio"
99
"fmt"
10+
"sort"
1011
"strconv"
1112
"strings"
1213
"time"
@@ -16,13 +17,23 @@ import (
1617
"github.com/go-xorm/xorm"
1718
)
1819

20+
// ActivityAuthorData represents statistical git commit count data
21+
type ActivityAuthorData struct {
22+
Name string `json:"name"`
23+
Login string `json:"login"`
24+
AvatarLink string `json:"avatar_link"`
25+
Commits int64 `json:"commits"`
26+
}
27+
28+
// CodeActivityStats represents git statistics data
1929
type CodeActivityStats struct {
2030
AuthorCount int64
2131
CommitCount int64
2232
ChangedFiles int64
2333
Additions int64
2434
Deletions int64
2535
CommitCountInAllBranches int64
36+
Authors map[string]int64
2637
}
2738

2839
// ActivityStats represets issue and pull request information.
@@ -63,13 +74,22 @@ func GetActivityStats(repo *Repository, timeFrom time.Time, releases, issues, pr
6374
return nil, fmt.Errorf("FillUnresolvedIssues: %v", err)
6475
}
6576
if code {
66-
if err := stats.Code.FillFromGit(repo, timeFrom); err != nil {
77+
if err := stats.Code.FillFromGit(repo, timeFrom, false); err != nil {
6778
return nil, fmt.Errorf("FillFromGit: %v", err)
6879
}
6980
}
7081
return stats, nil
7182
}
7283

84+
// GetActivityStatsAuthors returns stats for git commits for all branches
85+
func GetActivityStatsAuthors(repo *Repository, timeFrom time.Time) (*CodeActivityStats, error) {
86+
code := &CodeActivityStats{}
87+
if err := code.FillFromGit(repo, timeFrom, true); err != nil {
88+
return nil, fmt.Errorf("FillFromGit: %v", err)
89+
}
90+
return code, nil
91+
}
92+
7393
// ActivePRCount returns total active pull request count
7494
func (stats *ActivityStats) ActivePRCount() int {
7595
return stats.OpenedPRCount() + stats.MergedPRCount()
@@ -291,81 +311,133 @@ func releasesForActivityStatement(repoID int64, fromTime time.Time) *xorm.Sessio
291311
}
292312

293313
// FillFromGit returns code statistics for acitivity page
294-
func (stats *CodeActivityStats) FillFromGit(repo *Repository, fromTime time.Time) error {
314+
func (stats *CodeActivityStats) FillFromGit(repo *Repository, fromTime time.Time, allBranches bool) error {
295315
gitPath := repo.RepoPath()
296316
since := fromTime.Format(time.RFC3339)
297317

298-
if stdout, stderr, err := process.GetManager().ExecDir(-1, gitPath,
318+
stdout, stderr, err := process.GetManager().ExecDir(-1, gitPath,
299319
fmt.Sprintf("FillFromGit.RevList (git rev-list): %s", gitPath),
300-
"git", "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)); err != nil {
320+
"git", "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since))
321+
if err != nil {
301322
return fmt.Errorf("git rev-list --count --branch [%s]: %s", gitPath, stderr)
323+
}
324+
325+
c, err := strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
326+
if err != nil {
327+
return err
328+
}
329+
stats.CommitCountInAllBranches = c
330+
331+
args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}
332+
if allBranches {
333+
args = append(args, "--branches=*")
302334
} else {
303-
if c, err := strconv.ParseInt(strings.TrimSpace(stdout), 10, 64); err != nil {
304-
return err
305-
} else {
306-
stats.CommitCountInAllBranches = c
307-
}
335+
args = append(args, "--first-parent", repo.DefaultBranch)
308336
}
309337

310-
if stdout, stderr, err := process.GetManager().ExecDir(-1, gitPath,
338+
stdout, stderr, err = process.GetManager().ExecDir(-1, gitPath,
311339
fmt.Sprintf("FillFromGit.RevList (git rev-list): %s", gitPath),
312-
"git", "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%an%n%ae%n", "--first-parent", "--date=iso", fmt.Sprintf("--since='%s'", since), repo.DefaultBranch); err != nil {
313-
return fmt.Errorf("git log --numstat --first-parent [%s -> %s]: %s", repo.DefaultBranch, gitPath, stderr)
314-
} else {
315-
scanner := bufio.NewScanner(strings.NewReader(stdout))
316-
scanner.Split(bufio.ScanLines)
317-
stats.CommitCount = 0
318-
stats.Additions = 0
319-
stats.Deletions = 0
320-
authors := make(map[string]int64)
321-
files := make(map[string]bool)
322-
p := 0
323-
for scanner.Scan() {
324-
l := strings.TrimSpace(scanner.Text())
325-
if l == "---" {
326-
p = 1
327-
} else if p == 0 {
328-
continue
329-
} else {
330-
p++
331-
}
332-
if p > 4 && len(l) == 0 {
333-
continue
334-
}
335-
switch p {
336-
case 1: // Seperator
337-
case 2: // Commit sha-1
338-
stats.CommitCount++
339-
case 3: // Author
340-
//fmt.Println("Author: " + l)
341-
case 4: // E-mail
342-
email := strings.ToLower(l)
343-
i := authors[email]
344-
authors[email] = i + 1
345-
default: // Changed fileB
346-
fmt.Println("L:" + l)
347-
if parts := strings.Fields(l); len(parts) >= 3 {
348-
if parts[0] != "-" {
349-
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
350-
stats.Additions += c
351-
}
352-
}
353-
if parts[1] != "-" {
354-
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
355-
stats.Deletions += c
356-
}
340+
"git", args...)
341+
if err != nil {
342+
return fmt.Errorf("git log --numstat [%s]: %s", gitPath, stderr)
343+
}
344+
345+
scanner := bufio.NewScanner(strings.NewReader(stdout))
346+
scanner.Split(bufio.ScanLines)
347+
stats.CommitCount = 0
348+
stats.Additions = 0
349+
stats.Deletions = 0
350+
authors := make(map[string]int64)
351+
files := make(map[string]bool)
352+
p := 0
353+
for scanner.Scan() {
354+
l := strings.TrimSpace(scanner.Text())
355+
if l == "---" {
356+
p = 1
357+
} else if p == 0 {
358+
continue
359+
} else {
360+
p++
361+
}
362+
if p > 4 && len(l) == 0 {
363+
continue
364+
}
365+
switch p {
366+
case 1: // Seperator
367+
case 2: // Commit sha-1
368+
stats.CommitCount++
369+
case 3: // Author
370+
//fmt.Println("Author: " + l)
371+
case 4: // E-mail
372+
email := strings.ToLower(l)
373+
i := authors[email]
374+
authors[email] = i + 1
375+
default: // Changed file
376+
if parts := strings.Fields(l); len(parts) >= 3 {
377+
if parts[0] != "-" {
378+
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
379+
stats.Additions += c
357380
}
358-
if _, ok := files[parts[2]]; !ok {
359-
files[parts[2]] = true
381+
}
382+
if parts[1] != "-" {
383+
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
384+
stats.Deletions += c
360385
}
361-
} else {
362-
fmt.Println("err fields")
386+
}
387+
if _, ok := files[parts[2]]; !ok {
388+
files[parts[2]] = true
363389
}
364390
}
365391
}
366-
stats.AuthorCount = int64(len(authors))
367-
stats.ChangedFiles = int64(len(files))
368392
}
393+
stats.AuthorCount = int64(len(authors))
394+
stats.ChangedFiles = int64(len(files))
395+
stats.Authors = authors
369396

370397
return nil
371398
}
399+
400+
// GetTopAuthors get top users with most commit count based on already loaded data from git
401+
func (stats *CodeActivityStats) GetTopAuthors(count int) ([]*ActivityAuthorData, error) {
402+
if stats.Authors == nil {
403+
return nil, nil
404+
}
405+
users := make(map[int64]*ActivityAuthorData)
406+
for k, v := range stats.Authors {
407+
if len(k) == 0 {
408+
continue
409+
}
410+
u, err := GetUserByEmail(k)
411+
if u == nil || IsErrUserNotExist(err) {
412+
continue
413+
}
414+
if err != nil {
415+
return nil, err
416+
}
417+
if user, ok := users[u.ID]; !ok {
418+
users[u.ID] = &ActivityAuthorData{
419+
Name: u.DisplayName(),
420+
Login: u.LowerName,
421+
AvatarLink: u.AvatarLink(),
422+
Commits: v,
423+
}
424+
} else {
425+
user.Commits += v
426+
}
427+
}
428+
v := make([]*ActivityAuthorData, 0)
429+
for _, u := range users {
430+
v = append(v, u)
431+
}
432+
433+
sort.Slice(v[:], func(i, j int) bool {
434+
return v[i].Commits < v[j].Commits
435+
})
436+
437+
cnt := count
438+
if cnt > len(v) {
439+
cnt = len(v)
440+
}
441+
442+
return v[:cnt], nil
443+
}

routers/repo/activity.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,37 @@ func Activity(ctx *context.Context) {
5555

5656
ctx.HTML(200, tplActivity)
5757
}
58+
59+
// ActivityAuthors renders JSON with top commit authors for given time period over all branches
60+
func ActivityAuthors(ctx *context.Context) {
61+
timeUntil := time.Now()
62+
var timeFrom time.Time
63+
64+
switch ctx.Params("period") {
65+
case "daily":
66+
timeFrom = timeUntil.Add(-time.Hour * 24)
67+
case "halfweekly":
68+
timeFrom = timeUntil.Add(-time.Hour * 72)
69+
case "weekly":
70+
timeFrom = timeUntil.Add(-time.Hour * 168)
71+
case "monthly":
72+
timeFrom = timeUntil.AddDate(0, -1, 0)
73+
default:
74+
timeFrom = timeUntil.Add(-time.Hour * 168)
75+
}
76+
77+
var err error
78+
code, err := models.GetActivityStatsAuthors(ctx.Repo.Repository, timeFrom)
79+
if err != nil {
80+
ctx.ServerError("GetActivityStatsAuthors", err)
81+
return
82+
}
83+
84+
authors, err := code.GetTopAuthors(10)
85+
if err != nil {
86+
ctx.ServerError("GetTopAuthors", err)
87+
return
88+
}
89+
90+
ctx.JSON(200, authors)
91+
}

routers/routes/routes.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,11 @@ func RegisterRoutes(m *macaron.Macaron) {
719719
m.Get("/:period", repo.Activity)
720720
}, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypePullRequests, models.UnitTypeIssues, models.UnitTypeReleases))
721721

722+
m.Group("/activity_author_data", func() {
723+
m.Get("", repo.ActivityAuthors)
724+
m.Get("/:period", repo.ActivityAuthors)
725+
}, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypeCode))
726+
722727
m.Get("/archive/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.Download)
723728

724729
m.Group("/branches", func() {

0 commit comments

Comments
 (0)