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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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(ctx context.Context) 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.GetUserByIDCtx(ctx, 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(ctx, r)
  67. }
  68. // LoadAttributes load repo and publisher attributes for a release
  69. func (r *Release) LoadAttributes() error {
  70. return r.loadAttributes(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(ctx context.Context, rels ...*Release) (err error) {
  245. if len(rels) == 0 {
  246. return
  247. }
  248. // To keep this efficient as possible sort all releases by id,
  249. // select attachments by release id,
  250. // then merge join them
  251. // Sort
  252. sortedRels := releaseMetaSearch{ID: make([]int64, len(rels)), Rel: make([]*Release, len(rels))}
  253. var attachments []*repo_model.Attachment
  254. for index, element := range rels {
  255. element.Attachments = []*repo_model.Attachment{}
  256. sortedRels.ID[index] = element.ID
  257. sortedRels.Rel[index] = element
  258. }
  259. sort.Sort(sortedRels)
  260. // Select attachments
  261. err = db.GetEngine(ctx).
  262. Asc("release_id", "name").
  263. In("release_id", sortedRels.ID).
  264. Find(&attachments, repo_model.Attachment{})
  265. if err != nil {
  266. return err
  267. }
  268. // merge join
  269. currentIndex := 0
  270. for _, attachment := range attachments {
  271. for sortedRels.ID[currentIndex] < attachment.ReleaseID {
  272. currentIndex++
  273. }
  274. sortedRels.Rel[currentIndex].Attachments = append(sortedRels.Rel[currentIndex].Attachments, attachment)
  275. }
  276. return
  277. }
  278. type releaseSorter struct {
  279. rels []*Release
  280. }
  281. func (rs *releaseSorter) Len() int {
  282. return len(rs.rels)
  283. }
  284. func (rs *releaseSorter) Less(i, j int) bool {
  285. diffNum := rs.rels[i].NumCommits - rs.rels[j].NumCommits
  286. if diffNum != 0 {
  287. return diffNum > 0
  288. }
  289. return rs.rels[i].CreatedUnix > rs.rels[j].CreatedUnix
  290. }
  291. func (rs *releaseSorter) Swap(i, j int) {
  292. rs.rels[i], rs.rels[j] = rs.rels[j], rs.rels[i]
  293. }
  294. // SortReleases sorts releases by number of commits and created time.
  295. func SortReleases(rels []*Release) {
  296. sorter := &releaseSorter{rels: rels}
  297. sort.Sort(sorter)
  298. }
  299. // DeleteReleaseByID deletes a release from database by given ID.
  300. func DeleteReleaseByID(id int64) error {
  301. _, err := db.GetEngine(db.DefaultContext).ID(id).Delete(new(Release))
  302. return err
  303. }
  304. // UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID
  305. func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error {
  306. _, err := db.GetEngine(db.DefaultContext).Table("release").
  307. Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType).
  308. And("original_author_id = ?", originalAuthorID).
  309. Update(map[string]interface{}{
  310. "publisher_id": posterID,
  311. "original_author": "",
  312. "original_author_id": 0,
  313. })
  314. return err
  315. }
  316. // PushUpdateDeleteTagsContext updates a number of delete tags with context
  317. func PushUpdateDeleteTagsContext(ctx context.Context, repo *repo_model.Repository, tags []string) error {
  318. if len(tags) == 0 {
  319. return nil
  320. }
  321. lowerTags := make([]string, 0, len(tags))
  322. for _, tag := range tags {
  323. lowerTags = append(lowerTags, strings.ToLower(tag))
  324. }
  325. if _, err := db.GetEngine(ctx).
  326. Where("repo_id = ? AND is_tag = ?", repo.ID, true).
  327. In("lower_tag_name", lowerTags).
  328. Delete(new(Release)); err != nil {
  329. return fmt.Errorf("Delete: %v", err)
  330. }
  331. if _, err := db.GetEngine(ctx).
  332. Where("repo_id = ? AND is_tag = ?", repo.ID, false).
  333. In("lower_tag_name", lowerTags).
  334. Cols("is_draft", "num_commits", "sha1").
  335. Update(&Release{
  336. IsDraft: true,
  337. }); err != nil {
  338. return fmt.Errorf("Update: %v", err)
  339. }
  340. return nil
  341. }
  342. // PushUpdateDeleteTag must be called for any push actions to delete tag
  343. func PushUpdateDeleteTag(repo *repo_model.Repository, tagName string) error {
  344. rel, err := GetRelease(repo.ID, tagName)
  345. if err != nil {
  346. if IsErrReleaseNotExist(err) {
  347. return nil
  348. }
  349. return fmt.Errorf("GetRelease: %v", err)
  350. }
  351. if rel.IsTag {
  352. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil {
  353. return fmt.Errorf("Delete: %v", err)
  354. }
  355. } else {
  356. rel.IsDraft = true
  357. rel.NumCommits = 0
  358. rel.Sha1 = ""
  359. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil {
  360. return fmt.Errorf("Update: %v", err)
  361. }
  362. }
  363. return nil
  364. }
  365. // SaveOrUpdateTag must be called for any push actions to add tag
  366. func SaveOrUpdateTag(repo *repo_model.Repository, newRel *Release) error {
  367. rel, err := GetRelease(repo.ID, newRel.TagName)
  368. if err != nil && !IsErrReleaseNotExist(err) {
  369. return fmt.Errorf("GetRelease: %v", err)
  370. }
  371. if rel == nil {
  372. rel = newRel
  373. if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil {
  374. return fmt.Errorf("InsertOne: %v", err)
  375. }
  376. } else {
  377. rel.Sha1 = newRel.Sha1
  378. rel.CreatedUnix = newRel.CreatedUnix
  379. rel.NumCommits = newRel.NumCommits
  380. rel.IsDraft = false
  381. if rel.IsTag && newRel.PublisherID > 0 {
  382. rel.PublisherID = newRel.PublisherID
  383. }
  384. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil {
  385. return fmt.Errorf("Update: %v", err)
  386. }
  387. }
  388. return nil
  389. }
  390. // RemapExternalUser ExternalUserRemappable interface
  391. func (r *Release) RemapExternalUser(externalName string, externalID, userID int64) error {
  392. r.OriginalAuthor = externalName
  393. r.OriginalAuthorID = externalID
  394. r.PublisherID = userID
  395. return nil
  396. }
  397. // UserID ExternalUserRemappable interface
  398. func (r *Release) GetUserID() int64 { return r.PublisherID }
  399. // ExternalName ExternalUserRemappable interface
  400. func (r *Release) GetExternalName() string { return r.OriginalAuthor }
  401. // ExternalID ExternalUserRemappable interface
  402. func (r *Release) GetExternalID() int64 { return r.OriginalAuthorID }