Skip to content

Commit

Permalink
Create top user by commit count endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
lafriks committed Feb 23, 2019
1 parent 1a6342a commit dd732dd
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 61 deletions.
194 changes: 133 additions & 61 deletions models/repo_activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package models
import (
"bufio"
"fmt"
"sort"
"strconv"
"strings"
"time"
Expand All @@ -16,13 +17,23 @@ import (
"github.com/go-xorm/xorm"
)

// ActivityAuthorData represents statistical git commit count data
type ActivityAuthorData struct {
Name string `json:"name"`
Login string `json:"login"`
AvatarLink string `json:"avatar_link"`
Commits int64 `json:"commits"`
}

// CodeActivityStats represents git statistics data
type CodeActivityStats struct {
AuthorCount int64
CommitCount int64
ChangedFiles int64
Additions int64
Deletions int64
CommitCountInAllBranches int64
Authors map[string]int64
}

// ActivityStats represets issue and pull request information.
Expand Down Expand Up @@ -63,13 +74,22 @@ func GetActivityStats(repo *Repository, timeFrom time.Time, releases, issues, pr
return nil, fmt.Errorf("FillUnresolvedIssues: %v", err)
}
if code {
if err := stats.Code.FillFromGit(repo, timeFrom); err != nil {
if err := stats.Code.FillFromGit(repo, timeFrom, false); err != nil {
return nil, fmt.Errorf("FillFromGit: %v", err)
}
}
return stats, nil
}

// GetActivityStatsAuthors returns stats for git commits for all branches
func GetActivityStatsAuthors(repo *Repository, timeFrom time.Time) (*CodeActivityStats, error) {
code := &CodeActivityStats{}
if err := code.FillFromGit(repo, timeFrom, true); err != nil {
return nil, fmt.Errorf("FillFromGit: %v", err)
}
return code, nil
}

// ActivePRCount returns total active pull request count
func (stats *ActivityStats) ActivePRCount() int {
return stats.OpenedPRCount() + stats.MergedPRCount()
Expand Down Expand Up @@ -291,81 +311,133 @@ func releasesForActivityStatement(repoID int64, fromTime time.Time) *xorm.Sessio
}

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

if stdout, stderr, err := process.GetManager().ExecDir(-1, gitPath,
stdout, stderr, err := process.GetManager().ExecDir(-1, gitPath,
fmt.Sprintf("FillFromGit.RevList (git rev-list): %s", gitPath),
"git", "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since)); err != nil {
"git", "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso", fmt.Sprintf("--since='%s'", since))
if err != nil {
return fmt.Errorf("git rev-list --count --branch [%s]: %s", gitPath, stderr)
}

c, err := strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
if err != nil {
return err
}
stats.CommitCountInAllBranches = c

args := []string{"log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%an%n%ae%n", "--date=iso", fmt.Sprintf("--since='%s'", since)}
if allBranches {
args = append(args, "--branches=*")
} else {
if c, err := strconv.ParseInt(strings.TrimSpace(stdout), 10, 64); err != nil {
return err
} else {
stats.CommitCountInAllBranches = c
}
args = append(args, "--first-parent", repo.DefaultBranch)
}

if stdout, stderr, err := process.GetManager().ExecDir(-1, gitPath,
stdout, stderr, err = process.GetManager().ExecDir(-1, gitPath,
fmt.Sprintf("FillFromGit.RevList (git rev-list): %s", gitPath),
"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 {
return fmt.Errorf("git log --numstat --first-parent [%s -> %s]: %s", repo.DefaultBranch, gitPath, stderr)
} else {
scanner := bufio.NewScanner(strings.NewReader(stdout))
scanner.Split(bufio.ScanLines)
stats.CommitCount = 0
stats.Additions = 0
stats.Deletions = 0
authors := make(map[string]int64)
files := make(map[string]bool)
p := 0
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Seperator
case 2: // Commit sha-1
stats.CommitCount++
case 3: // Author
//fmt.Println("Author: " + l)
case 4: // E-mail
email := strings.ToLower(l)
i := authors[email]
authors[email] = i + 1
default: // Changed fileB
fmt.Println("L:" + l)
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
stats.Additions += c
}
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
stats.Deletions += c
}
"git", args...)
if err != nil {
return fmt.Errorf("git log --numstat [%s]: %s", gitPath, stderr)
}

scanner := bufio.NewScanner(strings.NewReader(stdout))
scanner.Split(bufio.ScanLines)
stats.CommitCount = 0
stats.Additions = 0
stats.Deletions = 0
authors := make(map[string]int64)
files := make(map[string]bool)
p := 0
for scanner.Scan() {
l := strings.TrimSpace(scanner.Text())
if l == "---" {
p = 1
} else if p == 0 {
continue
} else {
p++
}
if p > 4 && len(l) == 0 {
continue
}
switch p {
case 1: // Seperator
case 2: // Commit sha-1
stats.CommitCount++
case 3: // Author
//fmt.Println("Author: " + l)
case 4: // E-mail
email := strings.ToLower(l)
i := authors[email]
authors[email] = i + 1
default: // Changed file
if parts := strings.Fields(l); len(parts) >= 3 {
if parts[0] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64); err == nil {
stats.Additions += c
}
if _, ok := files[parts[2]]; !ok {
files[parts[2]] = true
}
if parts[1] != "-" {
if c, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64); err == nil {
stats.Deletions += c
}
} else {
fmt.Println("err fields")
}
if _, ok := files[parts[2]]; !ok {
files[parts[2]] = true
}
}
}
stats.AuthorCount = int64(len(authors))
stats.ChangedFiles = int64(len(files))
}
stats.AuthorCount = int64(len(authors))
stats.ChangedFiles = int64(len(files))
stats.Authors = authors

return nil
}

// GetTopAuthors get top users with most commit count based on already loaded data from git
func (stats *CodeActivityStats) GetTopAuthors(count int) ([]*ActivityAuthorData, error) {
if stats.Authors == nil {
return nil, nil
}
users := make(map[int64]*ActivityAuthorData)
for k, v := range stats.Authors {
if len(k) == 0 {
continue
}
u, err := GetUserByEmail(k)
if u == nil || IsErrUserNotExist(err) {
continue
}
if err != nil {
return nil, err
}
if user, ok := users[u.ID]; !ok {
users[u.ID] = &ActivityAuthorData{
Name: u.DisplayName(),
Login: u.LowerName,
AvatarLink: u.AvatarLink(),
Commits: v,
}
} else {
user.Commits += v
}
}
v := make([]*ActivityAuthorData, 0)
for _, u := range users {
v = append(v, u)
}

sort.Slice(v[:], func(i, j int) bool {
return v[i].Commits < v[j].Commits
})

cnt := count
if cnt > len(v) {
cnt = len(v)
}

return v[:cnt], nil
}
34 changes: 34 additions & 0 deletions routers/repo/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,37 @@ func Activity(ctx *context.Context) {

ctx.HTML(200, tplActivity)
}

// ActivityAuthors renders JSON with top commit authors for given time period over all branches
func ActivityAuthors(ctx *context.Context) {
timeUntil := time.Now()
var timeFrom time.Time

switch ctx.Params("period") {
case "daily":
timeFrom = timeUntil.Add(-time.Hour * 24)
case "halfweekly":
timeFrom = timeUntil.Add(-time.Hour * 72)
case "weekly":
timeFrom = timeUntil.Add(-time.Hour * 168)
case "monthly":
timeFrom = timeUntil.AddDate(0, -1, 0)
default:
timeFrom = timeUntil.Add(-time.Hour * 168)
}

var err error
code, err := models.GetActivityStatsAuthors(ctx.Repo.Repository, timeFrom)
if err != nil {
ctx.ServerError("GetActivityStatsAuthors", err)
return
}

authors, err := code.GetTopAuthors(10)
if err != nil {
ctx.ServerError("GetTopAuthors", err)
return
}

ctx.JSON(200, authors)
}
5 changes: 5 additions & 0 deletions routers/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/:period", repo.Activity)
}, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypePullRequests, models.UnitTypeIssues, models.UnitTypeReleases))

m.Group("/activity_author_data", func() {
m.Get("", repo.ActivityAuthors)
m.Get("/:period", repo.ActivityAuthors)
}, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypeCode))

m.Get("/archive/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.Download)

m.Group("/branches", func() {
Expand Down

0 comments on commit dd732dd

Please sign in to comment.