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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  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.GetRepositoryByIDCtx(ctx, 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(ctx context.Context, repoID int64, tagName string) (bool, error) {
  90. if len(tagName) == 0 {
  91. return false, nil
  92. }
  93. return db.GetEngine(ctx).Exist(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
  94. }
  95. // UpdateRelease updates all columns of a release
  96. func UpdateRelease(ctx context.Context, rel *Release) error {
  97. _, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
  98. return err
  99. }
  100. // AddReleaseAttachments adds a release attachments
  101. func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
  102. // Check attachments
  103. attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, attachmentUUIDs)
  104. if err != nil {
  105. return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err)
  106. }
  107. for i := range attachments {
  108. if attachments[i].ReleaseID != 0 {
  109. return errors.New("release permission denied")
  110. }
  111. attachments[i].ReleaseID = releaseID
  112. // No assign value could be 0, so ignore AllCols().
  113. if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
  114. return fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
  115. }
  116. }
  117. return err
  118. }
  119. // GetRelease returns release by given ID.
  120. func GetRelease(repoID int64, tagName string) (*Release, error) {
  121. rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
  122. has, err := db.GetEngine(db.DefaultContext).Get(rel)
  123. if err != nil {
  124. return nil, err
  125. } else if !has {
  126. return nil, ErrReleaseNotExist{0, tagName}
  127. }
  128. return rel, nil
  129. }
  130. // GetReleaseByID returns release with given ID.
  131. func GetReleaseByID(ctx context.Context, id int64) (*Release, error) {
  132. rel := new(Release)
  133. has, err := db.GetEngine(ctx).
  134. ID(id).
  135. Get(rel)
  136. if err != nil {
  137. return nil, err
  138. } else if !has {
  139. return nil, ErrReleaseNotExist{id, ""}
  140. }
  141. return rel, nil
  142. }
  143. // FindReleasesOptions describes the conditions to Find releases
  144. type FindReleasesOptions struct {
  145. db.ListOptions
  146. IncludeDrafts bool
  147. IncludeTags bool
  148. IsPreRelease util.OptionalBool
  149. IsDraft util.OptionalBool
  150. TagNames []string
  151. }
  152. func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond {
  153. cond := builder.NewCond()
  154. cond = cond.And(builder.Eq{"repo_id": repoID})
  155. if !opts.IncludeDrafts {
  156. cond = cond.And(builder.Eq{"is_draft": false})
  157. }
  158. if !opts.IncludeTags {
  159. cond = cond.And(builder.Eq{"is_tag": false})
  160. }
  161. if len(opts.TagNames) > 0 {
  162. cond = cond.And(builder.In("tag_name", opts.TagNames))
  163. }
  164. if !opts.IsPreRelease.IsNone() {
  165. cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.IsTrue()})
  166. }
  167. if !opts.IsDraft.IsNone() {
  168. cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()})
  169. }
  170. return cond
  171. }
  172. // GetReleasesByRepoID returns a list of releases of repository.
  173. func GetReleasesByRepoID(repoID int64, opts FindReleasesOptions) ([]*Release, error) {
  174. sess := db.GetEngine(db.DefaultContext).
  175. Desc("created_unix", "id").
  176. Where(opts.toConds(repoID))
  177. if opts.PageSize != 0 {
  178. sess = db.SetSessionPagination(sess, &opts.ListOptions)
  179. }
  180. rels := make([]*Release, 0, opts.PageSize)
  181. return rels, sess.Find(&rels)
  182. }
  183. // CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID.
  184. func CountReleasesByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) {
  185. return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(new(Release))
  186. }
  187. // GetLatestReleaseByRepoID returns the latest release for a repository
  188. func GetLatestReleaseByRepoID(repoID int64) (*Release, error) {
  189. cond := builder.NewCond().
  190. And(builder.Eq{"repo_id": repoID}).
  191. And(builder.Eq{"is_draft": false}).
  192. And(builder.Eq{"is_prerelease": false}).
  193. And(builder.Eq{"is_tag": false})
  194. rel := new(Release)
  195. has, err := db.GetEngine(db.DefaultContext).
  196. Desc("created_unix", "id").
  197. Where(cond).
  198. Get(rel)
  199. if err != nil {
  200. return nil, err
  201. } else if !has {
  202. return nil, ErrReleaseNotExist{0, "latest"}
  203. }
  204. return rel, nil
  205. }
  206. // GetReleasesByRepoIDAndNames returns a list of releases of repository according repoID and tagNames.
  207. func GetReleasesByRepoIDAndNames(ctx context.Context, repoID int64, tagNames []string) (rels []*Release, err error) {
  208. err = db.GetEngine(ctx).
  209. In("tag_name", tagNames).
  210. Desc("created_unix").
  211. Find(&rels, Release{RepoID: repoID})
  212. return rels, err
  213. }
  214. // GetReleaseCountByRepoID returns the count of releases of repository
  215. func GetReleaseCountByRepoID(repoID int64, opts FindReleasesOptions) (int64, error) {
  216. return db.GetEngine(db.DefaultContext).Where(opts.toConds(repoID)).Count(&Release{})
  217. }
  218. type releaseMetaSearch struct {
  219. ID []int64
  220. Rel []*Release
  221. }
  222. func (s releaseMetaSearch) Len() int {
  223. return len(s.ID)
  224. }
  225. func (s releaseMetaSearch) Swap(i, j int) {
  226. s.ID[i], s.ID[j] = s.ID[j], s.ID[i]
  227. s.Rel[i], s.Rel[j] = s.Rel[j], s.Rel[i]
  228. }
  229. func (s releaseMetaSearch) Less(i, j int) bool {
  230. return s.ID[i] < s.ID[j]
  231. }
  232. // GetReleaseAttachments retrieves the attachments for releases
  233. func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) {
  234. if len(rels) == 0 {
  235. return
  236. }
  237. // To keep this efficient as possible sort all releases by id,
  238. // select attachments by release id,
  239. // then merge join them
  240. // Sort
  241. sortedRels := releaseMetaSearch{ID: make([]int64, len(rels)), Rel: make([]*Release, len(rels))}
  242. var attachments []*repo_model.Attachment
  243. for index, element := range rels {
  244. element.Attachments = []*repo_model.Attachment{}
  245. sortedRels.ID[index] = element.ID
  246. sortedRels.Rel[index] = element
  247. }
  248. sort.Sort(sortedRels)
  249. // Select attachments
  250. err = db.GetEngine(ctx).
  251. Asc("release_id", "name").
  252. In("release_id", sortedRels.ID).
  253. Find(&attachments, repo_model.Attachment{})
  254. if err != nil {
  255. return err
  256. }
  257. // merge join
  258. currentIndex := 0
  259. for _, attachment := range attachments {
  260. for sortedRels.ID[currentIndex] < attachment.ReleaseID {
  261. currentIndex++
  262. }
  263. sortedRels.Rel[currentIndex].Attachments = append(sortedRels.Rel[currentIndex].Attachments, attachment)
  264. }
  265. return err
  266. }
  267. type releaseSorter struct {
  268. rels []*Release
  269. }
  270. func (rs *releaseSorter) Len() int {
  271. return len(rs.rels)
  272. }
  273. func (rs *releaseSorter) Less(i, j int) bool {
  274. diffNum := rs.rels[i].NumCommits - rs.rels[j].NumCommits
  275. if diffNum != 0 {
  276. return diffNum > 0
  277. }
  278. return rs.rels[i].CreatedUnix > rs.rels[j].CreatedUnix
  279. }
  280. func (rs *releaseSorter) Swap(i, j int) {
  281. rs.rels[i], rs.rels[j] = rs.rels[j], rs.rels[i]
  282. }
  283. // SortReleases sorts releases by number of commits and created time.
  284. func SortReleases(rels []*Release) {
  285. sorter := &releaseSorter{rels: rels}
  286. sort.Sort(sorter)
  287. }
  288. // DeleteReleaseByID deletes a release from database by given ID.
  289. func DeleteReleaseByID(id int64) error {
  290. _, err := db.GetEngine(db.DefaultContext).ID(id).Delete(new(Release))
  291. return err
  292. }
  293. // UpdateReleasesMigrationsByType updates all migrated repositories' releases from gitServiceType to replace originalAuthorID to posterID
  294. func UpdateReleasesMigrationsByType(gitServiceType structs.GitServiceType, originalAuthorID string, posterID int64) error {
  295. _, err := db.GetEngine(db.DefaultContext).Table("release").
  296. Where("repo_id IN (SELECT id FROM repository WHERE original_service_type = ?)", gitServiceType).
  297. And("original_author_id = ?", originalAuthorID).
  298. Update(map[string]interface{}{
  299. "publisher_id": posterID,
  300. "original_author": "",
  301. "original_author_id": 0,
  302. })
  303. return err
  304. }
  305. // PushUpdateDeleteTagsContext updates a number of delete tags with context
  306. func PushUpdateDeleteTagsContext(ctx context.Context, repo *repo_model.Repository, tags []string) error {
  307. if len(tags) == 0 {
  308. return nil
  309. }
  310. lowerTags := make([]string, 0, len(tags))
  311. for _, tag := range tags {
  312. lowerTags = append(lowerTags, strings.ToLower(tag))
  313. }
  314. if _, err := db.GetEngine(ctx).
  315. Where("repo_id = ? AND is_tag = ?", repo.ID, true).
  316. In("lower_tag_name", lowerTags).
  317. Delete(new(Release)); err != nil {
  318. return fmt.Errorf("Delete: %v", err)
  319. }
  320. if _, err := db.GetEngine(ctx).
  321. Where("repo_id = ? AND is_tag = ?", repo.ID, false).
  322. In("lower_tag_name", lowerTags).
  323. Cols("is_draft", "num_commits", "sha1").
  324. Update(&Release{
  325. IsDraft: true,
  326. }); err != nil {
  327. return fmt.Errorf("Update: %v", err)
  328. }
  329. return nil
  330. }
  331. // PushUpdateDeleteTag must be called for any push actions to delete tag
  332. func PushUpdateDeleteTag(repo *repo_model.Repository, tagName string) error {
  333. rel, err := GetRelease(repo.ID, tagName)
  334. if err != nil {
  335. if IsErrReleaseNotExist(err) {
  336. return nil
  337. }
  338. return fmt.Errorf("GetRelease: %v", err)
  339. }
  340. if rel.IsTag {
  341. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).Delete(new(Release)); err != nil {
  342. return fmt.Errorf("Delete: %v", err)
  343. }
  344. } else {
  345. rel.IsDraft = true
  346. rel.NumCommits = 0
  347. rel.Sha1 = ""
  348. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil {
  349. return fmt.Errorf("Update: %v", err)
  350. }
  351. }
  352. return nil
  353. }
  354. // SaveOrUpdateTag must be called for any push actions to add tag
  355. func SaveOrUpdateTag(repo *repo_model.Repository, newRel *Release) error {
  356. rel, err := GetRelease(repo.ID, newRel.TagName)
  357. if err != nil && !IsErrReleaseNotExist(err) {
  358. return fmt.Errorf("GetRelease: %v", err)
  359. }
  360. if rel == nil {
  361. rel = newRel
  362. if _, err = db.GetEngine(db.DefaultContext).Insert(rel); err != nil {
  363. return fmt.Errorf("InsertOne: %v", err)
  364. }
  365. } else {
  366. rel.Sha1 = newRel.Sha1
  367. rel.CreatedUnix = newRel.CreatedUnix
  368. rel.NumCommits = newRel.NumCommits
  369. rel.IsDraft = false
  370. if rel.IsTag && newRel.PublisherID > 0 {
  371. rel.PublisherID = newRel.PublisherID
  372. }
  373. if _, err = db.GetEngine(db.DefaultContext).ID(rel.ID).AllCols().Update(rel); err != nil {
  374. return fmt.Errorf("Update: %v", err)
  375. }
  376. }
  377. return nil
  378. }
  379. // RemapExternalUser ExternalUserRemappable interface
  380. func (r *Release) RemapExternalUser(externalName string, externalID, userID int64) error {
  381. r.OriginalAuthor = externalName
  382. r.OriginalAuthorID = externalID
  383. r.PublisherID = userID
  384. return nil
  385. }
  386. // UserID ExternalUserRemappable interface
  387. func (r *Release) GetUserID() int64 { return r.PublisherID }
  388. // ExternalName ExternalUserRemappable interface
  389. func (r *Release) GetExternalName() string { return r.OriginalAuthor }
  390. // ExternalID ExternalUserRemappable interface
  391. func (r *Release) GetExternalID() int64 { return r.OriginalAuthorID }