Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repository
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "time"
  9. git_model "code.gitea.io/gitea/models/git"
  10. repo_model "code.gitea.io/gitea/models/repo"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/gitrepo"
  13. "code.gitea.io/gitea/modules/lfs"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/timeutil"
  17. )
  18. // GarbageCollectLFSMetaObjectsOptions provides options for GarbageCollectLFSMetaObjects function
  19. type GarbageCollectLFSMetaObjectsOptions struct {
  20. LogDetail func(format string, v ...any)
  21. AutoFix bool
  22. OlderThan time.Time
  23. UpdatedLessRecentlyThan time.Time
  24. NumberToCheckPerRepo int64
  25. ProportionToCheckPerRepo float64
  26. }
  27. // GarbageCollectLFSMetaObjects garbage collects LFS objects for all repositories
  28. func GarbageCollectLFSMetaObjects(ctx context.Context, opts GarbageCollectLFSMetaObjectsOptions) error {
  29. log.Trace("Doing: GarbageCollectLFSMetaObjects")
  30. defer log.Trace("Finished: GarbageCollectLFSMetaObjects")
  31. if opts.LogDetail == nil {
  32. opts.LogDetail = log.Debug
  33. }
  34. if !setting.LFS.StartServer {
  35. opts.LogDetail("LFS support is disabled")
  36. return nil
  37. }
  38. return git_model.IterateRepositoryIDsWithLFSMetaObjects(ctx, func(ctx context.Context, repoID, count int64) error {
  39. repo, err := repo_model.GetRepositoryByID(ctx, repoID)
  40. if err != nil {
  41. return err
  42. }
  43. if newMinimum := int64(float64(count) * opts.ProportionToCheckPerRepo); newMinimum > opts.NumberToCheckPerRepo && opts.NumberToCheckPerRepo != 0 {
  44. opts.NumberToCheckPerRepo = newMinimum
  45. }
  46. return GarbageCollectLFSMetaObjectsForRepo(ctx, repo, opts)
  47. })
  48. }
  49. // GarbageCollectLFSMetaObjectsForRepo garbage collects LFS objects for a specific repository
  50. func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.Repository, opts GarbageCollectLFSMetaObjectsOptions) error {
  51. opts.LogDetail("Checking %-v", repo)
  52. total, orphaned, collected, deleted := int64(0), 0, 0, 0
  53. defer func() {
  54. if orphaned == 0 {
  55. opts.LogDetail("Found %d total LFSMetaObjects in %-v", total, repo)
  56. } else if !opts.AutoFix {
  57. opts.LogDetail("Found %d/%d orphaned LFSMetaObjects in %-v", orphaned, total, repo)
  58. } else {
  59. opts.LogDetail("Collected %d/%d orphaned/%d total LFSMetaObjects in %-v. %d removed from storage.", collected, orphaned, total, repo, deleted)
  60. }
  61. }()
  62. gitRepo, err := gitrepo.OpenRepository(ctx, repo)
  63. if err != nil {
  64. log.Error("Unable to open git repository %-v: %v", repo, err)
  65. return err
  66. }
  67. defer gitRepo.Close()
  68. store := lfs.NewContentStore()
  69. errStop := errors.New("STOPERR")
  70. objectFormat, _ := gitRepo.GetObjectFormat()
  71. err = git_model.IterateLFSMetaObjectsForRepo(ctx, repo.ID, func(ctx context.Context, metaObject *git_model.LFSMetaObject, count int64) error {
  72. if opts.NumberToCheckPerRepo > 0 && total > opts.NumberToCheckPerRepo {
  73. return errStop
  74. }
  75. total++
  76. pointerSha := git.ComputeBlobHash(objectFormat, []byte(metaObject.Pointer.StringContent()))
  77. if gitRepo.IsObjectExist(pointerSha.String()) {
  78. return git_model.MarkLFSMetaObject(ctx, metaObject.ID)
  79. }
  80. orphaned++
  81. if !opts.AutoFix {
  82. return nil
  83. }
  84. // Non-existent pointer file
  85. _, err = git_model.RemoveLFSMetaObjectByOidFn(ctx, repo.ID, metaObject.Oid, func(count int64) error {
  86. if count > 0 {
  87. return nil
  88. }
  89. if err := store.Delete(metaObject.RelativePath()); err != nil {
  90. log.Error("Unable to remove lfs metaobject %s from store: %v", metaObject.Oid, err)
  91. }
  92. deleted++
  93. return nil
  94. })
  95. if err != nil {
  96. return fmt.Errorf("unable to remove meta-object %s in %s: %w", metaObject.Oid, repo.FullName(), err)
  97. }
  98. collected++
  99. return nil
  100. }, &git_model.IterateLFSMetaObjectsForRepoOptions{
  101. // Only attempt to garbage collect lfs meta objects older than a week as the order of git lfs upload
  102. // and git object upload is not necessarily guaranteed. It's possible to imagine a situation whereby
  103. // an LFS object is uploaded but the git branch is not uploaded immediately, or there are some rapid
  104. // changes in new branches that might lead to lfs objects becoming temporarily unassociated with git
  105. // objects.
  106. //
  107. // It is likely that a week is potentially excessive but it should definitely be enough that any
  108. // unassociated LFS object is genuinely unassociated.
  109. OlderThan: timeutil.TimeStamp(opts.OlderThan.Unix()),
  110. UpdatedLessRecentlyThan: timeutil.TimeStamp(opts.UpdatedLessRecentlyThan.Unix()),
  111. OrderByUpdated: true,
  112. LoopFunctionAlwaysUpdates: true,
  113. })
  114. if err == errStop {
  115. opts.LogDetail("Processing stopped at %d total LFSMetaObjects in %-v", total, repo)
  116. return nil
  117. } else if err != nil {
  118. return err
  119. }
  120. return nil
  121. }