gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repository
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "strings"
  10. "code.gitea.io/gitea/models/db"
  11. git_model "code.gitea.io/gitea/models/git"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. "code.gitea.io/gitea/modules/git"
  14. "code.gitea.io/gitea/modules/gitrepo"
  15. "code.gitea.io/gitea/modules/lfs"
  16. "code.gitea.io/gitea/modules/log"
  17. "code.gitea.io/gitea/modules/setting"
  18. "code.gitea.io/gitea/modules/timeutil"
  19. )
  20. /*
  21. GitHub, GitLab, Gogs: *.wiki.git
  22. BitBucket: *.git/wiki
  23. */
  24. var commonWikiURLSuffixes = []string{".wiki.git", ".git/wiki"}
  25. // WikiRemoteURL returns accessible repository URL for wiki if exists.
  26. // Otherwise, it returns an empty string.
  27. func WikiRemoteURL(ctx context.Context, remote string) string {
  28. remote = strings.TrimSuffix(remote, ".git")
  29. for _, suffix := range commonWikiURLSuffixes {
  30. wikiURL := remote + suffix
  31. if git.IsRepoURLAccessible(ctx, wikiURL) {
  32. return wikiURL
  33. }
  34. }
  35. return ""
  36. }
  37. // SyncRepoTags synchronizes releases table with repository tags
  38. func SyncRepoTags(ctx context.Context, repoID int64) error {
  39. repo, err := repo_model.GetRepositoryByID(ctx, repoID)
  40. if err != nil {
  41. return err
  42. }
  43. gitRepo, err := gitrepo.OpenRepository(ctx, repo)
  44. if err != nil {
  45. return err
  46. }
  47. defer gitRepo.Close()
  48. return SyncReleasesWithTags(ctx, repo, gitRepo)
  49. }
  50. // StoreMissingLfsObjectsInRepository downloads missing LFS objects
  51. func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, lfsClient lfs.Client) error {
  52. contentStore := lfs.NewContentStore()
  53. pointerChan := make(chan lfs.PointerBlob)
  54. errChan := make(chan error, 1)
  55. go lfs.SearchPointerBlobs(ctx, gitRepo, pointerChan, errChan)
  56. downloadObjects := func(pointers []lfs.Pointer) error {
  57. err := lfsClient.Download(ctx, pointers, func(p lfs.Pointer, content io.ReadCloser, objectError error) error {
  58. if errors.Is(objectError, lfs.ErrObjectNotExist) {
  59. log.Warn("Ignoring missing upstream LFS object %-v: %v", p, objectError)
  60. return nil
  61. }
  62. if objectError != nil {
  63. return objectError
  64. }
  65. defer content.Close()
  66. _, err := git_model.NewLFSMetaObject(ctx, repo.ID, p)
  67. if err != nil {
  68. log.Error("Repo[%-v]: Error creating LFS meta object %-v: %v", repo, p, err)
  69. return err
  70. }
  71. if err := contentStore.Put(p, content); err != nil {
  72. log.Error("Repo[%-v]: Error storing content for LFS meta object %-v: %v", repo, p, err)
  73. if _, err2 := git_model.RemoveLFSMetaObjectByOid(ctx, repo.ID, p.Oid); err2 != nil {
  74. log.Error("Repo[%-v]: Error removing LFS meta object %-v: %v", repo, p, err2)
  75. }
  76. return err
  77. }
  78. return nil
  79. })
  80. if err != nil {
  81. select {
  82. case <-ctx.Done():
  83. return nil
  84. default:
  85. }
  86. }
  87. return err
  88. }
  89. var batch []lfs.Pointer
  90. for pointerBlob := range pointerChan {
  91. meta, err := git_model.GetLFSMetaObjectByOid(ctx, repo.ID, pointerBlob.Oid)
  92. if err != nil && err != git_model.ErrLFSObjectNotExist {
  93. log.Error("Repo[%-v]: Error querying LFS meta object %-v: %v", repo, pointerBlob.Pointer, err)
  94. return err
  95. }
  96. if meta != nil {
  97. log.Trace("Repo[%-v]: Skipping unknown LFS meta object %-v", repo, pointerBlob.Pointer)
  98. continue
  99. }
  100. log.Trace("Repo[%-v]: LFS object %-v not present in repository", repo, pointerBlob.Pointer)
  101. exist, err := contentStore.Exists(pointerBlob.Pointer)
  102. if err != nil {
  103. log.Error("Repo[%-v]: Error checking if LFS object %-v exists: %v", repo, pointerBlob.Pointer, err)
  104. return err
  105. }
  106. if exist {
  107. log.Trace("Repo[%-v]: LFS object %-v already present; creating meta object", repo, pointerBlob.Pointer)
  108. _, err := git_model.NewLFSMetaObject(ctx, repo.ID, pointerBlob.Pointer)
  109. if err != nil {
  110. log.Error("Repo[%-v]: Error creating LFS meta object %-v: %v", repo, pointerBlob.Pointer, err)
  111. return err
  112. }
  113. } else {
  114. if setting.LFS.MaxFileSize > 0 && pointerBlob.Size > setting.LFS.MaxFileSize {
  115. log.Info("Repo[%-v]: LFS object %-v download denied because of LFS_MAX_FILE_SIZE=%d < size %d", repo, pointerBlob.Pointer, setting.LFS.MaxFileSize, pointerBlob.Size)
  116. continue
  117. }
  118. batch = append(batch, pointerBlob.Pointer)
  119. if len(batch) >= lfsClient.BatchSize() {
  120. if err := downloadObjects(batch); err != nil {
  121. return err
  122. }
  123. batch = nil
  124. }
  125. }
  126. }
  127. if len(batch) > 0 {
  128. if err := downloadObjects(batch); err != nil {
  129. return err
  130. }
  131. }
  132. err, has := <-errChan
  133. if has {
  134. log.Error("Repo[%-v]: Error enumerating LFS objects for repository: %v", repo, err)
  135. return err
  136. }
  137. return nil
  138. }
  139. // shortRelease to reduce load memory, this struct can replace repo_model.Release
  140. type shortRelease struct {
  141. ID int64
  142. TagName string
  143. Sha1 string
  144. IsTag bool
  145. }
  146. func (shortRelease) TableName() string {
  147. return "release"
  148. }
  149. // SyncReleasesWithTags is a tag<->release table
  150. // synchronization which overwrites all Releases from the repository tags. This
  151. // can be relied on since a pull-mirror is always identical to its
  152. // upstream. Hence, after each sync we want the release set to be
  153. // identical to the upstream tag set. This is much more efficient for
  154. // repositories like https://github.com/vim/vim (with over 13000 tags).
  155. func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error {
  156. log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name)
  157. tags, _, err := gitRepo.GetTagInfos(0, 0)
  158. if err != nil {
  159. return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
  160. }
  161. var added, deleted, updated int
  162. err = db.WithTx(ctx, func(ctx context.Context) error {
  163. dbReleases, err := db.Find[shortRelease](ctx, repo_model.FindReleasesOptions{
  164. RepoID: repo.ID,
  165. IncludeDrafts: true,
  166. IncludeTags: true,
  167. })
  168. if err != nil {
  169. return fmt.Errorf("unable to FindReleases in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
  170. }
  171. inserts, deletes, updates := calcSync(tags, dbReleases)
  172. //
  173. // make release set identical to upstream tags
  174. //
  175. for _, tag := range inserts {
  176. release := repo_model.Release{
  177. RepoID: repo.ID,
  178. TagName: tag.Name,
  179. LowerTagName: strings.ToLower(tag.Name),
  180. Sha1: tag.Object.String(),
  181. // NOTE: ignored, The NumCommits value is calculated and cached on demand when the UI requires it.
  182. NumCommits: -1,
  183. CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
  184. IsTag: true,
  185. }
  186. if err := db.Insert(ctx, release); err != nil {
  187. return fmt.Errorf("unable insert tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
  188. }
  189. }
  190. // only delete tags releases
  191. if len(deletes) > 0 {
  192. if _, err := db.GetEngine(ctx).Where("repo_id=?", repo.ID).
  193. In("id", deletes).
  194. Delete(&repo_model.Release{}); err != nil {
  195. return fmt.Errorf("unable to delete tags for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
  196. }
  197. }
  198. for _, tag := range updates {
  199. if _, err := db.GetEngine(ctx).Where("repo_id = ? AND lower_tag_name = ?", repo.ID, strings.ToLower(tag.Name)).
  200. Cols("sha1", "created_unix").
  201. Update(&repo_model.Release{
  202. Sha1: tag.Object.String(),
  203. CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
  204. }); err != nil {
  205. return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
  206. }
  207. }
  208. added, deleted, updated = len(deletes), len(updates), len(inserts)
  209. return nil
  210. })
  211. if err != nil {
  212. return fmt.Errorf("unable to rebuild release table for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
  213. }
  214. log.Trace("SyncReleasesWithTags: %d tags added, %d tags deleted, %d tags updated", added, deleted, updated)
  215. return nil
  216. }
  217. func calcSync(destTags []*git.Tag, dbTags []*shortRelease) ([]*git.Tag, []int64, []*git.Tag) {
  218. destTagMap := make(map[string]*git.Tag)
  219. for _, tag := range destTags {
  220. destTagMap[tag.Name] = tag
  221. }
  222. dbTagMap := make(map[string]*shortRelease)
  223. for _, rel := range dbTags {
  224. dbTagMap[rel.TagName] = rel
  225. }
  226. inserted := make([]*git.Tag, 0, 10)
  227. updated := make([]*git.Tag, 0, 10)
  228. for _, tag := range destTags {
  229. rel := dbTagMap[tag.Name]
  230. if rel == nil {
  231. inserted = append(inserted, tag)
  232. } else if rel.Sha1 != tag.Object.String() {
  233. updated = append(updated, tag)
  234. }
  235. }
  236. deleted := make([]int64, 0, 10)
  237. for _, tag := range dbTags {
  238. if destTagMap[tag.TagName] == nil && tag.IsTag {
  239. deleted = append(deleted, tag.ID)
  240. }
  241. }
  242. return inserted, deleted, updated
  243. }