mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Git statistics in Activity tab (#4724)
* Initial implementation for git statistics in Activity tab * Create top user by commit count endpoint * Add UI and update src-d/go-git dependency * Add coloring * Fix typo * Move git activity stats data extraction to git module * Fix message * Add git code stats test
This commit is contained in:
		@@ -6,11 +6,22 @@ package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
 | 
			
		||||
	"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"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ActivityStats represets issue and pull request information.
 | 
			
		||||
type ActivityStats struct {
 | 
			
		||||
	OpenedPRs                   PullRequestList
 | 
			
		||||
@@ -24,32 +35,97 @@ type ActivityStats struct {
 | 
			
		||||
	UnresolvedIssues            IssueList
 | 
			
		||||
	PublishedReleases           []*Release
 | 
			
		||||
	PublishedReleaseAuthorCount int64
 | 
			
		||||
	Code                        *git.CodeActivityStats
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetActivityStats return stats for repository at given time range
 | 
			
		||||
func GetActivityStats(repoID int64, timeFrom time.Time, releases, issues, prs bool) (*ActivityStats, error) {
 | 
			
		||||
	stats := &ActivityStats{}
 | 
			
		||||
func GetActivityStats(repo *Repository, timeFrom time.Time, releases, issues, prs, code bool) (*ActivityStats, error) {
 | 
			
		||||
	stats := &ActivityStats{Code: &git.CodeActivityStats{}}
 | 
			
		||||
	if releases {
 | 
			
		||||
		if err := stats.FillReleases(repoID, timeFrom); err != nil {
 | 
			
		||||
		if err := stats.FillReleases(repo.ID, timeFrom); err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("FillReleases: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if prs {
 | 
			
		||||
		if err := stats.FillPullRequests(repoID, timeFrom); err != nil {
 | 
			
		||||
		if err := stats.FillPullRequests(repo.ID, timeFrom); err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("FillPullRequests: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if issues {
 | 
			
		||||
		if err := stats.FillIssues(repoID, timeFrom); err != nil {
 | 
			
		||||
		if err := stats.FillIssues(repo.ID, timeFrom); err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("FillIssues: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := stats.FillUnresolvedIssues(repoID, timeFrom, issues, prs); err != nil {
 | 
			
		||||
	if err := stats.FillUnresolvedIssues(repo.ID, timeFrom, issues, prs); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("FillUnresolvedIssues: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if code {
 | 
			
		||||
		gitRepo, err := git.OpenRepository(repo.RepoPath())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("OpenRepository: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		code, err := gitRepo.GetCodeActivityStats(timeFrom, repo.DefaultBranch)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("FillFromGit: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		stats.Code = code
 | 
			
		||||
	}
 | 
			
		||||
	return stats, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetActivityStatsTopAuthors returns top author stats for git commits for all branches
 | 
			
		||||
func GetActivityStatsTopAuthors(repo *Repository, timeFrom time.Time, count int) ([]*ActivityAuthorData, error) {
 | 
			
		||||
	gitRepo, err := git.OpenRepository(repo.RepoPath())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("OpenRepository: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	code, err := gitRepo.GetCodeActivityStats(timeFrom, "")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("FillFromGit: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if code.Authors == nil {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	users := make(map[int64]*ActivityAuthorData)
 | 
			
		||||
	for k, v := range code.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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ActivePRCount returns total active pull request count
 | 
			
		||||
func (stats *ActivityStats) ActivePRCount() int {
 | 
			
		||||
	return stats.OpenedPRCount() + stats.MergedPRCount()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user