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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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. "strconv"
  12. "strings"
  13. "code.gitea.io/gitea/models/db"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. user_model "code.gitea.io/gitea/models/user"
  16. "code.gitea.io/gitea/modules/structs"
  17. "code.gitea.io/gitea/modules/timeutil"
  18. "code.gitea.io/gitea/modules/util"
  19. "xorm.io/builder"
  20. )
  21. // Release represents a release of repository.
  22. type Release struct {
  23. ID int64 `xorm:"pk autoincr"`
  24. RepoID int64 `xorm:"INDEX UNIQUE(n)"`
  25. Repo *repo_model.Repository `xorm:"-"`
  26. PublisherID int64 `xorm:"INDEX"`
  27. Publisher *user_model.User `xorm:"-"`
  28. TagName string `xorm:"INDEX UNIQUE(n)"`
  29. OriginalAuthor string
  30. OriginalAuthorID int64 `xorm:"index"`
  31. LowerTagName string
  32. Target string
  33. Title string
  34. Sha1 string `xorm:"VARCHAR(40)"`
  35. NumCommits int64
  36. NumCommitsBehind int64 `xorm:"-"`
  37. Note string `xorm:"TEXT"`
  38. RenderedNote string `xorm:"-"`
  39. IsDraft bool `xorm:"NOT NULL DEFAULT false"`
  40. IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
  41. IsTag bool `xorm:"NOT NULL DEFAULT false"`
  42. Attachments []*repo_model.Attachment `xorm:"-"`
  43. CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
  44. }
  45. func init() {
  46. db.RegisterModel(new(Release))
  47. }
  48. func (r *Release) loadAttributes(e db.Engine) error {
  49. var err error
  50. if r.Repo == nil {
  51. r.Repo, err = repo_model.GetRepositoryByID(r.RepoID)
  52. if err != nil {
  53. return err
  54. }
  55. }
  56. if r.Publisher == nil {
  57. r.Publisher, err = user_model.GetUserByIDEngine(e, r.PublisherID)
  58. if err != nil {
  59. if user_model.IsErrUserNotExist(err) {
  60. r.Publisher = user_model.NewGhostUser()
  61. } else {
  62. return err
  63. }
  64. }
  65. }
  66. return getReleaseAttachments(e, r)
  67. }
  68. // LoadAttributes load repo and publisher attributes for a release
  69. func (r *Release) LoadAttributes() error {
  70. return r.loadAttributes(db.GetEngine(db.DefaultContext))
  71. }
  72. // APIURL the api url for a release. release must have attributes loaded
  73. func (r *Release) APIURL() string {
  74. return r.Repo.APIURL() + "/releases/" + strconv.FormatInt(r.ID, 10)
  75. }
  76. // ZipURL the zip url for a release. release must have attributes loaded
  77. func (r *Release) ZipURL() string {
  78. return r.Repo.HTMLURL() + "/archive/" + util.PathEscapeSegments(r.TagName) + ".zip"
  79. }
  80. // TarURL the tar.gz url for a release. release must have attributes loaded
  81. func (r *Release) TarURL() string {
  82. return r.Repo.HTMLURL() + "/archive/" + util.PathEscapeSegments(r.TagName) + ".tar.gz"
  83. }
  84. // HTMLURL the url for a release on the web UI. release must have attributes loaded
  85. func (r *Release) HTMLURL() string {
  86. return r.Repo.HTMLURL() + "/releases/tag/" + util.PathEscapeSegments(r.TagName)
  87. }
  88. // IsReleaseExist returns true if release with given tag name already exists.
  89. func IsReleaseExist(repoID int64, tagName string) (bool, error) {
  90. if len(tagName) == 0 {
  91. return false, nil
  92. }
  93. return db.GetEngine(db.DefaultContext).Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
  94. }
  95. // InsertRelease inserts a release
  96. func InsertRelease(rel *Release) error {
  97. _, err := db.GetEngine(db.DefaultContext).Insert(rel)
  98. return err
  99. }
  100. // InsertReleasesContext insert releases
  101. func InsertReleasesContext(ctx context.Context, rels []*Release) error {
  102. _, err := db.GetEngine(ctx).Insert(rels)
  103. return err
  104. }
  105. // UpdateRelease updates all columns of a release
  106. func UpdateRelease(ctx context.Context, rel *Release) error {
  107. _, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
  108. return err
  109. }
  110. // AddReleaseAttachments adds a release attachments
  111. func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
  112. // Check attachments
  113. attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, attachmentUUIDs)
  114. if err != nil {
  115. return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err)
  116. }
  117. for i := range attachments {
  118. if attachments[i].ReleaseID != 0 {
  119. return errors.New("release permission denied")
  120. }
  121. attachments[i].ReleaseID = releaseID
  122. // No assign value could be 0, so ignore AllCols().
  123. if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
  124. return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
  125. }
  126. }
  127. return
  128. }
  129. // GetRelease returns release by given ID.
  130. func GetRelease(repoID int64, tagName string) (*Release, error) {
  131. isExist, err := IsReleaseExist(repoID, tagName)
  132. if err != nil {
  133. return nil, err
  134. } else if !isExist {
  135. return nil, ErrReleaseNotExist{0, tagName}
  136. }
  137. rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
  138. _, err = db.GetEngine(db.DefaultContext).Get(rel)
  139. return rel, err
  140. }
  141. // GetReleaseByID returns release with given ID.
  142. func GetReleaseByID(id int64) (*Release, error) {
  143. rel := new(Release)
  144. has, err := db.GetEngine(db.DefaultContext).
  145. ID(id).
  146. Get(rel)
  147. if err != nil {
  148. return nil, err
  149. } else if !has {
  150. return nil, ErrReleaseNotExist{id, ""}
  151. }
  152. return rel, nil
  153. }
  154. // FindReleasesOptions describes the conditions to Find releases
  155. type FindReleasesOptions struct {
  156. db.ListOptions
  157. IncludeDrafts bool
  158. IncludeTags bool
  159. IsPreRelease util.OptionalBool
  160. IsDraft util.OptionalBool
  161. TagNames []string
  162. }
  163. func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
  164. cond := builder.NewCond()
  165. cond = cond.And(builder.Eq{"repo_id": repoID})
  166. if !opts.IncludeDrafts {
  167. cond = cond.And(builder.Eq{"is_draft": false})
  168. }
  169. if !opts.IncludeTags {
  170. cond = cond.And(builder.Eq{"is_tag": false})
  171. }
  172. if len(opts.TagNames) > 0 {
  173. cond = cond.And(builder.In("tag_name", opts.TagNames))
  174. }
  175. if !opts.IsPreRelease.IsNone() {
  176. cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.IsTrue()})
  177. }
  178. if !opts.IsDraft.IsNone() {
  179. cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()})
  180. }
  181. return cond
  182. }
  183. // GetReleasesByRepoID returns a list of releases of repository.
  184. func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions) ([]*Release, error) {
  185. sess := db.GetEngine(db.DefaultContext).
  186. Desc("created_unix", "id").
  187. Where(opts.toConds(repoID))
  188. if opts.PageSize != 0 {
  189. sess = db.SetSessionPagination(sess, &opts.ListOptions)
  190. }
  191. rels := make([]*Release, 0, opts.PageSize)
  192. return rels, sess.Find(&rels)
  193. }
  194. // CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID.
  195. func CountReleasesByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) {
  196. return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(new(Release))
  197. }
  198. // GetLatestReleaseByRepoID returns the latest release for a repository
  199. func GetLatestReleaseByRepoID(repoID int64) (*Release, error) {
  200. cond := builder.NewCond().
  201. And(builder.Eq{"repo_id": repoID}).
  202. And(builder.Eq{"is_draft": false}).
  203. And(builder.Eq{"is_prerelease": false}).
  204. And(builder.Eq{"is_tag": false})
  205. rel := new(Release)
  206. has, err := db.GetEngine(db.DefaultContext).
  207. Desc("created_unix", "id").
  208. Where(cond).
  209. Get(rel)
  210. if err != nil {
  211. return nil, err
  212. } else if !has {
  213. return nil, ErrReleaseNotExist{0, "latest"}
  214. }
  215. return rel, nil
  216. }
  217. // GetReleasesByRepoIDAndNames returns a list of releases of repository according repoID and tagNames.
  218. func GetReleasesByRepoIDAndNames(ctx context.Context, repoID int64, tagNames []string) (rels []*Release, err error) {
  219. err = db.GetEngine(ctx).
  220. In("tag_name", tagNames).
  221. Desc("created_unix").
  222. Find(&rels, Release{RepoID: repoID})
  223. return rels, err
  224. }
  225. // GetReleaseCountByRepoID returns the count of releases of repository
  226. func GetReleaseCountByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) {
  227. return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(&Release{})
  228. }
  229. type releaseMetaSearch struct {
  230. ID []int64
  231. Rel []*Release
  232. }
  233. func (s releaseMetaSearch) Len() int {
  234. return len(s.ID)
  235. }
  236. func (s releaseMetaSearch) Swap(i, j int) {
  237. s.ID[i], s.ID[j] = s.ID[j], s.ID[i]
  238. s.Rel[i], s.Rel[j] = s.Rel[j], s.Rel[i]
  239. }
  240. func (s releaseMetaSearch) Less(i, j int) bool {
  241. return s.ID[i] < s.ID[j]
  242. }
  243. // GetReleaseAttachments retrieves the attachments for releases
  244. func GetReleaseAttachments(rels ...*Release) (err error) {
  245. return getReleaseAttachments(db.GetEngine(db.DefaultContext), rels...)
  246. }
  247. func getReleaseAttachments(e db.Engine, rels ...*Release) (err error) {
  248. if len(rels) == 0 {
  249. return
  250. }
  251. // To keep this efficient as possible sort all releases by id,
  252. // select attachments by release id,
  253. // then merge join them
  254. // Sort
  255. sortedRels := releaseMetaSearch{ID: make([]int64, len(rels)), Rel: make([]*Release, len(rels))}
  256. var attachments []*repo_model.Attachment
  257. for index, element := range rels {
  258. element.Attachments = []*repo_model.Attachment{}
  259. sortedRels.ID[index] = element.ID
  260. sortedRels.Rel[index] = element
  261. }
  262. sort.Sort(sortedRels)
  263. // Select attachments
  264. err = e.
  265. Asc("release_id", "name").
  266. In("release_id", sortedRels.ID).
  267. Find(&attachments, repo_model.Attachment{})
  268. if err != nil {
  269. return err
  270. }
  271. // merge join
  272. currentIndex := 0
  273. for _, attachment := range attachments {
  274. for sortedRels.ID[currentIndex] < attachment.ReleaseID {
  275. currentIndex++
  276. }
  277. sortedRels.Rel[currentIndex].Attachments = append(sortedRels.Rel[currentIndex].Attachments, attachment)
  278. }
  279. return
  280. }
  281. type releaseSorter struct {
  282. rels []*Release
  283. }
  284. func (rs *releaseSorter) Len() int {
  285. return len(rs.rels)
  286. }
  287. func (rs *releaseSorter) Less(i, j int) bool {
  288. diffNum := rs.rels[i].NumCommits - rs.rels[j].NumCommits
  289. if diffNum != 0 {
  290. return diffNum > 0
  291. }
  292. return rs.rels[i].CreatedUnix > rs.rels[j].CreatedUnix
  293. }
  294. func (rs *releaseSorter) Swap(i, j int) {
  295. rs.rels[i], rs.rels[j] = rs.rels[j], rs.rels[i]
  296. }
  297. // SortReleases sorts releases by number of commits and created time.
  298. func SortReleases(rels []*Release) {
  299. sorter := &releaseSorter{rels: rels}
  300. sort.Sort(sorter)
  301. }
  302. // DeleteReleaseByID deletes a release from database by given ID.
  303. func DeleteReleaseByID(id int64) error {
  304. _, err := db.GetEngine(db.DefaultContext).ID(id).Delete(new(Release))
  305. return err
  306. }
  307. // UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID
  308. func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error {
  309. _, err := db.GetEngine(db.DefaultContext).Table("release").
  310. Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType).
  311. And("original_author_id = ?", originalAuthorID).
  312. Update(map[string]interface{}{
  313. "publisher_id": posterID,
  314. "original_author": "",
  315. "original_author_id": 0,
  316. })
  317. return err
  318. }
  319. // PushUpdateDeleteTagsContext updates a number of delete tags with context
  320. func PushUpdateDeleteTagsContext(ctx context.Context, repo *repo_model.Repository, tags []string) error {
  321. return pushUpdateDeleteTags(db.GetEngine(ctx), repo, tags)
  322. }
  323. func pushUpdateDeleteTags(e db.Engine, repo *repo_model.Repository, tags []string) error {
  324. if len(tags) == 0 {
  325. return nil
  326. }
  327. lowerTags := make([]string, 0, len(tags))
  328. for _, tag := range tags {
  329. lowerTags = append(lowerTags, strings.ToLower(tag))
  330. }
  331. if _, err := e.
  332. Where("repo_id = ? AND is_tag = ?", repo.ID, true).
  333. In("lower_tag_name", lowerTags).
  334. Delete(new(Release)); err != nil {
  335. return fmt.Errorf("Delete: %v", err)
  336. }
  337. if _, err := e.
  338. Where("repo_id = ? AND is_tag = ?", repo.ID, false).
  339. In("lower_tag_name", lowerTags).
  340. Cols("is_draft", "num_commits", "sha1").
  341. Update(&Release{
  342. IsDraft: true,
  343. }); err != nil {
  344. return fmt.Errorf("Update: %v", err)
  345. }
  346. return nil
  347. }
  348. // PushUpdateDeleteTag must be called for any push actions to delete tag
  349. func PushUpdateDeleteTag(repo *repo_model.Repository, tagName string) error {
  350. rel, err := GetRelease(repo.ID, tagName)
  351. if err != nil {
  352. if IsErrReleaseNotExist(err) {
  353. return nil
  354. }
  355. return fmt.Errorf("GetRelease: %v", err)
  356. }
  357. if rel.IsTag {
  358. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil {
  359. return fmt.Errorf("Delete: %v", err)
  360. }
  361. } else {
  362. rel.IsDraft = true
  363. rel.NumCommits = 0
  364. rel.Sha1 = ""
  365. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil {
  366. return fmt.Errorf("Update: %v", err)
  367. }
  368. }
  369. return nil
  370. }
  371. // SaveOrUpdateTag must be called for any push actions to add tag
  372. func SaveOrUpdateTag(repo *repo_model.Repository, newRel *Release) error {
  373. rel, err := GetRelease(repo.ID, newRel.TagName)
  374. if err != nil && !IsErrReleaseNotExist(err) {
  375. return fmt.Errorf("GetRelease: %v", err)
  376. }
  377. if rel == nil {
  378. rel = newRel
  379. if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil {
  380. return fmt.Errorf("InsertOne: %v", err)
  381. }
  382. } else {
  383. rel.Sha1 = newRel.Sha1
  384. rel.CreatedUnix = newRel.CreatedUnix
  385. rel.NumCommits = newRel.NumCommits
  386. rel.IsDraft = false
  387. if rel.IsTag && newRel.PublisherID > 0 {
  388. rel.PublisherID = newRel.PublisherID
  389. }
  390. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil {
  391. return fmt.Errorf("Update: %v", err)
  392. }
  393. }
  394. return nil
  395. }
  396. // RemapExternalUser ExternalUserRemappable interface
  397. func (r *Release) RemapExternalUser(externalName string, externalID, userID int64) error {
  398. r.OriginalAuthor = externalName
  399. r.OriginalAuthorID = externalID
  400. r.PublisherID = userID
  401. return nil
  402. }
  403. // UserID ExternalUserRemappable interface
  404. func (r *Release) GetUserID() int64 { return r.PublisherID }
  405. // ExternalName ExternalUserRemappable interface
  406. func (r *Release) GetExternalName() string { return r.OriginalAuthor }
  407. // ExternalID ExternalUserRemappable interface
  408. func (r *Release) GetExternalID() int64 { return r.OriginalAuthorID }