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