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.

release.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package models
  6. import (
  7. "context"
  8. "errors"
  9. "fmt"
  10. "sort"
  11. "strings"
  12. "code.gitea.io/gitea/models/db"
  13. "code.gitea.io/gitea/modules/setting"
  14. "code.gitea.io/gitea/modules/structs"
  15. "code.gitea.io/gitea/modules/timeutil"
  16. "code.gitea.io/gitea/modules/util"
  17. "xorm.io/builder"
  18. )
  19. // Release represents a release of repository.
  20. type Release struct {
  21. ID int64 `xorm:"pk autoincr"`
  22. RepoID int64 `xorm:"INDEX UNIQUE(n)"`
  23. Repo *Repository `xorm:"-"`
  24. PublisherID int64 `xorm:"INDEX"`
  25. Publisher *User `xorm:"-"`
  26. TagName string `xorm:"INDEX UNIQUE(n)"`
  27. OriginalAuthor string
  28. OriginalAuthorID int64 `xorm:"index"`
  29. LowerTagName string
  30. Target string
  31. Title string
  32. Sha1 string `xorm:"VARCHAR(40)"`
  33. NumCommits int64
  34. NumCommitsBehind int64 `xorm:"-"`
  35. Note string `xorm:"TEXT"`
  36. RenderedNote string `xorm:"-"`
  37. IsDraft bool `xorm:"NOT NULL DEFAULT false"`
  38. IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
  39. IsTag bool `xorm:"NOT NULL DEFAULT false"`
  40. Attachments []*Attachment `xorm:"-"`
  41. CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
  42. }
  43. func init() {
  44. db.RegisterModel(new(Release))
  45. }
  46. func (r *Release) loadAttributes(e db.Engine) error {
  47. var err error
  48. if r.Repo == nil {
  49. r.Repo, err = GetRepositoryByID(r.RepoID)
  50. if err != nil {
  51. return err
  52. }
  53. }
  54. if r.Publisher == nil {
  55. r.Publisher, err = getUserByID(e, r.PublisherID)
  56. if err != nil {
  57. if IsErrUserNotExist(err) {
  58. r.Publisher = NewGhostUser()
  59. } else {
  60. return err
  61. }
  62. }
  63. }
  64. return getReleaseAttachments(e, r)
  65. }
  66. // LoadAttributes load repo and publisher attributes for a release
  67. func (r *Release) LoadAttributes() error {
  68. return r.loadAttributes(db.GetEngine(db.DefaultContext))
  69. }
  70. // APIURL the api url for a release. release must have attributes loaded
  71. func (r *Release) APIURL() string {
  72. return fmt.Sprintf("%sapi/v1/repos/%s/releases/%d",
  73. setting.AppURL, r.Repo.FullName(), r.ID)
  74. }
  75. // ZipURL the zip url for a release. release must have attributes loaded
  76. func (r *Release) ZipURL() string {
  77. return fmt.Sprintf("%s/archive/%s.zip", r.Repo.HTMLURL(), r.TagName)
  78. }
  79. // TarURL the tar.gz url for a release. release must have attributes loaded
  80. func (r *Release) TarURL() string {
  81. return fmt.Sprintf("%s/archive/%s.tar.gz", r.Repo.HTMLURL(), r.TagName)
  82. }
  83. // HTMLURL the url for a release on the web UI. release must have attributes loaded
  84. func (r *Release) HTMLURL() string {
  85. return fmt.Sprintf("%s/releases/tag/%s", r.Repo.HTMLURL(), r.TagName)
  86. }
  87. // IsReleaseExist returns true if release with given tag name already exists.
  88. func IsReleaseExist(repoID int64, tagName string) (bool, error) {
  89. if len(tagName) == 0 {
  90. return false, nil
  91. }
  92. return db.GetEngine(db.DefaultContext).Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
  93. }
  94. // InsertRelease inserts a release
  95. func InsertRelease(rel *Release) error {
  96. _, err := db.GetEngine(db.DefaultContext).Insert(rel)
  97. return err
  98. }
  99. // InsertReleasesContext insert releases
  100. func InsertReleasesContext(ctx context.Context, rels []*Release) error {
  101. _, err := db.GetEngine(ctx).Insert(rels)
  102. return err
  103. }
  104. // UpdateRelease updates all columns of a release
  105. func UpdateRelease(ctx context.Context, rel *Release) error {
  106. _, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
  107. return err
  108. }
  109. // AddReleaseAttachments adds a release attachments
  110. func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
  111. // Check attachments
  112. attachments, err := getAttachmentsByUUIDs(db.GetEngine(ctx), attachmentUUIDs)
  113. if err != nil {
  114. return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err)
  115. }
  116. for i := range attachments {
  117. if attachments[i].ReleaseID != 0 {
  118. return errors.New("release permission denied")
  119. }
  120. attachments[i].ReleaseID = releaseID
  121. // No assign value could be 0, so ignore AllCols().
  122. if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
  123. return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
  124. }
  125. }
  126. return
  127. }
  128. // GetRelease returns release by given ID.
  129. func GetRelease(repoID int64, tagName string) (*Release, error) {
  130. isExist, err := IsReleaseExist(repoID, tagName)
  131. if err != nil {
  132. return nil, err
  133. } else if !isExist {
  134. return nil, ErrReleaseNotExist{0, tagName}
  135. }
  136. rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
  137. _, err = db.GetEngine(db.DefaultContext).Get(rel)
  138. return rel, err
  139. }
  140. // GetReleaseByID returns release with given ID.
  141. func GetReleaseByID(id int64) (*Release, error) {
  142. rel := new(Release)
  143. has, err := db.GetEngine(db.DefaultContext).
  144. ID(id).
  145. Get(rel)
  146. if err != nil {
  147. return nil, err
  148. } else if !has {
  149. return nil, ErrReleaseNotExist{id, ""}
  150. }
  151. return rel, nil
  152. }
  153. // FindReleasesOptions describes the conditions to Find releases
  154. type FindReleasesOptions struct {
  155. db.ListOptions
  156. IncludeDrafts bool
  157. IncludeTags bool
  158. IsPreRelease util.OptionalBool
  159. IsDraft util.OptionalBool
  160. TagNames []string
  161. }
  162. func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
  163. cond := builder.NewCond()
  164. cond = cond.And(builder.Eq{"repo_id": repoID})
  165. if !opts.IncludeDrafts {
  166. cond = cond.And(builder.Eq{"is_draft": false})
  167. }
  168. if !opts.IncludeTags {
  169. cond = cond.And(builder.Eq{"is_tag": false})
  170. }
  171. if len(opts.TagNames) > 0 {
  172. cond = cond.And(builder.In("tag_name", opts.TagNames))
  173. }
  174. if !opts.IsPreRelease.IsNone() {
  175. cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.IsTrue()})
  176. }
  177. if !opts.IsDraft.IsNone() {
  178. cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()})
  179. }
  180. return cond
  181. }
  182. // GetReleasesByRepoID returns a list of releases of repository.
  183. func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions) ([]*Release, error) {
  184. sess := db.GetEngine(db.DefaultContext).
  185. Desc("created_unix", "id").
  186. Where(opts.toConds(repoID))
  187. if opts.PageSize != 0 {
  188. sess = db.SetSessionPagination(sess, &opts.ListOptions)
  189. }
  190. rels := make([]*Release, 0, opts.PageSize)
  191. return rels, sess.Find(&rels)
  192. }
  193. // CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID.
  194. func CountReleasesByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) {
  195. return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(new(Release))
  196. }
  197. // GetLatestReleaseByRepoID returns the latest release for a repository
  198. func GetLatestReleaseByRepoID(repoID int64) (*Release, error) {
  199. cond := builder.NewCond().
  200. And(builder.Eq{"repo_id": repoID}).
  201. And(builder.Eq{"is_draft": false}).
  202. And(builder.Eq{"is_prerelease": false}).
  203. And(builder.Eq{"is_tag": false})
  204. rel := new(Release)
  205. has, err := db.GetEngine(db.DefaultContext).
  206. Desc("created_unix", "id").
  207. Where(cond).
  208. Get(rel)
  209. if err != nil {
  210. return nil, err
  211. } else if !has {
  212. return nil, ErrReleaseNotExist{0, "latest"}
  213. }
  214. return rel, nil
  215. }
  216. // GetReleasesByRepoIDAndNames returns a list of releases of repository according repoID and tagNames.
  217. func GetReleasesByRepoIDAndNames(ctx context.Context, repoID int64, tagNames []string) (rels []*Release, err error) {
  218. err = db.GetEngine(ctx).
  219. In("tag_name", tagNames).
  220. Desc("created_unix").
  221. Find(&rels, Release{RepoID: repoID})
  222. return rels, err
  223. }
  224. // GetReleaseCountByRepoID returns the count of releases of repository
  225. func GetReleaseCountByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) {
  226. return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(&Release{})
  227. }
  228. type releaseMetaSearch struct {
  229. ID []int64
  230. Rel []*Release
  231. }
  232. func (s releaseMetaSearch) Len() int {
  233. return len(s.ID)
  234. }
  235. func (s releaseMetaSearch) Swap(i, j int) {
  236. s.ID[i], s.ID[j] = s.ID[j], s.ID[i]
  237. s.Rel[i], s.Rel[j] = s.Rel[j], s.Rel[i]
  238. }
  239. func (s releaseMetaSearch) Less(i, j int) bool {
  240. return s.ID[i] < s.ID[j]
  241. }
  242. // GetReleaseAttachments retrieves the attachments for releases
  243. func GetReleaseAttachments(rels ...*Release) (err error) {
  244. return getReleaseAttachments(db.GetEngine(db.DefaultContext), rels...)
  245. }
  246. func getReleaseAttachments(e db.Engine, rels ...*Release) (err error) {
  247. if len(rels) == 0 {
  248. return
  249. }
  250. // To keep this efficient as possible sort all releases by id,
  251. // select attachments by release id,
  252. // then merge join them
  253. // Sort
  254. sortedRels := releaseMetaSearch{ID: make([]int64, len(rels)), Rel: make([]*Release, len(rels))}
  255. var attachments []*Attachment
  256. for index, element := range rels {
  257. element.Attachments = []*Attachment{}
  258. sortedRels.ID[index] = element.ID
  259. sortedRels.Rel[index] = element
  260. }
  261. sort.Sort(sortedRels)
  262. // Select attachments
  263. err = e.
  264. Asc("release_id", "name").
  265. In("release_id", sortedRels.ID).
  266. Find(&attachments, Attachment{})
  267. if err != nil {
  268. return err
  269. }
  270. // merge join
  271. currentIndex := 0
  272. for _, attachment := range attachments {
  273. for sortedRels.ID[currentIndex] < attachment.ReleaseID {
  274. currentIndex++
  275. }
  276. sortedRels.Rel[currentIndex].Attachments = append(sortedRels.Rel[currentIndex].Attachments, attachment)
  277. }
  278. return
  279. }
  280. type releaseSorter struct {
  281. rels []*Release
  282. }
  283. func (rs *releaseSorter) Len() int {
  284. return len(rs.rels)
  285. }
  286. func (rs *releaseSorter) Less(i, j int) bool {
  287. diffNum := rs.rels[i].NumCommits - rs.rels[j].NumCommits
  288. if diffNum != 0 {
  289. return diffNum > 0
  290. }
  291. return rs.rels[i].CreatedUnix > rs.rels[j].CreatedUnix
  292. }
  293. func (rs *releaseSorter) Swap(i, j int) {
  294. rs.rels[i], rs.rels[j] = rs.rels[j], rs.rels[i]
  295. }
  296. // SortReleases sorts releases by number of commits and created time.
  297. func SortReleases(rels []*Release) {
  298. sorter := &releaseSorter{rels: rels}
  299. sort.Sort(sorter)
  300. }
  301. // DeleteReleaseByID deletes a release from database by given ID.
  302. func DeleteReleaseByID(id int64) error {
  303. _, err := db.GetEngine(db.DefaultContext).ID(id).Delete(new(Release))
  304. return err
  305. }
  306. // UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID
  307. func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error {
  308. _, err := db.GetEngine(db.DefaultContext).Table("release").
  309. Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType).
  310. And("original_author_id = ?", originalAuthorID).
  311. Update(map[string]interface{}{
  312. "publisher_id": posterID,
  313. "original_author": "",
  314. "original_author_id": 0,
  315. })
  316. return err
  317. }