2019-02-12 16:07:31 +03:00
// 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.
2021-11-24 10:56:24 +03:00
package files
2019-02-12 16:07:31 +03:00
import (
2022-01-20 02:26:57 +03:00
"context"
2019-02-12 16:07:31 +03:00
"fmt"
"os"
"path"
"strings"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/models"
2022-06-12 18:51:54 +03:00
git_model "code.gitea.io/gitea/models/git"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2021-03-01 15:14:17 +03:00
"code.gitea.io/gitea/modules/git"
2019-02-12 16:07:31 +03:00
"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.
2021-01-29 11:57:45 +03:00
Signoff bool
2019-02-12 16:07:31 +03:00
}
type uploadInfo struct {
upload * models . Upload
2022-06-12 18:51:54 +03:00
lfsMetaObject * git_model . LFSMetaObject
2019-02-12 16:07:31 +03:00
}
func cleanUpAfterFailure ( infos * [ ] uploadInfo , t * TemporaryUploadRepository , original error ) error {
for _ , info := range * infos {
if info . lfsMetaObject == nil {
continue
}
if ! info . lfsMetaObject . Existing {
2022-06-12 18:51:54 +03:00
if _ , err := git_model . RemoveLFSMetaObjectByOid ( t . repo . ID , info . lfsMetaObject . Oid ) ; err != nil {
2019-02-12 16:07:31 +03:00
original = fmt . Errorf ( "%v, %v" , original , err )
}
}
}
return original
}
// UploadRepoFiles uploads files to the given repository
2022-01-20 02:26:57 +03:00
func UploadRepoFiles ( ctx context . Context , repo * repo_model . Repository , doer * user_model . User , opts * UploadRepoFileOptions ) error {
2019-02-12 16:07:31 +03:00
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 )
}
2019-11-02 10:37:05 +03:00
names := make ( [ ] string , len ( uploads ) )
infos := make ( [ ] uploadInfo , len ( uploads ) )
for i , upload := range uploads {
// Check file is not lfs locked, will return nil if lock setting not enabled
filepath := path . Join ( opts . TreePath , upload . Name )
2022-06-12 18:51:54 +03:00
lfsLock , err := git_model . GetTreePathLock ( repo . ID , filepath )
2019-11-02 10:37:05 +03:00
if err != nil {
return err
}
if lfsLock != nil && lfsLock . OwnerID != doer . ID {
2021-11-24 12:49:20 +03:00
u , err := user_model . GetUserByID ( lfsLock . OwnerID )
if err != nil {
return err
}
2022-06-12 18:51:54 +03:00
return git_model . ErrLFSFileLocked { RepoID : repo . ID , Path : filepath , UserName : u . Name }
2019-11-02 10:37:05 +03:00
}
names [ i ] = upload . Name
infos [ i ] = uploadInfo { upload : upload }
}
2022-01-20 02:26:57 +03:00
t , err := NewTemporaryUploadRepository ( ctx , repo )
2019-02-12 16:07:31 +03:00
if err != nil {
return err
}
2019-06-12 22:41:28 +03:00
defer t . Close ( )
2019-02-12 16:07:31 +03:00
if err := t . Clone ( opts . OldBranch ) ; err != nil {
return err
}
if err := t . SetDefaultIndex ( ) ; err != nil {
return err
}
2019-10-12 03:13:27 +03:00
var filename2attribute2info map [ string ] map [ string ] string
if setting . LFS . StartServer {
2021-03-01 15:14:17 +03:00
filename2attribute2info , err = t . gitRepo . CheckAttribute ( git . CheckAttributeOpts {
Attributes : [ ] string { "filter" } ,
Filenames : names ,
2022-01-18 10:44:30 +03:00
CachedOnly : true ,
2021-03-01 15:14:17 +03:00
} )
2019-10-12 03:13:27 +03:00
if err != nil {
return err
}
2019-02-12 16:07:31 +03:00
}
// Copy uploaded files into repository.
2021-03-20 19:30:29 +03:00
for i := range infos {
if err := copyUploadedLFSFileIntoRepository ( & infos [ i ] , filename2attribute2info , t , opts . TreePath ) ; err != nil {
2019-02-12 16:07:31 +03:00
return err
}
}
// Now write the tree
treeHash , err := t . WriteTree ( )
if err != nil {
return err
}
2019-04-17 19:06:35 +03:00
// make author and committer the doer
author := doer
committer := doer
2019-02-12 16:07:31 +03:00
// Now commit the tree
2022-03-28 22:48:41 +03:00
commitHash , err := t . CommitTree ( opts . LastCommitID , author , committer , treeHash , opts . Message , opts . Signoff )
2019-02-12 16:07:31 +03:00
if err != nil {
return err
}
// Now deal with LFS objects
2021-03-20 19:30:29 +03:00
for i := range infos {
if infos [ i ] . lfsMetaObject == nil {
2019-02-12 16:07:31 +03:00
continue
}
2022-06-12 18:51:54 +03:00
infos [ i ] . lfsMetaObject , err = git_model . NewLFSMetaObject ( infos [ i ] . lfsMetaObject )
2019-02-12 16:07:31 +03:00
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.
2021-04-09 01:25:57 +03:00
contentStore := lfs . NewContentStore ( )
2021-03-20 01:49:29 +03:00
for _ , info := range infos {
if err := uploadToLFSContentStore ( info , contentStore ) ; err != nil {
2020-09-08 18:45:10 +03:00
return cleanUpAfterFailure ( & infos , t , err )
}
2019-02-12 16:07:31 +03:00
}
// Then push this tree to NewBranch
if err := t . Push ( doer , commitHash , opts . NewBranch ) ; err != nil {
return err
}
return models . DeleteUploads ( uploads ... )
}
2021-03-20 01:49:29 +03:00
2021-03-20 19:30:29 +03:00
func copyUploadedLFSFileIntoRepository ( info * uploadInfo , filename2attribute2info map [ string ] map [ string ] string , t * TemporaryUploadRepository , treePath string ) error {
file , err := os . Open ( info . upload . LocalPath ( ) )
if err != nil {
return err
}
defer file . Close ( )
var objectHash string
if setting . LFS . StartServer && filename2attribute2info [ info . upload . Name ] != nil && filename2attribute2info [ info . upload . Name ] [ "filter" ] == "lfs" {
// Handle LFS
// FIXME: Inefficient! this should probably happen in models.Upload
2021-04-09 01:25:57 +03:00
pointer , err := lfs . GeneratePointer ( file )
2021-03-20 19:30:29 +03:00
if err != nil {
return err
}
2022-06-12 18:51:54 +03:00
info . lfsMetaObject = & git_model . LFSMetaObject { Pointer : pointer , RepositoryID : t . repo . ID }
2021-03-20 19:30:29 +03:00
2021-04-09 01:25:57 +03:00
if objectHash , err = t . HashObject ( strings . NewReader ( pointer . StringContent ( ) ) ) ; err != nil {
2021-03-20 19:30:29 +03:00
return err
}
} else if objectHash , err = t . HashObject ( file ) ; err != nil {
return err
}
// Add the object to the index
return t . AddObjectToIndex ( "100644" , objectHash , path . Join ( treePath , info . upload . Name ) )
}
2021-03-20 01:49:29 +03:00
func uploadToLFSContentStore ( info uploadInfo , contentStore * lfs . ContentStore ) error {
if info . lfsMetaObject == nil {
return nil
}
2021-04-09 01:25:57 +03:00
exist , err := contentStore . Exists ( info . lfsMetaObject . Pointer )
2021-03-20 01:49:29 +03:00
if err != nil {
return err
}
if ! exist {
file , err := os . Open ( info . upload . LocalPath ( ) )
if err != nil {
return 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.
2021-04-09 01:25:57 +03:00
if err := contentStore . Put ( info . lfsMetaObject . Pointer , file ) ; err != nil {
2021-03-20 01:49:29 +03:00
// OK Now we need to cleanup
// Can't clean up the store, once uploaded there they're there.
return err
}
}
return nil
}