2020-02-11 12:34:17 +03:00
|
|
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a MIT-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2021-12-10 04:27:50 +03:00
|
|
|
package repo
|
2020-02-11 12:34:17 +03:00
|
|
|
|
|
|
|
import (
|
2022-05-20 17:08:52 +03:00
|
|
|
"context"
|
2020-02-11 12:34:17 +03:00
|
|
|
"math"
|
|
|
|
"strings"
|
|
|
|
|
2021-09-19 14:49:59 +03:00
|
|
|
"code.gitea.io/gitea/models/db"
|
2020-02-11 12:34:17 +03:00
|
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
|
|
|
2020-04-15 20:40:39 +03:00
|
|
|
"github.com/go-enry/go-enry/v2"
|
2020-02-11 12:34:17 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// LanguageStat describes language statistics of a repository
|
|
|
|
type LanguageStat struct {
|
|
|
|
ID int64 `xorm:"pk autoincr"`
|
|
|
|
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
|
|
|
CommitID string
|
|
|
|
IsPrimary bool
|
2020-08-04 16:54:29 +03:00
|
|
|
Language string `xorm:"VARCHAR(50) UNIQUE(s) INDEX NOT NULL"`
|
2020-05-30 10:46:15 +03:00
|
|
|
Percentage float32 `xorm:"-"`
|
|
|
|
Size int64 `xorm:"NOT NULL DEFAULT 0"`
|
2020-02-11 12:34:17 +03:00
|
|
|
Color string `xorm:"-"`
|
|
|
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
|
|
|
}
|
|
|
|
|
2021-09-19 14:49:59 +03:00
|
|
|
func init() {
|
|
|
|
db.RegisterModel(new(LanguageStat))
|
|
|
|
}
|
|
|
|
|
2020-02-11 12:34:17 +03:00
|
|
|
// LanguageStatList defines a list of language statistics
|
|
|
|
type LanguageStatList []*LanguageStat
|
|
|
|
|
2021-12-10 04:27:50 +03:00
|
|
|
// LoadAttributes loads attributes
|
|
|
|
func (stats LanguageStatList) LoadAttributes() {
|
2020-02-11 12:34:17 +03:00
|
|
|
for i := range stats {
|
|
|
|
stats[i].Color = enry.GetColor(stats[i].Language)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-30 10:46:15 +03:00
|
|
|
func (stats LanguageStatList) getLanguagePercentages() map[string]float32 {
|
|
|
|
langPerc := make(map[string]float32)
|
|
|
|
var otherPerc float32 = 100
|
|
|
|
var total int64
|
2020-05-31 01:58:55 +03:00
|
|
|
|
2020-05-30 10:46:15 +03:00
|
|
|
for _, stat := range stats {
|
|
|
|
total += stat.Size
|
|
|
|
}
|
|
|
|
if total > 0 {
|
|
|
|
for _, stat := range stats {
|
|
|
|
perc := float32(math.Round(float64(stat.Size)/float64(total)*1000) / 10)
|
|
|
|
if perc <= 0.1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
otherPerc -= perc
|
|
|
|
langPerc[stat.Language] = perc
|
|
|
|
}
|
|
|
|
otherPerc = float32(math.Round(float64(otherPerc)*10) / 10)
|
|
|
|
}
|
|
|
|
if otherPerc > 0 {
|
|
|
|
langPerc["other"] = otherPerc
|
|
|
|
}
|
|
|
|
return langPerc
|
|
|
|
}
|
|
|
|
|
2022-05-20 17:08:52 +03:00
|
|
|
// GetLanguageStats returns the language statistics for a repository
|
|
|
|
func GetLanguageStats(ctx context.Context, repo *Repository) (LanguageStatList, error) {
|
2020-02-11 12:34:17 +03:00
|
|
|
stats := make(LanguageStatList, 0, 6)
|
2022-05-20 17:08:52 +03:00
|
|
|
if err := db.GetEngine(ctx).Where("`repo_id` = ?", repo.ID).Desc("`size`").Find(&stats); err != nil {
|
2020-02-11 12:34:17 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return stats, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTopLanguageStats returns the top language statistics for a repository
|
2021-12-10 04:27:50 +03:00
|
|
|
func GetTopLanguageStats(repo *Repository, limit int) (LanguageStatList, error) {
|
2022-05-20 17:08:52 +03:00
|
|
|
stats, err := GetLanguageStats(db.DefaultContext, repo)
|
2020-02-11 12:34:17 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-30 10:46:15 +03:00
|
|
|
perc := stats.getLanguagePercentages()
|
2020-02-11 12:34:17 +03:00
|
|
|
topstats := make(LanguageStatList, 0, limit)
|
|
|
|
var other float32
|
|
|
|
for i := range stats {
|
2020-05-30 10:46:15 +03:00
|
|
|
if _, ok := perc[stats[i].Language]; !ok {
|
|
|
|
continue
|
|
|
|
}
|
2020-02-11 12:34:17 +03:00
|
|
|
if stats[i].Language == "other" || len(topstats) >= limit {
|
2020-05-30 10:46:15 +03:00
|
|
|
other += perc[stats[i].Language]
|
2020-02-11 12:34:17 +03:00
|
|
|
continue
|
|
|
|
}
|
2020-05-30 10:46:15 +03:00
|
|
|
stats[i].Percentage = perc[stats[i].Language]
|
2020-02-11 12:34:17 +03:00
|
|
|
topstats = append(topstats, stats[i])
|
|
|
|
}
|
|
|
|
if other > 0 {
|
|
|
|
topstats = append(topstats, &LanguageStat{
|
|
|
|
RepoID: repo.ID,
|
|
|
|
Language: "other",
|
|
|
|
Color: "#cccccc",
|
|
|
|
Percentage: float32(math.Round(float64(other)*10) / 10),
|
|
|
|
})
|
|
|
|
}
|
2021-12-10 04:27:50 +03:00
|
|
|
topstats.LoadAttributes()
|
2020-02-11 12:34:17 +03:00
|
|
|
return topstats, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateLanguageStats updates the language statistics for repository
|
2021-12-10 04:27:50 +03:00
|
|
|
func UpdateLanguageStats(repo *Repository, commitID string, stats map[string]int64) error {
|
2022-11-12 23:18:50 +03:00
|
|
|
ctx, committer, err := db.TxContext(db.DefaultContext)
|
2021-11-21 18:41:00 +03:00
|
|
|
if err != nil {
|
2020-02-11 12:34:17 +03:00
|
|
|
return err
|
|
|
|
}
|
2021-11-21 18:41:00 +03:00
|
|
|
defer committer.Close()
|
|
|
|
sess := db.GetEngine(ctx)
|
2020-02-11 12:34:17 +03:00
|
|
|
|
2022-05-20 17:08:52 +03:00
|
|
|
oldstats, err := GetLanguageStats(ctx, repo)
|
2020-02-11 12:34:17 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var topLang string
|
2020-05-30 10:46:15 +03:00
|
|
|
var s int64
|
|
|
|
for lang, size := range stats {
|
|
|
|
if size > s {
|
|
|
|
s = size
|
2020-02-11 12:34:17 +03:00
|
|
|
topLang = strings.ToLower(lang)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-30 10:46:15 +03:00
|
|
|
for lang, size := range stats {
|
2020-02-11 12:34:17 +03:00
|
|
|
upd := false
|
|
|
|
llang := strings.ToLower(lang)
|
|
|
|
for _, s := range oldstats {
|
|
|
|
// Update already existing language
|
|
|
|
if strings.ToLower(s.Language) == llang {
|
|
|
|
s.CommitID = commitID
|
|
|
|
s.IsPrimary = llang == topLang
|
2020-05-30 10:46:15 +03:00
|
|
|
s.Size = size
|
|
|
|
if _, err := sess.ID(s.ID).Cols("`commit_id`", "`size`", "`is_primary`").Update(s); err != nil {
|
2020-02-11 12:34:17 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
upd = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Insert new language
|
|
|
|
if !upd {
|
2022-05-20 17:08:52 +03:00
|
|
|
if err := db.Insert(ctx, &LanguageStat{
|
2020-05-30 10:46:15 +03:00
|
|
|
RepoID: repo.ID,
|
|
|
|
CommitID: commitID,
|
|
|
|
IsPrimary: llang == topLang,
|
|
|
|
Language: lang,
|
|
|
|
Size: size,
|
2020-02-11 12:34:17 +03:00
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Delete old languages
|
2020-02-14 15:42:30 +03:00
|
|
|
statsToDelete := make([]int64, 0, len(oldstats))
|
|
|
|
for _, s := range oldstats {
|
|
|
|
if s.CommitID != commitID {
|
|
|
|
statsToDelete = append(statsToDelete, s.ID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(statsToDelete) > 0 {
|
|
|
|
if _, err := sess.In("`id`", statsToDelete).Delete(&LanguageStat{}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-02-11 12:34:17 +03:00
|
|
|
}
|
|
|
|
|
2020-02-14 15:42:30 +03:00
|
|
|
// Update indexer status
|
2022-05-20 17:08:52 +03:00
|
|
|
if err = UpdateIndexerStatus(ctx, repo, RepoIndexerTypeStats, commitID); err != nil {
|
2020-02-11 12:34:17 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-11-21 18:41:00 +03:00
|
|
|
return committer.Commit()
|
2020-02-11 12:34:17 +03:00
|
|
|
}
|
2020-04-08 15:13:04 +03:00
|
|
|
|
|
|
|
// CopyLanguageStat Copy originalRepo language stat information to destRepo (use for forked repo)
|
|
|
|
func CopyLanguageStat(originalRepo, destRepo *Repository) error {
|
2022-11-12 23:18:50 +03:00
|
|
|
ctx, committer, err := db.TxContext(db.DefaultContext)
|
2021-11-21 18:41:00 +03:00
|
|
|
if err != nil {
|
2020-04-08 15:13:04 +03:00
|
|
|
return err
|
|
|
|
}
|
2021-11-21 18:41:00 +03:00
|
|
|
defer committer.Close()
|
|
|
|
|
2020-04-08 15:13:04 +03:00
|
|
|
RepoLang := make(LanguageStatList, 0, 6)
|
2022-05-20 17:08:52 +03:00
|
|
|
if err := db.GetEngine(ctx).Where("`repo_id` = ?", originalRepo.ID).Desc("`size`").Find(&RepoLang); err != nil {
|
2020-04-08 15:13:04 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(RepoLang) > 0 {
|
|
|
|
for i := range RepoLang {
|
|
|
|
RepoLang[i].ID = 0
|
|
|
|
RepoLang[i].RepoID = destRepo.ID
|
|
|
|
RepoLang[i].CreatedUnix = timeutil.TimeStampNow()
|
|
|
|
}
|
2021-03-14 21:52:12 +03:00
|
|
|
// update destRepo's indexer status
|
2020-04-08 15:13:04 +03:00
|
|
|
tmpCommitID := RepoLang[0].CommitID
|
2022-05-20 17:08:52 +03:00
|
|
|
if err := UpdateIndexerStatus(ctx, destRepo, RepoIndexerTypeStats, tmpCommitID); err != nil {
|
2020-04-08 15:13:04 +03:00
|
|
|
return err
|
|
|
|
}
|
2022-05-20 17:08:52 +03:00
|
|
|
if err := db.Insert(ctx, &RepoLang); err != nil {
|
2020-04-08 15:13:04 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2021-11-21 18:41:00 +03:00
|
|
|
return committer.Commit()
|
2020-04-08 15:13:04 +03:00
|
|
|
}
|