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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. // Copyright 2016 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repo
  4. import (
  5. "fmt"
  6. "net/http"
  7. "code.gitea.io/gitea/models"
  8. "code.gitea.io/gitea/models/db"
  9. "code.gitea.io/gitea/models/perm"
  10. repo_model "code.gitea.io/gitea/models/repo"
  11. "code.gitea.io/gitea/models/unit"
  12. api "code.gitea.io/gitea/modules/structs"
  13. "code.gitea.io/gitea/modules/web"
  14. "code.gitea.io/gitea/routers/api/v1/utils"
  15. "code.gitea.io/gitea/services/context"
  16. "code.gitea.io/gitea/services/convert"
  17. release_service "code.gitea.io/gitea/services/release"
  18. )
  19. // GetRelease get a single release of a repository
  20. func GetRelease(ctx *context.APIContext) {
  21. // swagger:operation GET /repos/{owner}/{repo}/releases/{id} repository repoGetRelease
  22. // ---
  23. // summary: Get a release
  24. // produces:
  25. // - application/json
  26. // parameters:
  27. // - name: owner
  28. // in: path
  29. // description: owner of the repo
  30. // type: string
  31. // required: true
  32. // - name: repo
  33. // in: path
  34. // description: name of the repo
  35. // type: string
  36. // required: true
  37. // - name: id
  38. // in: path
  39. // description: id of the release to get
  40. // type: integer
  41. // format: int64
  42. // required: true
  43. // responses:
  44. // "200":
  45. // "$ref": "#/responses/Release"
  46. // "404":
  47. // "$ref": "#/responses/notFound"
  48. id := ctx.ParamsInt64(":id")
  49. release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
  50. if err != nil && !repo_model.IsErrReleaseNotExist(err) {
  51. ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
  52. return
  53. }
  54. if err != nil && repo_model.IsErrReleaseNotExist(err) || release.IsTag {
  55. ctx.NotFound()
  56. return
  57. }
  58. if err := release.LoadAttributes(ctx); err != nil {
  59. ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
  60. return
  61. }
  62. ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release))
  63. }
  64. // GetLatestRelease gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at
  65. func GetLatestRelease(ctx *context.APIContext) {
  66. // swagger:operation GET /repos/{owner}/{repo}/releases/latest repository repoGetLatestRelease
  67. // ---
  68. // summary: Gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at
  69. // produces:
  70. // - application/json
  71. // parameters:
  72. // - name: owner
  73. // in: path
  74. // description: owner of the repo
  75. // type: string
  76. // required: true
  77. // - name: repo
  78. // in: path
  79. // description: name of the repo
  80. // type: string
  81. // required: true
  82. // responses:
  83. // "200":
  84. // "$ref": "#/responses/Release"
  85. // "404":
  86. // "$ref": "#/responses/notFound"
  87. release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
  88. if err != nil && !repo_model.IsErrReleaseNotExist(err) {
  89. ctx.Error(http.StatusInternalServerError, "GetLatestRelease", err)
  90. return
  91. }
  92. if err != nil && repo_model.IsErrReleaseNotExist(err) ||
  93. release.IsTag || release.RepoID != ctx.Repo.Repository.ID {
  94. ctx.NotFound()
  95. return
  96. }
  97. if err := release.LoadAttributes(ctx); err != nil {
  98. ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
  99. return
  100. }
  101. ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release))
  102. }
  103. // ListReleases list a repository's releases
  104. func ListReleases(ctx *context.APIContext) {
  105. // swagger:operation GET /repos/{owner}/{repo}/releases repository repoListReleases
  106. // ---
  107. // summary: List a repo's releases
  108. // produces:
  109. // - application/json
  110. // parameters:
  111. // - name: owner
  112. // in: path
  113. // description: owner of the repo
  114. // type: string
  115. // required: true
  116. // - name: repo
  117. // in: path
  118. // description: name of the repo
  119. // type: string
  120. // required: true
  121. // - name: draft
  122. // in: query
  123. // description: filter (exclude / include) drafts, if you dont have repo write access none will show
  124. // type: boolean
  125. // - name: pre-release
  126. // in: query
  127. // description: filter (exclude / include) pre-releases
  128. // type: boolean
  129. // - name: page
  130. // in: query
  131. // description: page number of results to return (1-based)
  132. // type: integer
  133. // - name: limit
  134. // in: query
  135. // description: page size of results
  136. // type: integer
  137. // responses:
  138. // "200":
  139. // "$ref": "#/responses/ReleaseList"
  140. // "404":
  141. // "$ref": "#/responses/notFound"
  142. listOptions := utils.GetListOptions(ctx)
  143. opts := repo_model.FindReleasesOptions{
  144. ListOptions: listOptions,
  145. IncludeDrafts: ctx.Repo.AccessMode >= perm.AccessModeWrite || ctx.Repo.UnitAccessMode(unit.TypeReleases) >= perm.AccessModeWrite,
  146. IncludeTags: false,
  147. IsDraft: ctx.FormOptionalBool("draft"),
  148. IsPreRelease: ctx.FormOptionalBool("pre-release"),
  149. RepoID: ctx.Repo.Repository.ID,
  150. }
  151. releases, err := db.Find[repo_model.Release](ctx, opts)
  152. if err != nil {
  153. ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err)
  154. return
  155. }
  156. rels := make([]*api.Release, len(releases))
  157. for i, release := range releases {
  158. if err := release.LoadAttributes(ctx); err != nil {
  159. ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
  160. return
  161. }
  162. rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)
  163. }
  164. filteredCount, err := db.Count[repo_model.Release](ctx, opts)
  165. if err != nil {
  166. ctx.InternalServerError(err)
  167. return
  168. }
  169. ctx.SetLinkHeader(int(filteredCount), listOptions.PageSize)
  170. ctx.SetTotalCountHeader(filteredCount)
  171. ctx.JSON(http.StatusOK, rels)
  172. }
  173. // CreateRelease create a release
  174. func CreateRelease(ctx *context.APIContext) {
  175. // swagger:operation POST /repos/{owner}/{repo}/releases repository repoCreateRelease
  176. // ---
  177. // summary: Create a release
  178. // consumes:
  179. // - application/json
  180. // produces:
  181. // - application/json
  182. // parameters:
  183. // - name: owner
  184. // in: path
  185. // description: owner of the repo
  186. // type: string
  187. // required: true
  188. // - name: repo
  189. // in: path
  190. // description: name of the repo
  191. // type: string
  192. // required: true
  193. // - name: body
  194. // in: body
  195. // schema:
  196. // "$ref": "#/definitions/CreateReleaseOption"
  197. // responses:
  198. // "201":
  199. // "$ref": "#/responses/Release"
  200. // "404":
  201. // "$ref": "#/responses/notFound"
  202. // "409":
  203. // "$ref": "#/responses/error"
  204. form := web.GetForm(ctx).(*api.CreateReleaseOption)
  205. if ctx.Repo.Repository.IsEmpty {
  206. ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
  207. return
  208. }
  209. rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName)
  210. if err != nil {
  211. if !repo_model.IsErrReleaseNotExist(err) {
  212. ctx.Error(http.StatusInternalServerError, "GetRelease", err)
  213. return
  214. }
  215. // If target is not provided use default branch
  216. if len(form.Target) == 0 {
  217. form.Target = ctx.Repo.Repository.DefaultBranch
  218. }
  219. rel = &repo_model.Release{
  220. RepoID: ctx.Repo.Repository.ID,
  221. PublisherID: ctx.Doer.ID,
  222. Publisher: ctx.Doer,
  223. TagName: form.TagName,
  224. Target: form.Target,
  225. Title: form.Title,
  226. Note: form.Note,
  227. IsDraft: form.IsDraft,
  228. IsPrerelease: form.IsPrerelease,
  229. IsTag: false,
  230. Repo: ctx.Repo.Repository,
  231. }
  232. if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, ""); err != nil {
  233. if repo_model.IsErrReleaseAlreadyExist(err) {
  234. ctx.Error(http.StatusConflict, "ReleaseAlreadyExist", err)
  235. } else {
  236. ctx.Error(http.StatusInternalServerError, "CreateRelease", err)
  237. }
  238. return
  239. }
  240. } else {
  241. if !rel.IsTag {
  242. ctx.Error(http.StatusConflict, "GetRelease", "Release is has no Tag")
  243. return
  244. }
  245. rel.Title = form.Title
  246. rel.Note = form.Note
  247. rel.IsDraft = form.IsDraft
  248. rel.IsPrerelease = form.IsPrerelease
  249. rel.PublisherID = ctx.Doer.ID
  250. rel.IsTag = false
  251. rel.Repo = ctx.Repo.Repository
  252. rel.Publisher = ctx.Doer
  253. rel.Target = form.Target
  254. if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil); err != nil {
  255. ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
  256. return
  257. }
  258. }
  259. ctx.JSON(http.StatusCreated, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel))
  260. }
  261. // EditRelease edit a release
  262. func EditRelease(ctx *context.APIContext) {
  263. // swagger:operation PATCH /repos/{owner}/{repo}/releases/{id} repository repoEditRelease
  264. // ---
  265. // summary: Update a release
  266. // consumes:
  267. // - application/json
  268. // produces:
  269. // - application/json
  270. // parameters:
  271. // - name: owner
  272. // in: path
  273. // description: owner of the repo
  274. // type: string
  275. // required: true
  276. // - name: repo
  277. // in: path
  278. // description: name of the repo
  279. // type: string
  280. // required: true
  281. // - name: id
  282. // in: path
  283. // description: id of the release to edit
  284. // type: integer
  285. // format: int64
  286. // required: true
  287. // - name: body
  288. // in: body
  289. // schema:
  290. // "$ref": "#/definitions/EditReleaseOption"
  291. // responses:
  292. // "200":
  293. // "$ref": "#/responses/Release"
  294. // "404":
  295. // "$ref": "#/responses/notFound"
  296. form := web.GetForm(ctx).(*api.EditReleaseOption)
  297. id := ctx.ParamsInt64(":id")
  298. rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
  299. if err != nil && !repo_model.IsErrReleaseNotExist(err) {
  300. ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
  301. return
  302. }
  303. if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
  304. ctx.NotFound()
  305. return
  306. }
  307. if len(form.TagName) > 0 {
  308. rel.TagName = form.TagName
  309. }
  310. if len(form.Target) > 0 {
  311. rel.Target = form.Target
  312. }
  313. if len(form.Title) > 0 {
  314. rel.Title = form.Title
  315. }
  316. if len(form.Note) > 0 {
  317. rel.Note = form.Note
  318. }
  319. if form.IsDraft != nil {
  320. rel.IsDraft = *form.IsDraft
  321. }
  322. if form.IsPrerelease != nil {
  323. rel.IsPrerelease = *form.IsPrerelease
  324. }
  325. if err := release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil); err != nil {
  326. ctx.Error(http.StatusInternalServerError, "UpdateRelease", err)
  327. return
  328. }
  329. // reload data from database
  330. rel, err = repo_model.GetReleaseByID(ctx, id)
  331. if err != nil {
  332. ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
  333. return
  334. }
  335. if err := rel.LoadAttributes(ctx); err != nil {
  336. ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
  337. return
  338. }
  339. ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel))
  340. }
  341. // DeleteRelease delete a release from a repository
  342. func DeleteRelease(ctx *context.APIContext) {
  343. // swagger:operation DELETE /repos/{owner}/{repo}/releases/{id} repository repoDeleteRelease
  344. // ---
  345. // summary: Delete a release
  346. // parameters:
  347. // - name: owner
  348. // in: path
  349. // description: owner of the repo
  350. // type: string
  351. // required: true
  352. // - name: repo
  353. // in: path
  354. // description: name of the repo
  355. // type: string
  356. // required: true
  357. // - name: id
  358. // in: path
  359. // description: id of the release to delete
  360. // type: integer
  361. // format: int64
  362. // required: true
  363. // responses:
  364. // "204":
  365. // "$ref": "#/responses/empty"
  366. // "404":
  367. // "$ref": "#/responses/notFound"
  368. // "405":
  369. // "$ref": "#/responses/empty"
  370. id := ctx.ParamsInt64(":id")
  371. rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
  372. if err != nil && !repo_model.IsErrReleaseNotExist(err) {
  373. ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
  374. return
  375. }
  376. if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
  377. ctx.NotFound()
  378. return
  379. }
  380. if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, false); err != nil {
  381. if models.IsErrProtectedTagName(err) {
  382. ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
  383. return
  384. }
  385. ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err)
  386. return
  387. }
  388. ctx.Status(http.StatusNoContent)
  389. }