diff options
Diffstat (limited to 'modules/repofiles/upload.go')
-rw-r--r-- | modules/repofiles/upload.go | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/modules/repofiles/upload.go b/modules/repofiles/upload.go new file mode 100644 index 0000000000..ed6a9438c7 --- /dev/null +++ b/modules/repofiles/upload.go @@ -0,0 +1,209 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repofiles + +import ( + "fmt" + "os" + "path" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/modules/setting" +) + +// UploadRepoFileOptions contains the uploaded repository file options +type UploadRepoFileOptions struct { + LastCommitID string + OldBranch string + NewBranch string + TreePath string + Message string + Files []string // In UUID format. +} + +type uploadInfo struct { + upload *models.Upload + lfsMetaObject *models.LFSMetaObject +} + +func cleanUpAfterFailure(infos *[]uploadInfo, t *TemporaryUploadRepository, original error) error { + for _, info := range *infos { + if info.lfsMetaObject == nil { + continue + } + if !info.lfsMetaObject.Existing { + if err := t.repo.RemoveLFSMetaObjectByOid(info.lfsMetaObject.Oid); err != nil { + original = fmt.Errorf("%v, %v", original, err) + } + } + } + return original +} + +// UploadRepoFiles uploads files to the given repository +func UploadRepoFiles(repo *models.Repository, doer *models.User, opts *UploadRepoFileOptions) error { + if len(opts.Files) == 0 { + return nil + } + + uploads, err := models.GetUploadsByUUIDs(opts.Files) + if err != nil { + return fmt.Errorf("GetUploadsByUUIDs [uuids: %v]: %v", opts.Files, err) + } + + t, err := NewTemporaryUploadRepository(repo) + defer t.Close() + if err != nil { + return err + } + if err := t.Clone(opts.OldBranch); err != nil { + return err + } + if err := t.SetDefaultIndex(); err != nil { + return err + } + + names := make([]string, len(uploads)) + infos := make([]uploadInfo, len(uploads)) + for i, upload := range uploads { + names[i] = upload.Name + infos[i] = uploadInfo{upload: upload} + } + + filename2attribute2info, err := t.CheckAttribute("filter", names...) + if err != nil { + return err + } + + // Copy uploaded files into repository. + for i, uploadInfo := range infos { + file, err := os.Open(uploadInfo.upload.LocalPath()) + if err != nil { + return err + } + defer file.Close() + + var objectHash string + if filename2attribute2info[uploadInfo.upload.Name] != nil && filename2attribute2info[uploadInfo.upload.Name]["filter"] == "lfs" { + // Handle LFS + // FIXME: Inefficient! this should probably happen in models.Upload + oid, err := models.GenerateLFSOid(file) + if err != nil { + return err + } + fileInfo, err := file.Stat() + if err != nil { + return err + } + + uploadInfo.lfsMetaObject = &models.LFSMetaObject{Oid: oid, Size: fileInfo.Size(), RepositoryID: t.repo.ID} + + if objectHash, err = t.HashObject(strings.NewReader(uploadInfo.lfsMetaObject.Pointer())); err != nil { + return err + } + infos[i] = uploadInfo + + } else { + if objectHash, err = t.HashObject(file); err != nil { + return err + } + } + + // Add the object to the index + if err := t.AddObjectToIndex("100644", objectHash, path.Join(opts.TreePath, uploadInfo.upload.Name)); err != nil { + return err + + } + } + + // Now write the tree + treeHash, err := t.WriteTree() + if err != nil { + return err + } + + // make author and committer the doer + author := doer + committer := doer + + // Now commit the tree + commitHash, err := t.CommitTree(author, committer, treeHash, opts.Message) + if err != nil { + return err + } + + // Now deal with LFS objects + for _, uploadInfo := range infos { + if uploadInfo.lfsMetaObject == nil { + continue + } + uploadInfo.lfsMetaObject, err = models.NewLFSMetaObject(uploadInfo.lfsMetaObject) + if err != nil { + // OK Now we need to cleanup + return cleanUpAfterFailure(&infos, t, err) + } + // Don't move the files yet - we need to ensure that + // everything can be inserted first + } + + // OK now we can insert the data into the store - there's no way to clean up the store + // once it's in there, it's in there. + contentStore := &lfs.ContentStore{BasePath: setting.LFS.ContentPath} + for _, uploadInfo := range infos { + if uploadInfo.lfsMetaObject == nil { + continue + } + if !contentStore.Exists(uploadInfo.lfsMetaObject) { + file, err := os.Open(uploadInfo.upload.LocalPath()) + if err != nil { + return cleanUpAfterFailure(&infos, t, err) + } + defer file.Close() + // FIXME: Put regenerates the hash and copies the file over. + // I guess this strictly ensures the soundness of the store but this is inefficient. + if err := contentStore.Put(uploadInfo.lfsMetaObject, file); err != nil { + // OK Now we need to cleanup + // Can't clean up the store, once uploaded there they're there. + return cleanUpAfterFailure(&infos, t, err) + } + } + } + + // Then push this tree to NewBranch + if err := t.Push(doer, commitHash, opts.NewBranch); err != nil { + return err + } + + // Simulate push event. + oldCommitID := opts.LastCommitID + if opts.NewBranch != opts.OldBranch { + oldCommitID = git.EmptySHA + } + + if err = repo.GetOwner(); err != nil { + return fmt.Errorf("GetOwner: %v", err) + } + err = models.PushUpdate( + opts.NewBranch, + models.PushUpdateOptions{ + PusherID: doer.ID, + PusherName: doer.Name, + RepoUserName: repo.Owner.Name, + RepoName: repo.Name, + RefFullName: git.BranchPrefix + opts.NewBranch, + OldCommitID: oldCommitID, + NewCommitID: commitHash, + }, + ) + if err != nil { + return fmt.Errorf("PushUpdate: %v", err) + } + // FIXME: Should we models.UpdateRepoIndexer(repo) here? + + return models.DeleteUploads(uploads...) +} |