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 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package models
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "code.gitea.io/gitea/modules/timeutil"
  9. "xorm.io/builder"
  10. )
  11. // LFSMetaObject stores metadata for LFS tracked files.
  12. type LFSMetaObject struct {
  13. ID int64 `xorm:"pk autoincr"`
  14. Oid string `xorm:"UNIQUE(s) INDEX NOT NULL"`
  15. Size int64 `xorm:"NOT NULL"`
  16. RepositoryID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  17. Existing bool `xorm:"-"`
  18. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  19. }
  20. // Pointer returns the string representation of an LFS pointer file
  21. func (m *LFSMetaObject) Pointer() string {
  22. return fmt.Sprintf("%s\n%s%s\nsize %d\n", LFSMetaFileIdentifier, LFSMetaFileOidPrefix, m.Oid, m.Size)
  23. }
  24. // LFSTokenResponse defines the JSON structure in which the JWT token is stored.
  25. // This structure is fetched via SSH and passed by the Git LFS client to the server
  26. // endpoint for authorization.
  27. type LFSTokenResponse struct {
  28. Header map[string]string `json:"header"`
  29. Href string `json:"href"`
  30. }
  31. var (
  32. // ErrLFSObjectNotExist is returned from lfs models functions in order
  33. // to differentiate between database and missing object errors.
  34. ErrLFSObjectNotExist = errors.New("LFS Meta object does not exist")
  35. )
  36. const (
  37. // LFSMetaFileIdentifier is the string appearing at the first line of LFS pointer files.
  38. // https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
  39. LFSMetaFileIdentifier = "version https://git-lfs.github.com/spec/v1"
  40. // LFSMetaFileOidPrefix appears in LFS pointer files on a line before the sha256 hash.
  41. LFSMetaFileOidPrefix = "oid sha256:"
  42. )
  43. // NewLFSMetaObject stores a given populated LFSMetaObject structure in the database
  44. // if it is not already present.
  45. func NewLFSMetaObject(m *LFSMetaObject) (*LFSMetaObject, error) {
  46. var err error
  47. sess := x.NewSession()
  48. defer sess.Close()
  49. if err = sess.Begin(); err != nil {
  50. return nil, err
  51. }
  52. has, err := sess.Get(m)
  53. if err != nil {
  54. return nil, err
  55. }
  56. if has {
  57. m.Existing = true
  58. return m, sess.Commit()
  59. }
  60. if _, err = sess.Insert(m); err != nil {
  61. return nil, err
  62. }
  63. return m, sess.Commit()
  64. }
  65. // GenerateLFSOid generates a Sha256Sum to represent an oid for arbitrary content
  66. func GenerateLFSOid(content io.Reader) (string, error) {
  67. h := sha256.New()
  68. if _, err := io.Copy(h, content); err != nil {
  69. return "", err
  70. }
  71. sum := h.Sum(nil)
  72. return hex.EncodeToString(sum), nil
  73. }
  74. // GetLFSMetaObjectByOid selects a LFSMetaObject entry from database by its OID.
  75. // It may return ErrLFSObjectNotExist or a database error. If the error is nil,
  76. // the returned pointer is a valid LFSMetaObject.
  77. func (repo *Repository) GetLFSMetaObjectByOid(oid string) (*LFSMetaObject, error) {
  78. if len(oid) == 0 {
  79. return nil, ErrLFSObjectNotExist
  80. }
  81. m := &LFSMetaObject{Oid: oid, RepositoryID: repo.ID}
  82. has, err := x.Get(m)
  83. if err != nil {
  84. return nil, err
  85. } else if !has {
  86. return nil, ErrLFSObjectNotExist
  87. }
  88. return m, nil
  89. }
  90. // RemoveLFSMetaObjectByOid removes a LFSMetaObject entry from database by its OID.
  91. // It may return ErrLFSObjectNotExist or a database error.
  92. func (repo *Repository) RemoveLFSMetaObjectByOid(oid string) (int64, error) {
  93. if len(oid) == 0 {
  94. return 0, ErrLFSObjectNotExist
  95. }
  96. sess := x.NewSession()
  97. defer sess.Close()
  98. if err := sess.Begin(); err != nil {
  99. return -1, err
  100. }
  101. m := &LFSMetaObject{Oid: oid, RepositoryID: repo.ID}
  102. if _, err := sess.Delete(m); err != nil {
  103. return -1, err
  104. }
  105. count, err := sess.Count(&LFSMetaObject{Oid: oid})
  106. if err != nil {
  107. return count, err
  108. }
  109. return count, sess.Commit()
  110. }
  111. // GetLFSMetaObjects returns all LFSMetaObjects associated with a repository
  112. func (repo *Repository) GetLFSMetaObjects(page, pageSize int) ([]*LFSMetaObject, error) {
  113. sess := x.NewSession()
  114. defer sess.Close()
  115. if page >= 0 && pageSize > 0 {
  116. start := 0
  117. if page > 0 {
  118. start = (page - 1) * pageSize
  119. }
  120. sess.Limit(pageSize, start)
  121. }
  122. lfsObjects := make([]*LFSMetaObject, 0, pageSize)
  123. return lfsObjects, sess.Find(&lfsObjects, &LFSMetaObject{RepositoryID: repo.ID})
  124. }
  125. // CountLFSMetaObjects returns a count of all LFSMetaObjects associated with a repository
  126. func (repo *Repository) CountLFSMetaObjects() (int64, error) {
  127. return x.Count(&LFSMetaObject{RepositoryID: repo.ID})
  128. }
  129. // LFSObjectAccessible checks if a provided Oid is accessible to the user
  130. func LFSObjectAccessible(user *User, oid string) (bool, error) {
  131. if user.IsAdmin {
  132. count, err := x.Count(&LFSMetaObject{Oid: oid})
  133. return (count > 0), err
  134. }
  135. cond := accessibleRepositoryCondition(user)
  136. count, err := x.Where(cond).Join("INNER", "repository", "`lfs_meta_object`.repository_id = `repository`.id").Count(&LFSMetaObject{Oid: oid})
  137. return (count > 0), err
  138. }
  139. // LFSAutoAssociate auto associates accessible LFSMetaObjects
  140. func LFSAutoAssociate(metas []*LFSMetaObject, user *User, repoID int64) error {
  141. sess := x.NewSession()
  142. defer sess.Close()
  143. if err := sess.Begin(); err != nil {
  144. return err
  145. }
  146. oids := make([]interface{}, len(metas))
  147. oidMap := make(map[string]*LFSMetaObject, len(metas))
  148. for i, meta := range metas {
  149. oids[i] = meta.Oid
  150. oidMap[meta.Oid] = meta
  151. }
  152. cond := builder.NewCond()
  153. if !user.IsAdmin {
  154. cond = builder.In("`lfs_meta_object`.repository_id",
  155. builder.Select("`repository`.id").From("repository").Where(accessibleRepositoryCondition(user)))
  156. }
  157. newMetas := make([]*LFSMetaObject, 0, len(metas))
  158. if err := sess.Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas); err != nil {
  159. return err
  160. }
  161. for i := range newMetas {
  162. newMetas[i].Size = oidMap[newMetas[i].Oid].Size
  163. newMetas[i].RepositoryID = repoID
  164. }
  165. if _, err := sess.InsertMulti(newMetas); err != nil {
  166. return err
  167. }
  168. return sess.Commit()
  169. }