2020-04-05 09:20:50 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2020-04-05 09:20:50 +03:00
2022-06-12 18:51:54 +03:00
package git
2016-12-26 04:16:37 +03:00
import (
2021-12-10 04:27:50 +03:00
"context"
2022-01-01 12:05:31 +03:00
"fmt"
2017-12-11 07:37:04 +03:00
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2022-06-12 18:51:54 +03:00
"code.gitea.io/gitea/models/perm"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2022-06-16 02:24:10 +03:00
"code.gitea.io/gitea/models/unit"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2021-04-09 01:25:57 +03:00
"code.gitea.io/gitea/modules/lfs"
2022-01-01 12:05:31 +03:00
"code.gitea.io/gitea/modules/log"
2022-12-15 23:44:16 +03:00
"code.gitea.io/gitea/modules/setting"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2022-10-18 08:50:37 +03:00
"code.gitea.io/gitea/modules/util"
2019-10-28 21:31:55 +03:00
"xorm.io/builder"
2016-12-26 04:16:37 +03:00
)
2022-06-12 18:51:54 +03:00
// ErrLFSLockNotExist represents a "LFSLockNotExist" kind of error.
type ErrLFSLockNotExist struct {
ID int64
RepoID int64
Path string
}
// IsErrLFSLockNotExist checks if an error is a ErrLFSLockNotExist.
func IsErrLFSLockNotExist ( err error ) bool {
_ , ok := err . ( ErrLFSLockNotExist )
return ok
}
func ( err ErrLFSLockNotExist ) Error ( ) string {
return fmt . Sprintf ( "lfs lock does not exist [id: %d, rid: %d, path: %s]" , err . ID , err . RepoID , err . Path )
}
2022-10-18 08:50:37 +03:00
func ( err ErrLFSLockNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2022-06-12 18:51:54 +03:00
// ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
type ErrLFSUnauthorizedAction struct {
RepoID int64
UserName string
Mode perm . AccessMode
}
// IsErrLFSUnauthorizedAction checks if an error is a ErrLFSUnauthorizedAction.
func IsErrLFSUnauthorizedAction ( err error ) bool {
_ , ok := err . ( ErrLFSUnauthorizedAction )
return ok
}
func ( err ErrLFSUnauthorizedAction ) Error ( ) string {
if err . Mode == perm . AccessModeWrite {
return fmt . Sprintf ( "User %s doesn't have write access for lfs lock [rid: %d]" , err . UserName , err . RepoID )
}
return fmt . Sprintf ( "User %s doesn't have read access for lfs lock [rid: %d]" , err . UserName , err . RepoID )
}
2022-10-18 08:50:37 +03:00
func ( err ErrLFSUnauthorizedAction ) Unwrap ( ) error {
return util . ErrPermissionDenied
}
2022-06-12 18:51:54 +03:00
// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
type ErrLFSLockAlreadyExist struct {
RepoID int64
Path string
}
// IsErrLFSLockAlreadyExist checks if an error is a ErrLFSLockAlreadyExist.
func IsErrLFSLockAlreadyExist ( err error ) bool {
_ , ok := err . ( ErrLFSLockAlreadyExist )
return ok
}
func ( err ErrLFSLockAlreadyExist ) Error ( ) string {
return fmt . Sprintf ( "lfs lock already exists [rid: %d, path: %s]" , err . RepoID , err . Path )
}
2022-10-18 08:50:37 +03:00
func ( err ErrLFSLockAlreadyExist ) Unwrap ( ) error {
return util . ErrAlreadyExist
}
2022-06-12 18:51:54 +03:00
// ErrLFSFileLocked represents a "LFSFileLocked" kind of error.
type ErrLFSFileLocked struct {
RepoID int64
Path string
UserName string
}
// IsErrLFSFileLocked checks if an error is a ErrLFSFileLocked.
func IsErrLFSFileLocked ( err error ) bool {
_ , ok := err . ( ErrLFSFileLocked )
return ok
}
func ( err ErrLFSFileLocked ) Error ( ) string {
return fmt . Sprintf ( "File is lfs locked [repo: %d, locked by: %s, path: %s]" , err . RepoID , err . UserName , err . Path )
}
2022-10-18 08:50:37 +03:00
func ( err ErrLFSFileLocked ) Unwrap ( ) error {
return util . ErrPermissionDenied
}
2016-12-26 04:16:37 +03:00
// LFSMetaObject stores metadata for LFS tracked files.
type LFSMetaObject struct {
2021-04-09 01:25:57 +03:00
ID int64 ` xorm:"pk autoincr" `
lfs . Pointer ` xorm:"extends" `
2019-08-15 17:46:21 +03:00
RepositoryID int64 ` xorm:"UNIQUE(s) INDEX NOT NULL" `
Existing bool ` xorm:"-" `
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
2023-01-16 22:50:53 +03:00
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2016-12-26 04:16:37 +03:00
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( LFSMetaObject ) )
}
2016-12-26 04:16:37 +03:00
// LFSTokenResponse defines the JSON structure in which the JWT token is stored.
// This structure is fetched via SSH and passed by the Git LFS client to the server
// endpoint for authorization.
type LFSTokenResponse struct {
Header map [ string ] string ` json:"header" `
Href string ` json:"href" `
}
2021-03-14 21:52:12 +03:00
// ErrLFSObjectNotExist is returned from lfs models functions in order
// to differentiate between database and missing object errors.
2022-10-18 08:50:37 +03:00
var ErrLFSObjectNotExist = db . ErrNotExist { Resource : "LFS Meta object" }
2016-12-26 04:16:37 +03:00
// NewLFSMetaObject stores a given populated LFSMetaObject structure in the database
// if it is not already present.
2023-01-09 06:50:54 +03:00
func NewLFSMetaObject ( ctx context . Context , m * LFSMetaObject ) ( * LFSMetaObject , error ) {
2016-12-26 04:16:37 +03:00
var err error
2023-01-09 06:50:54 +03:00
ctx , committer , err := db . TxContext ( ctx )
2021-11-21 18:41:00 +03:00
if err != nil {
2019-01-23 11:56:51 +03:00
return nil , err
}
2021-11-21 18:41:00 +03:00
defer committer . Close ( )
2019-01-23 11:56:51 +03:00
2021-11-21 18:41:00 +03:00
has , err := db . GetByBean ( ctx , m )
2016-12-26 04:16:37 +03:00
if err != nil {
return nil , err
}
if has {
m . Existing = true
2021-11-21 18:41:00 +03:00
return m , committer . Commit ( )
2016-12-26 04:16:37 +03:00
}
2021-11-21 18:41:00 +03:00
if err = db . Insert ( ctx , m ) ; err != nil {
2016-12-26 04:16:37 +03:00
return nil , err
}
2021-11-21 18:41:00 +03:00
return m , committer . Commit ( )
2016-12-26 04:16:37 +03:00
}
// GetLFSMetaObjectByOid selects a LFSMetaObject entry from database by its OID.
// It may return ErrLFSObjectNotExist or a database error. If the error is nil,
// the returned pointer is a valid LFSMetaObject.
2023-01-09 06:50:54 +03:00
func GetLFSMetaObjectByOid ( ctx context . Context , repoID int64 , oid string ) ( * LFSMetaObject , error ) {
2016-12-26 04:16:37 +03:00
if len ( oid ) == 0 {
return nil , ErrLFSObjectNotExist
}
2021-12-10 04:27:50 +03:00
m := & LFSMetaObject { Pointer : lfs . Pointer { Oid : oid } , RepositoryID : repoID }
2023-01-09 06:50:54 +03:00
has , err := db . GetEngine ( ctx ) . Get ( m )
2016-12-26 04:16:37 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrLFSObjectNotExist
}
return m , nil
}
// RemoveLFSMetaObjectByOid removes a LFSMetaObject entry from database by its OID.
// It may return ErrLFSObjectNotExist or a database error.
2023-01-09 06:50:54 +03:00
func RemoveLFSMetaObjectByOid ( ctx context . Context , repoID int64 , oid string ) ( int64 , error ) {
return RemoveLFSMetaObjectByOidFn ( ctx , repoID , oid , nil )
2022-12-15 23:44:16 +03:00
}
// RemoveLFSMetaObjectByOidFn removes a LFSMetaObject entry from database by its OID.
// It may return ErrLFSObjectNotExist or a database error. It will run Fn with the current count within the transaction
2023-01-09 06:50:54 +03:00
func RemoveLFSMetaObjectByOidFn ( ctx context . Context , repoID int64 , oid string , fn func ( count int64 ) error ) ( int64 , error ) {
2016-12-26 04:16:37 +03:00
if len ( oid ) == 0 {
2019-10-28 21:31:55 +03:00
return 0 , ErrLFSObjectNotExist
2016-12-26 04:16:37 +03:00
}
2023-01-09 06:50:54 +03:00
ctx , committer , err := db . TxContext ( ctx )
2021-11-21 18:41:00 +03:00
if err != nil {
return 0 , err
2016-12-26 04:16:37 +03:00
}
2021-11-21 18:41:00 +03:00
defer committer . Close ( )
2016-12-26 04:16:37 +03:00
2021-12-10 04:27:50 +03:00
m := & LFSMetaObject { Pointer : lfs . Pointer { Oid : oid } , RepositoryID : repoID }
2021-11-21 18:41:00 +03:00
if _ , err := db . DeleteByBean ( ctx , m ) ; err != nil {
2019-10-28 21:31:55 +03:00
return - 1 , err
}
2021-11-21 18:41:00 +03:00
count , err := db . CountByBean ( ctx , & LFSMetaObject { Pointer : lfs . Pointer { Oid : oid } } )
2019-10-28 21:31:55 +03:00
if err != nil {
return count , err
}
2022-12-15 23:44:16 +03:00
if fn != nil {
if err := fn ( count ) ; err != nil {
return count , err
}
}
2021-11-21 18:41:00 +03:00
return count , committer . Commit ( )
2019-10-28 21:31:55 +03:00
}
// GetLFSMetaObjects returns all LFSMetaObjects associated with a repository
2023-01-09 06:50:54 +03:00
func GetLFSMetaObjects ( ctx context . Context , repoID int64 , page , pageSize int ) ( [ ] * LFSMetaObject , error ) {
sess := db . GetEngine ( ctx )
2019-10-28 21:31:55 +03:00
if page >= 0 && pageSize > 0 {
start := 0
if page > 0 {
start = ( page - 1 ) * pageSize
}
sess . Limit ( pageSize , start )
}
lfsObjects := make ( [ ] * LFSMetaObject , 0 , pageSize )
2021-12-10 04:27:50 +03:00
return lfsObjects , sess . Find ( & lfsObjects , & LFSMetaObject { RepositoryID : repoID } )
2019-10-28 21:31:55 +03:00
}
// CountLFSMetaObjects returns a count of all LFSMetaObjects associated with a repository
2023-01-09 06:50:54 +03:00
func CountLFSMetaObjects ( ctx context . Context , repoID int64 ) ( int64 , error ) {
return db . GetEngine ( ctx ) . Count ( & LFSMetaObject { RepositoryID : repoID } )
2019-10-28 21:31:55 +03:00
}
// LFSObjectAccessible checks if a provided Oid is accessible to the user
2023-01-09 06:50:54 +03:00
func LFSObjectAccessible ( ctx context . Context , user * user_model . User , oid string ) ( bool , error ) {
2019-10-28 21:31:55 +03:00
if user . IsAdmin {
2023-01-09 06:50:54 +03:00
count , err := db . GetEngine ( ctx ) . Count ( & LFSMetaObject { Pointer : lfs . Pointer { Oid : oid } } )
2021-04-09 10:40:34 +03:00
return count > 0 , err
2019-10-28 21:31:55 +03:00
}
2022-06-16 02:24:10 +03:00
cond := repo_model . AccessibleRepositoryCondition ( user , unit . TypeInvalid )
2023-01-09 06:50:54 +03:00
count , err := db . GetEngine ( ctx ) . Where ( cond ) . Join ( "INNER" , "repository" , "`lfs_meta_object`.repository_id = `repository`.id" ) . Count ( & LFSMetaObject { Pointer : lfs . Pointer { Oid : oid } } )
2021-04-09 10:40:34 +03:00
return count > 0 , err
2019-10-28 21:31:55 +03:00
}
2022-11-15 11:08:59 +03:00
// ExistsLFSObject checks if a provided Oid exists within the DB
func ExistsLFSObject ( ctx context . Context , oid string ) ( bool , error ) {
return db . GetEngine ( ctx ) . Exist ( & LFSMetaObject { Pointer : lfs . Pointer { Oid : oid } } )
2022-01-01 12:05:31 +03:00
}
2019-10-28 21:31:55 +03:00
// LFSAutoAssociate auto associates accessible LFSMetaObjects
2023-01-09 06:50:54 +03:00
func LFSAutoAssociate ( ctx context . Context , metas [ ] * LFSMetaObject , user * user_model . User , repoID int64 ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-11-21 18:41:00 +03:00
if err != nil {
2019-10-28 21:31:55 +03:00
return err
}
2021-11-21 18:41:00 +03:00
defer committer . Close ( )
sess := db . GetEngine ( ctx )
2019-10-28 21:31:55 +03:00
2023-07-04 21:36:08 +03:00
oids := make ( [ ] any , len ( metas ) )
2019-10-28 21:31:55 +03:00
oidMap := make ( map [ string ] * LFSMetaObject , len ( metas ) )
for i , meta := range metas {
oids [ i ] = meta . Oid
oidMap [ meta . Oid ] = meta
}
if ! user . IsAdmin {
2022-01-01 12:05:31 +03:00
newMetas := make ( [ ] * LFSMetaObject , 0 , len ( metas ) )
cond := builder . In (
"`lfs_meta_object`.repository_id" ,
2022-06-16 02:24:10 +03:00
builder . Select ( "`repository`.id" ) . From ( "repository" ) . Where ( repo_model . AccessibleRepositoryCondition ( user , unit . TypeInvalid ) ) ,
2022-01-01 12:05:31 +03:00
)
err = sess . Cols ( "oid" ) . Where ( cond ) . In ( "oid" , oids ... ) . GroupBy ( "oid" ) . Find ( & newMetas )
if err != nil {
return err
}
if len ( newMetas ) != len ( oidMap ) {
return fmt . Errorf ( "unable collect all LFS objects from database, expected %d, actually %d" , len ( oidMap ) , len ( newMetas ) )
}
for i := range newMetas {
newMetas [ i ] . Size = oidMap [ newMetas [ i ] . Oid ] . Size
newMetas [ i ] . RepositoryID = repoID
}
if err = db . Insert ( ctx , newMetas ) ; err != nil {
return err
}
} else {
// admin can associate any LFS object to any repository, and we do not care about errors (eg: duplicated unique key),
// even if error occurs, it won't hurt users and won't make things worse
for i := range metas {
2022-03-10 13:09:48 +03:00
p := lfs . Pointer { Oid : metas [ i ] . Oid , Size : metas [ i ] . Size }
2022-01-01 12:05:31 +03:00
_ , err = sess . Insert ( & LFSMetaObject {
2022-03-10 13:09:48 +03:00
Pointer : p ,
2022-01-01 12:05:31 +03:00
RepositoryID : repoID ,
} )
if err != nil {
2022-03-10 13:09:48 +03:00
log . Warn ( "failed to insert LFS meta object %-v for repo_id: %d into database, err=%v" , p , repoID , err )
2022-01-01 12:05:31 +03:00
}
}
2016-12-26 04:16:37 +03:00
}
2021-11-21 18:41:00 +03:00
return committer . Commit ( )
2016-12-26 04:16:37 +03:00
}
2020-09-08 18:45:10 +03:00
2021-12-10 04:27:50 +03:00
// CopyLFS copies LFS data from one repo to another
func CopyLFS ( ctx context . Context , newRepo , oldRepo * repo_model . Repository ) error {
var lfsObjects [ ] * LFSMetaObject
if err := db . GetEngine ( ctx ) . Where ( "repository_id=?" , oldRepo . ID ) . Find ( & lfsObjects ) ; err != nil {
return err
}
for _ , v := range lfsObjects {
v . ID = 0
v . RepositoryID = newRepo . ID
2022-06-12 18:51:54 +03:00
if err := db . Insert ( ctx , v ) ; err != nil {
2021-12-10 04:27:50 +03:00
return err
}
}
return nil
}
2022-06-06 11:01:49 +03:00
// GetRepoLFSSize return a repository's lfs files size
func GetRepoLFSSize ( ctx context . Context , repoID int64 ) ( int64 , error ) {
lfsSize , err := db . GetEngine ( ctx ) . Where ( "repository_id = ?" , repoID ) . SumInt ( new ( LFSMetaObject ) , "size" )
if err != nil {
2022-10-24 22:29:17 +03:00
return 0 , fmt . Errorf ( "updateSize: GetLFSMetaObjects: %w" , err )
2022-06-06 11:01:49 +03:00
}
return lfsSize , nil
}
2022-12-15 23:44:16 +03:00
2023-01-16 22:50:53 +03:00
// IterateRepositoryIDsWithLFSMetaObjects iterates across the repositories that have LFSMetaObjects
func IterateRepositoryIDsWithLFSMetaObjects ( ctx context . Context , f func ( ctx context . Context , repoID , count int64 ) error ) error {
batchSize := setting . Database . IterateBufferSize
sess := db . GetEngine ( ctx )
id := int64 ( 0 )
type RepositoryCount struct {
RepositoryID int64
Count int64
}
for {
counts := make ( [ ] * RepositoryCount , 0 , batchSize )
sess . Select ( "repository_id, COUNT(id) AS count" ) .
Table ( "lfs_meta_object" ) .
Where ( "repository_id > ?" , id ) .
GroupBy ( "repository_id" ) .
OrderBy ( "repository_id ASC" )
if err := sess . Limit ( batchSize , 0 ) . Find ( & counts ) ; err != nil {
return err
}
if len ( counts ) == 0 {
return nil
}
for _ , count := range counts {
if err := f ( ctx , count . RepositoryID , count . Count ) ; err != nil {
return err
}
}
id = counts [ len ( counts ) - 1 ] . RepositoryID
}
}
// IterateLFSMetaObjectsForRepoOptions provides options for IterateLFSMetaObjectsForRepo
2022-12-15 23:44:16 +03:00
type IterateLFSMetaObjectsForRepoOptions struct {
2023-07-26 10:02:53 +03:00
OlderThan timeutil . TimeStamp
UpdatedLessRecentlyThan timeutil . TimeStamp
2023-01-16 22:50:53 +03:00
OrderByUpdated bool
LoopFunctionAlwaysUpdates bool
2022-12-15 23:44:16 +03:00
}
// IterateLFSMetaObjectsForRepo provides a iterator for LFSMetaObjects per Repo
func IterateLFSMetaObjectsForRepo ( ctx context . Context , repoID int64 , f func ( context . Context , * LFSMetaObject , int64 ) error , opts * IterateLFSMetaObjectsForRepoOptions ) error {
var start int
batchSize := setting . Database . IterateBufferSize
engine := db . GetEngine ( ctx )
type CountLFSMetaObject struct {
2023-07-26 10:02:53 +03:00
Count int64
LFSMetaObject ` xorm:"extends" `
2022-12-15 23:44:16 +03:00
}
2023-01-16 22:50:53 +03:00
id := int64 ( 0 )
2022-12-15 23:44:16 +03:00
for {
beans := make ( [ ] * CountLFSMetaObject , 0 , batchSize )
2023-05-17 18:51:22 +03:00
sess := engine . Table ( "lfs_meta_object" ) . Select ( "`lfs_meta_object`.*, COUNT(`l1`.oid) AS `count`" ) .
2022-12-15 23:44:16 +03:00
Join ( "INNER" , "`lfs_meta_object` AS l1" , "`lfs_meta_object`.oid = `l1`.oid" ) .
Where ( "`lfs_meta_object`.repository_id = ?" , repoID )
if ! opts . OlderThan . IsZero ( ) {
sess . And ( "`lfs_meta_object`.created_unix < ?" , opts . OlderThan )
}
2023-01-16 22:50:53 +03:00
if ! opts . UpdatedLessRecentlyThan . IsZero ( ) {
sess . And ( "`lfs_meta_object`.updated_unix < ?" , opts . UpdatedLessRecentlyThan )
}
2022-12-15 23:44:16 +03:00
sess . GroupBy ( "`lfs_meta_object`.id" )
2023-01-16 22:50:53 +03:00
if opts . OrderByUpdated {
sess . OrderBy ( "`lfs_meta_object`.updated_unix ASC" )
} else {
sess . And ( "`lfs_meta_object`.id > ?" , id )
sess . OrderBy ( "`lfs_meta_object`.id ASC" )
}
2022-12-15 23:44:16 +03:00
if err := sess . Limit ( batchSize , start ) . Find ( & beans ) ; err != nil {
return err
}
if len ( beans ) == 0 {
return nil
}
2023-01-16 22:50:53 +03:00
if ! opts . LoopFunctionAlwaysUpdates {
start += len ( beans )
}
2022-12-15 23:44:16 +03:00
for _ , bean := range beans {
if err := f ( ctx , & bean . LFSMetaObject , bean . Count ) ; err != nil {
return err
}
}
2023-01-16 22:50:53 +03:00
id = beans [ len ( beans ) - 1 ] . ID
}
}
// MarkLFSMetaObject updates the updated time for the provided LFSMetaObject
func MarkLFSMetaObject ( ctx context . Context , id int64 ) error {
obj := & LFSMetaObject {
UpdatedUnix : timeutil . TimeStampNow ( ) ,
}
count , err := db . GetEngine ( ctx ) . ID ( id ) . Update ( obj )
if count != 1 {
log . Error ( "Unexpectedly updated %d LFSMetaObjects with ID: %d" , count , id )
2022-12-15 23:44:16 +03:00
}
2023-01-16 22:50:53 +03:00
return err
2022-12-15 23:44:16 +03:00
}