You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
5.3 KiB

  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "math"
  7. "strings"
  8. "code.gitea.io/gitea/modules/timeutil"
  9. "github.com/go-enry/go-enry/v2"
  10. )
  11. // LanguageStat describes language statistics of a repository
  12. type LanguageStat struct {
  13. ID int64 `xorm:"pk autoincr"`
  14. RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  15. CommitID string
  16. IsPrimary bool
  17. Language string `xorm:"VARCHAR(50) UNIQUE(s) INDEX NOT NULL"`
  18. Percentage float32 `xorm:"-"`
  19. Size int64 `xorm:"NOT NULL DEFAULT 0"`
  20. Color string `xorm:"-"`
  21. CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
  22. }
  23. // LanguageStatList defines a list of language statistics
  24. type LanguageStatList []*LanguageStat
  25. func (stats LanguageStatList) loadAttributes() {
  26. for i := range stats {
  27. stats[i].Color = enry.GetColor(stats[i].Language)
  28. }
  29. }
  30. func (stats LanguageStatList) getLanguagePercentages() map[string]float32 {
  31. langPerc := make(map[string]float32)
  32. var otherPerc float32 = 100
  33. var total int64
  34. for _, stat := range stats {
  35. total += stat.Size
  36. }
  37. if total > 0 {
  38. for _, stat := range stats {
  39. perc := float32(math.Round(float64(stat.Size)/float64(total)*1000) / 10)
  40. if perc <= 0.1 {
  41. continue
  42. }
  43. otherPerc -= perc
  44. langPerc[stat.Language] = perc
  45. }
  46. otherPerc = float32(math.Round(float64(otherPerc)*10) / 10)
  47. }
  48. if otherPerc > 0 {
  49. langPerc["other"] = otherPerc
  50. }
  51. return langPerc
  52. }
  53. func (repo *Repository) getLanguageStats(e Engine) (LanguageStatList, error) {
  54. stats := make(LanguageStatList, 0, 6)
  55. if err := e.Where("`repo_id` = ?", repo.ID).Desc("`size`").Find(&stats); err != nil {
  56. return nil, err
  57. }
  58. return stats, nil
  59. }
  60. // GetLanguageStats returns the language statistics for a repository
  61. func (repo *Repository) GetLanguageStats() (LanguageStatList, error) {
  62. return repo.getLanguageStats(x)
  63. }
  64. // GetTopLanguageStats returns the top language statistics for a repository
  65. func (repo *Repository) GetTopLanguageStats(limit int) (LanguageStatList, error) {
  66. stats, err := repo.getLanguageStats(x)
  67. if err != nil {
  68. return nil, err
  69. }
  70. perc := stats.getLanguagePercentages()
  71. topstats := make(LanguageStatList, 0, limit)
  72. var other float32
  73. for i := range stats {
  74. if _, ok := perc[stats[i].Language]; !ok {
  75. continue
  76. }
  77. if stats[i].Language == "other" || len(topstats) >= limit {
  78. other += perc[stats[i].Language]
  79. continue
  80. }
  81. stats[i].Percentage = perc[stats[i].Language]
  82. topstats = append(topstats, stats[i])
  83. }
  84. if other > 0 {
  85. topstats = append(topstats, &LanguageStat{
  86. RepoID: repo.ID,
  87. Language: "other",
  88. Color: "#cccccc",
  89. Percentage: float32(math.Round(float64(other)*10) / 10),
  90. })
  91. }
  92. topstats.loadAttributes()
  93. return topstats, nil
  94. }
  95. // UpdateLanguageStats updates the language statistics for repository
  96. func (repo *Repository) UpdateLanguageStats(commitID string, stats map[string]int64) error {
  97. sess := x.NewSession()
  98. if err := sess.Begin(); err != nil {
  99. return err
  100. }
  101. defer sess.Close()
  102. oldstats, err := repo.getLanguageStats(sess)
  103. if err != nil {
  104. return err
  105. }
  106. var topLang string
  107. var s int64
  108. for lang, size := range stats {
  109. if size > s {
  110. s = size
  111. topLang = strings.ToLower(lang)
  112. }
  113. }
  114. for lang, size := range stats {
  115. upd := false
  116. llang := strings.ToLower(lang)
  117. for _, s := range oldstats {
  118. // Update already existing language
  119. if strings.ToLower(s.Language) == llang {
  120. s.CommitID = commitID
  121. s.IsPrimary = llang == topLang
  122. s.Size = size
  123. if _, err := sess.ID(s.ID).Cols("`commit_id`", "`size`", "`is_primary`").Update(s); err != nil {
  124. return err
  125. }
  126. upd = true
  127. break
  128. }
  129. }
  130. // Insert new language
  131. if !upd {
  132. if _, err := sess.Insert(&LanguageStat{
  133. RepoID: repo.ID,
  134. CommitID: commitID,
  135. IsPrimary: llang == topLang,
  136. Language: lang,
  137. Size: size,
  138. }); err != nil {
  139. return err
  140. }
  141. }
  142. }
  143. // Delete old languages
  144. statsToDelete := make([]int64, 0, len(oldstats))
  145. for _, s := range oldstats {
  146. if s.CommitID != commitID {
  147. statsToDelete = append(statsToDelete, s.ID)
  148. }
  149. }
  150. if len(statsToDelete) > 0 {
  151. if _, err := sess.In("`id`", statsToDelete).Delete(&LanguageStat{}); err != nil {
  152. return err
  153. }
  154. }
  155. // Update indexer status
  156. if err = repo.updateIndexerStatus(sess, RepoIndexerTypeStats, commitID); err != nil {
  157. return err
  158. }
  159. return sess.Commit()
  160. }
  161. // CopyLanguageStat Copy originalRepo language stat information to destRepo (use for forked repo)
  162. func CopyLanguageStat(originalRepo, destRepo *Repository) error {
  163. sess := x.NewSession()
  164. defer sess.Close()
  165. if err := sess.Begin(); err != nil {
  166. return err
  167. }
  168. RepoLang := make(LanguageStatList, 0, 6)
  169. if err := sess.Where("`repo_id` = ?", originalRepo.ID).Desc("`size`").Find(&RepoLang); err != nil {
  170. return err
  171. }
  172. if len(RepoLang) > 0 {
  173. for i := range RepoLang {
  174. RepoLang[i].ID = 0
  175. RepoLang[i].RepoID = destRepo.ID
  176. RepoLang[i].CreatedUnix = timeutil.TimeStampNow()
  177. }
  178. //update destRepo's indexer status
  179. tmpCommitID := RepoLang[0].CommitID
  180. if err := destRepo.updateIndexerStatus(sess, RepoIndexerTypeStats, tmpCommitID); err != nil {
  181. return err
  182. }
  183. if _, err := sess.Insert(&RepoLang); err != nil {
  184. return err
  185. }
  186. }
  187. return sess.Commit()
  188. }