gitea源码

lfs.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package git
  4. import (
  5. "context"
  6. "fmt"
  7. "code.gitea.io/gitea/models/db"
  8. "code.gitea.io/gitea/models/perm"
  9. repo_model "code.gitea.io/gitea/models/repo"
  10. "code.gitea.io/gitea/models/unit"
  11. user_model "code.gitea.io/gitea/models/user"
  12. "code.gitea.io/gitea/modules/lfs"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/setting"
  15. "code.gitea.io/gitea/modules/timeutil"
  16. "code.gitea.io/gitea/modules/util"
  17. "xorm.io/builder"
  18. )
  19. // ErrLFSLockNotExist represents a "LFSLockNotExist" kind of error.
  20. type ErrLFSLockNotExist struct {
  21. ID int64
  22. RepoID int64
  23. Path string
  24. }
  25. // IsErrLFSLockNotExist checks if an error is a ErrLFSLockNotExist.
  26. func IsErrLFSLockNotExist(err error) bool {
  27. _, ok := err.(ErrLFSLockNotExist)
  28. return ok
  29. }
  30. func (err ErrLFSLockNotExist) Error() string {
  31. return fmt.Sprintf("lfs lock does not exist [id: %d, rid: %d, path: %s]", err.ID, err.RepoID, err.Path)
  32. }
  33. func (err ErrLFSLockNotExist) Unwrap() error {
  34. return util.ErrNotExist
  35. }
  36. // ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
  37. type ErrLFSUnauthorizedAction struct {
  38. RepoID int64
  39. UserName string
  40. Mode perm.AccessMode
  41. }
  42. // IsErrLFSUnauthorizedAction checks if an error is a ErrLFSUnauthorizedAction.
  43. func IsErrLFSUnauthorizedAction(err error) bool {
  44. _, ok := err.(ErrLFSUnauthorizedAction)
  45. return ok
  46. }
  47. func (err ErrLFSUnauthorizedAction) Error() string {
  48. if err.Mode == perm.AccessModeWrite {
  49. return fmt.Sprintf("User %s doesn't have write access for lfs lock [rid: %d]", err.UserName, err.RepoID)
  50. }
  51. return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID)
  52. }
  53. func (err ErrLFSUnauthorizedAction) Unwrap() error {
  54. return util.ErrPermissionDenied
  55. }
  56. // ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
  57. type ErrLFSLockAlreadyExist struct {
  58. RepoID int64
  59. Path string
  60. }
  61. // IsErrLFSLockAlreadyExist checks if an error is a ErrLFSLockAlreadyExist.
  62. func IsErrLFSLockAlreadyExist(err error) bool {
  63. _, ok := err.(ErrLFSLockAlreadyExist)
  64. return ok
  65. }
  66. func (err ErrLFSLockAlreadyExist) Error() string {
  67. return fmt.Sprintf("lfs lock already exists [rid: %d, path: %s]", err.RepoID, err.Path)
  68. }
  69. func (err ErrLFSLockAlreadyExist) Unwrap() error {
  70. return util.ErrAlreadyExist
  71. }
  72. // ErrLFSFileLocked represents a "LFSFileLocked" kind of error.
  73. type ErrLFSFileLocked struct {
  74. RepoID int64
  75. Path string
  76. UserName string
  77. }
  78. // IsErrLFSFileLocked checks if an error is a ErrLFSFileLocked.
  79. func IsErrLFSFileLocked(err error) bool {
  80. _, ok := err.(ErrLFSFileLocked)
  81. return ok
  82. }
  83. func (err ErrLFSFileLocked) Error() string {
  84. return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
  85. }
  86. func (err ErrLFSFileLocked) Unwrap() error {
  87. return util.ErrPermissionDenied
  88. }
  89. // LFSMetaObject stores metadata for LFS tracked files.
  90. type LFSMetaObject struct {
  91. ID int64 `xorm:"pk autoincr"`
  92. lfs.Pointer `xorm:"extends"`
  93. RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  94. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  95. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  96. }
  97. func init() {
  98. db.RegisterModel(new(LFSMetaObject))
  99. }
  100. // LFSTokenResponse defines the JSON structure in which the JWT token is stored.
  101. // This structure is fetched via SSH and passed by the Git LFS client to the server
  102. // endpoint for authorization.
  103. type LFSTokenResponse struct {
  104. Header map[string]string `json:"header"`
  105. Href string `json:"href"`
  106. }
  107. // ErrLFSObjectNotExist is returned from lfs models functions in order
  108. // to differentiate between database and missing object errors.
  109. var ErrLFSObjectNotExist = db.ErrNotExist{Resource: "LFS Meta object"}
  110. // NewLFSMetaObject stores a given populated LFSMetaObject structure in the database
  111. // if it is not already present.
  112. func NewLFSMetaObject(ctx context.Context, repoID int64, p lfs.Pointer) (*LFSMetaObject, error) {
  113. m, exist, err := db.Get[LFSMetaObject](ctx, builder.Eq{"repository_id": repoID, "oid": p.Oid})
  114. if err != nil {
  115. return nil, err
  116. } else if exist {
  117. return m, nil
  118. }
  119. m = &LFSMetaObject{Pointer: p, RepositoryID: repoID}
  120. if err = db.Insert(ctx, m); err != nil {
  121. return nil, err
  122. }
  123. return m, nil
  124. }
  125. // GetLFSMetaObjectByOid selects a LFSMetaObject entry from database by its OID.
  126. // It may return ErrLFSObjectNotExist or a database error. If the error is nil,
  127. // the returned pointer is a valid LFSMetaObject.
  128. func GetLFSMetaObjectByOid(ctx context.Context, repoID int64, oid string) (*LFSMetaObject, error) {
  129. if len(oid) == 0 {
  130. return nil, ErrLFSObjectNotExist
  131. }
  132. m := &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}, RepositoryID: repoID}
  133. has, err := db.GetEngine(ctx).Get(m)
  134. if err != nil {
  135. return nil, err
  136. } else if !has {
  137. return nil, ErrLFSObjectNotExist
  138. }
  139. return m, nil
  140. }
  141. // RemoveLFSMetaObjectByOid removes a LFSMetaObject entry from database by its OID.
  142. // It may return ErrLFSObjectNotExist or a database error.
  143. func RemoveLFSMetaObjectByOid(ctx context.Context, repoID int64, oid string) (int64, error) {
  144. return RemoveLFSMetaObjectByOidFn(ctx, repoID, oid, nil)
  145. }
  146. // RemoveLFSMetaObjectByOidFn removes a LFSMetaObject entry from database by its OID.
  147. // It may return ErrLFSObjectNotExist or a database error. It will run Fn with the current count within the transaction
  148. func RemoveLFSMetaObjectByOidFn(ctx context.Context, repoID int64, oid string, fn func(count int64) error) (int64, error) {
  149. if len(oid) == 0 {
  150. return 0, ErrLFSObjectNotExist
  151. }
  152. return db.WithTx2(ctx, func(ctx context.Context) (int64, error) {
  153. m := &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}, RepositoryID: repoID}
  154. if _, err := db.DeleteByBean(ctx, m); err != nil {
  155. return -1, err
  156. }
  157. count, err := db.CountByBean(ctx, &LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}})
  158. if err != nil {
  159. return count, err
  160. }
  161. if fn != nil {
  162. if err := fn(count); err != nil {
  163. return count, err
  164. }
  165. }
  166. return count, nil
  167. })
  168. }
  169. // GetLFSMetaObjects returns all LFSMetaObjects associated with a repository
  170. func GetLFSMetaObjects(ctx context.Context, repoID int64, page, pageSize int) ([]*LFSMetaObject, error) {
  171. sess := db.GetEngine(ctx)
  172. if page >= 0 && pageSize > 0 {
  173. start := 0
  174. if page > 0 {
  175. start = (page - 1) * pageSize
  176. }
  177. sess.Limit(pageSize, start)
  178. }
  179. lfsObjects := make([]*LFSMetaObject, 0, pageSize)
  180. return lfsObjects, sess.Find(&lfsObjects, &LFSMetaObject{RepositoryID: repoID})
  181. }
  182. // CountLFSMetaObjects returns a count of all LFSMetaObjects associated with a repository
  183. func CountLFSMetaObjects(ctx context.Context, repoID int64) (int64, error) {
  184. return db.GetEngine(ctx).Count(&LFSMetaObject{RepositoryID: repoID})
  185. }
  186. // LFSObjectAccessible checks if a provided Oid is accessible to the user
  187. func LFSObjectAccessible(ctx context.Context, user *user_model.User, oid string) (bool, error) {
  188. if user.IsAdmin {
  189. count, err := db.GetEngine(ctx).Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}})
  190. return count > 0, err
  191. }
  192. cond := repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)
  193. count, err := db.GetEngine(ctx).Where(cond).Join("INNER", "repository", "`lfs_meta_object`.repository_id = `repository`.id").Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}})
  194. return count > 0, err
  195. }
  196. // ExistsLFSObject checks if a provided Oid exists within the DB
  197. func ExistsLFSObject(ctx context.Context, oid string) (bool, error) {
  198. return db.GetEngine(ctx).Exist(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}})
  199. }
  200. // LFSAutoAssociate auto associates accessible LFSMetaObjects
  201. func LFSAutoAssociate(ctx context.Context, metas []*LFSMetaObject, user *user_model.User, repoID int64) error {
  202. return db.WithTx(ctx, func(ctx context.Context) error {
  203. oids := make([]any, len(metas))
  204. oidMap := make(map[string]*LFSMetaObject, len(metas))
  205. for i, meta := range metas {
  206. oids[i] = meta.Oid
  207. oidMap[meta.Oid] = meta
  208. }
  209. if !user.IsAdmin {
  210. newMetas := make([]*LFSMetaObject, 0, len(metas))
  211. cond := builder.In(
  212. "`lfs_meta_object`.repository_id",
  213. builder.Select("`repository`.id").From("repository").Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)),
  214. )
  215. if err := db.GetEngine(ctx).Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas); err != nil {
  216. return err
  217. }
  218. if len(newMetas) != len(oidMap) {
  219. return fmt.Errorf("unable collect all LFS objects from database, expected %d, actually %d", len(oidMap), len(newMetas))
  220. }
  221. for i := range newMetas {
  222. newMetas[i].Size = oidMap[newMetas[i].Oid].Size
  223. newMetas[i].RepositoryID = repoID
  224. }
  225. return db.Insert(ctx, newMetas)
  226. }
  227. // admin can associate any LFS object to any repository, and we do not care about errors (eg: duplicated unique key),
  228. // even if error occurs, it won't hurt users and won't make things worse
  229. for i := range metas {
  230. p := lfs.Pointer{Oid: metas[i].Oid, Size: metas[i].Size}
  231. if err := db.Insert(ctx, &LFSMetaObject{
  232. Pointer: p,
  233. RepositoryID: repoID,
  234. }); err != nil {
  235. log.Warn("failed to insert LFS meta object %-v for repo_id: %d into database, err=%v", p, repoID, err)
  236. }
  237. }
  238. return nil
  239. })
  240. }
  241. // CopyLFS copies LFS data from one repo to another
  242. func CopyLFS(ctx context.Context, newRepo, oldRepo *repo_model.Repository) error {
  243. var lfsObjects []*LFSMetaObject
  244. if err := db.GetEngine(ctx).Where("repository_id=?", oldRepo.ID).Find(&lfsObjects); err != nil {
  245. return err
  246. }
  247. for _, v := range lfsObjects {
  248. v.ID = 0
  249. v.RepositoryID = newRepo.ID
  250. if err := db.Insert(ctx, v); err != nil {
  251. return err
  252. }
  253. }
  254. return nil
  255. }
  256. // GetRepoLFSSize return a repository's lfs files size
  257. func GetRepoLFSSize(ctx context.Context, repoID int64) (int64, error) {
  258. lfsSize, err := db.GetEngine(ctx).Where("repository_id = ?", repoID).SumInt(new(LFSMetaObject), "size")
  259. if err != nil {
  260. return 0, fmt.Errorf("updateSize: GetLFSMetaObjects: %w", err)
  261. }
  262. return lfsSize, nil
  263. }
  264. // IterateRepositoryIDsWithLFSMetaObjects iterates across the repositories that have LFSMetaObjects
  265. func IterateRepositoryIDsWithLFSMetaObjects(ctx context.Context, f func(ctx context.Context, repoID, count int64) error) error {
  266. batchSize := setting.Database.IterateBufferSize
  267. sess := db.GetEngine(ctx)
  268. id := int64(0)
  269. type RepositoryCount struct {
  270. RepositoryID int64
  271. Count int64
  272. }
  273. for {
  274. counts := make([]*RepositoryCount, 0, batchSize)
  275. sess.Select("repository_id, COUNT(id) AS count").
  276. Table("lfs_meta_object").
  277. Where("repository_id > ?", id).
  278. GroupBy("repository_id").
  279. OrderBy("repository_id ASC")
  280. if err := sess.Limit(batchSize, 0).Find(&counts); err != nil {
  281. return err
  282. }
  283. if len(counts) == 0 {
  284. return nil
  285. }
  286. for _, count := range counts {
  287. if err := f(ctx, count.RepositoryID, count.Count); err != nil {
  288. return err
  289. }
  290. }
  291. id = counts[len(counts)-1].RepositoryID
  292. }
  293. }
  294. // IterateLFSMetaObjectsForRepoOptions provides options for IterateLFSMetaObjectsForRepo
  295. type IterateLFSMetaObjectsForRepoOptions struct {
  296. OlderThan timeutil.TimeStamp
  297. UpdatedLessRecentlyThan timeutil.TimeStamp
  298. OrderByUpdated bool
  299. LoopFunctionAlwaysUpdates bool
  300. }
  301. // IterateLFSMetaObjectsForRepo provides a iterator for LFSMetaObjects per Repo
  302. func IterateLFSMetaObjectsForRepo(ctx context.Context, repoID int64, f func(context.Context, *LFSMetaObject, int64) error, opts *IterateLFSMetaObjectsForRepoOptions) error {
  303. var start int
  304. batchSize := setting.Database.IterateBufferSize
  305. engine := db.GetEngine(ctx)
  306. type CountLFSMetaObject struct {
  307. Count int64
  308. LFSMetaObject `xorm:"extends"`
  309. }
  310. id := int64(0)
  311. for {
  312. beans := make([]*CountLFSMetaObject, 0, batchSize)
  313. sess := engine.Table("lfs_meta_object").Select("`lfs_meta_object`.*, COUNT(`l1`.oid) AS `count`").
  314. Join("INNER", "`lfs_meta_object` AS l1", "`lfs_meta_object`.oid = `l1`.oid").
  315. Where("`lfs_meta_object`.repository_id = ?", repoID)
  316. if !opts.OlderThan.IsZero() {
  317. sess.And("`lfs_meta_object`.created_unix < ?", opts.OlderThan)
  318. }
  319. if !opts.UpdatedLessRecentlyThan.IsZero() {
  320. sess.And("`lfs_meta_object`.updated_unix < ?", opts.UpdatedLessRecentlyThan)
  321. }
  322. sess.GroupBy("`lfs_meta_object`.id")
  323. if opts.OrderByUpdated {
  324. sess.OrderBy("`lfs_meta_object`.updated_unix ASC")
  325. } else {
  326. sess.And("`lfs_meta_object`.id > ?", id)
  327. sess.OrderBy("`lfs_meta_object`.id ASC")
  328. }
  329. if err := sess.Limit(batchSize, start).Find(&beans); err != nil {
  330. return err
  331. }
  332. if len(beans) == 0 {
  333. return nil
  334. }
  335. if !opts.LoopFunctionAlwaysUpdates {
  336. start += len(beans)
  337. }
  338. for _, bean := range beans {
  339. if err := f(ctx, &bean.LFSMetaObject, bean.Count); err != nil {
  340. return err
  341. }
  342. }
  343. id = beans[len(beans)-1].ID
  344. }
  345. }
  346. // MarkLFSMetaObject updates the updated time for the provided LFSMetaObject
  347. func MarkLFSMetaObject(ctx context.Context, id int64) error {
  348. obj := &LFSMetaObject{
  349. UpdatedUnix: timeutil.TimeStampNow(),
  350. }
  351. count, err := db.GetEngine(ctx).ID(id).Update(obj)
  352. if count != 1 {
  353. log.Error("Unexpectedly updated %d LFSMetaObjects with ID: %d", count, id)
  354. }
  355. return err
  356. }