You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lfs.go 13KB

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