gitea源码

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