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.

repo_language_stats.go 2.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  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 git
  5. import (
  6. "bytes"
  7. "io"
  8. "io/ioutil"
  9. "math"
  10. "code.gitea.io/gitea/modules/analyze"
  11. "github.com/src-d/enry/v2"
  12. "gopkg.in/src-d/go-git.v4"
  13. "gopkg.in/src-d/go-git.v4/plumbing"
  14. "gopkg.in/src-d/go-git.v4/plumbing/object"
  15. )
  16. const fileSizeLimit int64 = 16 * 1024 * 1024
  17. // GetLanguageStats calculates language stats for git repository at specified commit
  18. func (repo *Repository) GetLanguageStats(commitID string) (map[string]float32, error) {
  19. r, err := git.PlainOpen(repo.Path)
  20. if err != nil {
  21. return nil, err
  22. }
  23. rev, err := r.ResolveRevision(plumbing.Revision(commitID))
  24. if err != nil {
  25. return nil, err
  26. }
  27. commit, err := r.CommitObject(*rev)
  28. if err != nil {
  29. return nil, err
  30. }
  31. tree, err := commit.Tree()
  32. if err != nil {
  33. return nil, err
  34. }
  35. sizes := make(map[string]int64)
  36. var total int64
  37. err = tree.Files().ForEach(func(f *object.File) error {
  38. if enry.IsVendor(f.Name) || enry.IsDotFile(f.Name) ||
  39. enry.IsDocumentation(f.Name) || enry.IsConfiguration(f.Name) {
  40. return nil
  41. }
  42. // TODO: Use .gitattributes file for linguist overrides
  43. language := analyze.GetCodeLanguageWithCallback(f.Name, func() ([]byte, error) {
  44. return readFile(f, fileSizeLimit)
  45. })
  46. if language == enry.OtherLanguage || language == "" {
  47. return nil
  48. }
  49. sizes[language] += f.Size
  50. total += f.Size
  51. return nil
  52. })
  53. if err != nil {
  54. return nil, err
  55. }
  56. stats := make(map[string]float32)
  57. var otherPerc float32 = 100
  58. for language, size := range sizes {
  59. perc := float32(math.Round(float64(size)/float64(total)*1000) / 10)
  60. if perc <= 0.1 {
  61. continue
  62. }
  63. otherPerc -= perc
  64. stats[language] = perc
  65. }
  66. otherPerc = float32(math.Round(float64(otherPerc)*10) / 10)
  67. if otherPerc > 0 {
  68. stats["other"] = otherPerc
  69. }
  70. return stats, nil
  71. }
  72. func readFile(f *object.File, limit int64) ([]byte, error) {
  73. r, err := f.Reader()
  74. if err != nil {
  75. return nil, err
  76. }
  77. defer r.Close()
  78. if limit <= 0 {
  79. return ioutil.ReadAll(r)
  80. }
  81. size := f.Size
  82. if limit > 0 && size > limit {
  83. size = limit
  84. }
  85. buf := bytes.NewBuffer(nil)
  86. buf.Grow(int(size))
  87. _, err = io.Copy(buf, io.LimitReader(r, limit))
  88. return buf.Bytes(), err
  89. }