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.

upload.go 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package files
  4. import (
  5. "context"
  6. "fmt"
  7. "os"
  8. "path"
  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. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/git"
  15. "code.gitea.io/gitea/modules/lfs"
  16. "code.gitea.io/gitea/modules/setting"
  17. )
  18. // UploadRepoFileOptions contains the uploaded repository file options
  19. type UploadRepoFileOptions struct {
  20. LastCommitID string
  21. OldBranch string
  22. NewBranch string
  23. TreePath string
  24. Message string
  25. Files []string // In UUID format.
  26. Signoff bool
  27. }
  28. type uploadInfo struct {
  29. upload *repo_model.Upload
  30. lfsMetaObject *git_model.LFSMetaObject
  31. }
  32. func cleanUpAfterFailure(infos *[]uploadInfo, t *TemporaryUploadRepository, original error) error {
  33. for _, info := range *infos {
  34. if info.lfsMetaObject == nil {
  35. continue
  36. }
  37. if !info.lfsMetaObject.Existing {
  38. if _, err := git_model.RemoveLFSMetaObjectByOid(db.DefaultContext, t.repo.ID, info.lfsMetaObject.Oid); err != nil {
  39. original = fmt.Errorf("%w, %v", original, err) // We wrap the original error - as this is the underlying error that required the fallback
  40. }
  41. }
  42. }
  43. return original
  44. }
  45. // UploadRepoFiles uploads files to the given repository
  46. func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *UploadRepoFileOptions) error {
  47. if len(opts.Files) == 0 {
  48. return nil
  49. }
  50. uploads, err := repo_model.GetUploadsByUUIDs(opts.Files)
  51. if err != nil {
  52. return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %w", opts.Files, err)
  53. }
  54. names := make([]string, len(uploads))
  55. infos := make([]uploadInfo, len(uploads))
  56. for i, upload := range uploads {
  57. // Check file is not lfs locked, will return nil if lock setting not enabled
  58. filepath := path.Join(opts.TreePath, upload.Name)
  59. lfsLock, err := git_model.GetTreePathLock(ctx, repo.ID, filepath)
  60. if err != nil {
  61. return err
  62. }
  63. if lfsLock != nil && lfsLock.OwnerID != doer.ID {
  64. u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
  65. if err != nil {
  66. return err
  67. }
  68. return git_model.ErrLFSFileLocked{RepoID: repo.ID, Path: filepath, UserName: u.Name}
  69. }
  70. names[i] = upload.Name
  71. infos[i] = uploadInfo{upload: upload}
  72. }
  73. t, err := NewTemporaryUploadRepository(ctx, repo)
  74. if err != nil {
  75. return err
  76. }
  77. defer t.Close()
  78. if err := t.Clone(opts.OldBranch); err != nil {
  79. return err
  80. }
  81. if err := t.SetDefaultIndex(); err != nil {
  82. return err
  83. }
  84. var filename2attribute2info map[string]map[string]string
  85. if setting.LFS.StartServer {
  86. filename2attribute2info, err = t.gitRepo.CheckAttribute(git.CheckAttributeOpts{
  87. Attributes: []git.CmdArg{"filter"},
  88. Filenames: names,
  89. CachedOnly: true,
  90. })
  91. if err != nil {
  92. return err
  93. }
  94. }
  95. // Copy uploaded files into repository.
  96. for i := range infos {
  97. if err := copyUploadedLFSFileIntoRepository(&infos[i], filename2attribute2info, t, opts.TreePath); err != nil {
  98. return err
  99. }
  100. }
  101. // Now write the tree
  102. treeHash, err := t.WriteTree()
  103. if err != nil {
  104. return err
  105. }
  106. // make author and committer the doer
  107. author := doer
  108. committer := doer
  109. // Now commit the tree
  110. commitHash, err := t.CommitTree(opts.LastCommitID, author, committer, treeHash, opts.Message, opts.Signoff)
  111. if err != nil {
  112. return err
  113. }
  114. // Now deal with LFS objects
  115. for i := range infos {
  116. if infos[i].lfsMetaObject == nil {
  117. continue
  118. }
  119. infos[i].lfsMetaObject, err = git_model.NewLFSMetaObject(ctx, infos[i].lfsMetaObject)
  120. if err != nil {
  121. // OK Now we need to cleanup
  122. return cleanUpAfterFailure(&infos, t, err)
  123. }
  124. // Don't move the files yet - we need to ensure that
  125. // everything can be inserted first
  126. }
  127. // OK now we can insert the data into the store - there's no way to clean up the store
  128. // once it's in there, it's in there.
  129. contentStore := lfs.NewContentStore()
  130. for _, info := range infos {
  131. if err := uploadToLFSContentStore(info, contentStore); err != nil {
  132. return cleanUpAfterFailure(&infos, t, err)
  133. }
  134. }
  135. // Then push this tree to NewBranch
  136. if err := t.Push(doer, commitHash, opts.NewBranch); err != nil {
  137. return err
  138. }
  139. return repo_model.DeleteUploads(uploads...)
  140. }
  141. func copyUploadedLFSFileIntoRepository(info *uploadInfo, filename2attribute2info map[string]map[string]string, t *TemporaryUploadRepository, treePath string) error {
  142. file, err := os.Open(info.upload.LocalPath())
  143. if err != nil {
  144. return err
  145. }
  146. defer file.Close()
  147. var objectHash string
  148. if setting.LFS.StartServer && filename2attribute2info[info.upload.Name] != nil && filename2attribute2info[info.upload.Name]["filter"] == "lfs" {
  149. // Handle LFS
  150. // FIXME: Inefficient! this should probably happen in models.Upload
  151. pointer, err := lfs.GeneratePointer(file)
  152. if err != nil {
  153. return err
  154. }
  155. info.lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: t.repo.ID}
  156. if objectHash, err = t.HashObject(strings.NewReader(pointer.StringContent())); err != nil {
  157. return err
  158. }
  159. } else if objectHash, err = t.HashObject(file); err != nil {
  160. return err
  161. }
  162. // Add the object to the index
  163. return t.AddObjectToIndex("100644", objectHash, path.Join(treePath, info.upload.Name))
  164. }
  165. func uploadToLFSContentStore(info uploadInfo, contentStore *lfs.ContentStore) error {
  166. if info.lfsMetaObject == nil {
  167. return nil
  168. }
  169. exist, err := contentStore.Exists(info.lfsMetaObject.Pointer)
  170. if err != nil {
  171. return err
  172. }
  173. if !exist {
  174. file, err := os.Open(info.upload.LocalPath())
  175. if err != nil {
  176. return err
  177. }
  178. defer file.Close()
  179. // FIXME: Put regenerates the hash and copies the file over.
  180. // I guess this strictly ensures the soundness of the store but this is inefficient.
  181. if err := contentStore.Put(info.lfsMetaObject.Pointer, file); err != nil {
  182. // OK Now we need to cleanup
  183. // Can't clean up the store, once uploaded there they're there.
  184. return err
  185. }
  186. }
  187. return nil
  188. }