gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2023 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "net/http"
  7. "code.gitea.io/gitea/models/db"
  8. issues_model "code.gitea.io/gitea/models/issues"
  9. access_model "code.gitea.io/gitea/models/perm/access"
  10. repo_model "code.gitea.io/gitea/models/repo"
  11. "code.gitea.io/gitea/modules/setting"
  12. api "code.gitea.io/gitea/modules/structs"
  13. "code.gitea.io/gitea/modules/web"
  14. "code.gitea.io/gitea/services/context"
  15. "code.gitea.io/gitea/services/convert"
  16. )
  17. // GetIssueDependencies list an issue's dependencies
  18. func GetIssueDependencies(ctx *context.APIContext) {
  19. // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/dependencies issue issueListIssueDependencies
  20. // ---
  21. // summary: List an issue's dependencies, i.e all issues that block this issue.
  22. // produces:
  23. // - application/json
  24. // parameters:
  25. // - name: owner
  26. // in: path
  27. // description: owner of the repo
  28. // type: string
  29. // required: true
  30. // - name: repo
  31. // in: path
  32. // description: name of the repo
  33. // type: string
  34. // required: true
  35. // - name: index
  36. // in: path
  37. // description: index of the issue
  38. // type: string
  39. // required: true
  40. // - name: page
  41. // in: query
  42. // description: page number of results to return (1-based)
  43. // type: integer
  44. // - name: limit
  45. // in: query
  46. // description: page size of results
  47. // type: integer
  48. // responses:
  49. // "200":
  50. // "$ref": "#/responses/IssueList"
  51. // "404":
  52. // "$ref": "#/responses/notFound"
  53. // If this issue's repository does not enable dependencies then there can be no dependencies by default
  54. if !ctx.Repo.Repository.IsDependenciesEnabled(ctx) {
  55. ctx.APIErrorNotFound()
  56. return
  57. }
  58. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  59. if err != nil {
  60. if issues_model.IsErrIssueNotExist(err) {
  61. ctx.APIErrorNotFound("IsErrIssueNotExist", err)
  62. } else {
  63. ctx.APIErrorInternal(err)
  64. }
  65. return
  66. }
  67. // 1. We must be able to read this issue
  68. if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
  69. ctx.APIErrorNotFound()
  70. return
  71. }
  72. page := max(ctx.FormInt("page"), 1)
  73. limit := ctx.FormInt("limit")
  74. if limit == 0 {
  75. limit = setting.API.DefaultPagingNum
  76. } else if limit > setting.API.MaxResponseItems {
  77. limit = setting.API.MaxResponseItems
  78. }
  79. canWrite := ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)
  80. blockerIssues := make([]*issues_model.Issue, 0, limit)
  81. // 2. Get the issues this issue depends on, i.e. the `<#b>`: `<issue> <- <#b>`
  82. blockersInfo, err := issue.BlockedByDependencies(ctx, db.ListOptions{
  83. Page: page,
  84. PageSize: limit,
  85. })
  86. if err != nil {
  87. ctx.APIErrorInternal(err)
  88. return
  89. }
  90. repoPerms := make(map[int64]access_model.Permission)
  91. repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
  92. for _, blocker := range blockersInfo {
  93. // Get the permissions for this repository
  94. // If the repo ID exists in the map, return the exist permissions
  95. // else get the permission and add it to the map
  96. var perm access_model.Permission
  97. existPerm, ok := repoPerms[blocker.RepoID]
  98. if ok {
  99. perm = existPerm
  100. } else {
  101. var err error
  102. perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
  103. if err != nil {
  104. ctx.APIErrorInternal(err)
  105. return
  106. }
  107. repoPerms[blocker.RepoID] = perm
  108. }
  109. // check permission
  110. if !perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
  111. if !canWrite {
  112. hiddenBlocker := &issues_model.DependencyInfo{
  113. Issue: issues_model.Issue{
  114. Title: "HIDDEN",
  115. },
  116. }
  117. blocker = hiddenBlocker
  118. } else {
  119. confidentialBlocker := &issues_model.DependencyInfo{
  120. Issue: issues_model.Issue{
  121. RepoID: blocker.Issue.RepoID,
  122. Index: blocker.Index,
  123. Title: blocker.Title,
  124. IsClosed: blocker.IsClosed,
  125. IsPull: blocker.IsPull,
  126. },
  127. Repository: repo_model.Repository{
  128. ID: blocker.Issue.Repo.ID,
  129. Name: blocker.Issue.Repo.Name,
  130. OwnerName: blocker.Issue.Repo.OwnerName,
  131. },
  132. }
  133. confidentialBlocker.Issue.Repo = &confidentialBlocker.Repository
  134. blocker = confidentialBlocker
  135. }
  136. }
  137. blockerIssues = append(blockerIssues, &blocker.Issue)
  138. }
  139. ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, blockerIssues))
  140. }
  141. // CreateIssueDependency create a new issue dependencies
  142. func CreateIssueDependency(ctx *context.APIContext) {
  143. // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/dependencies issue issueCreateIssueDependencies
  144. // ---
  145. // summary: Make the issue in the url depend on the issue in the form.
  146. // produces:
  147. // - application/json
  148. // parameters:
  149. // - name: owner
  150. // in: path
  151. // description: owner of the repo
  152. // type: string
  153. // required: true
  154. // - name: repo
  155. // in: path
  156. // description: name of the repo
  157. // type: string
  158. // required: true
  159. // - name: index
  160. // in: path
  161. // description: index of the issue
  162. // type: string
  163. // required: true
  164. // - name: body
  165. // in: body
  166. // schema:
  167. // "$ref": "#/definitions/IssueMeta"
  168. // responses:
  169. // "201":
  170. // "$ref": "#/responses/Issue"
  171. // "404":
  172. // description: the issue does not exist
  173. // "423":
  174. // "$ref": "#/responses/repoArchivedError"
  175. // We want to make <:index> depend on <Form>, i.e. <:index> is the target
  176. target := getParamsIssue(ctx)
  177. if ctx.Written() {
  178. return
  179. }
  180. // and <Form> represents the dependency
  181. form := web.GetForm(ctx).(*api.IssueMeta)
  182. dependency := getFormIssue(ctx, form)
  183. if ctx.Written() {
  184. return
  185. }
  186. dependencyPerm := getPermissionForRepo(ctx, target.Repo)
  187. if ctx.Written() {
  188. return
  189. }
  190. createIssueDependency(ctx, target, dependency, ctx.Repo.Permission, *dependencyPerm)
  191. if ctx.Written() {
  192. return
  193. }
  194. ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, target))
  195. }
  196. // RemoveIssueDependency remove an issue dependency
  197. func RemoveIssueDependency(ctx *context.APIContext) {
  198. // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/dependencies issue issueRemoveIssueDependencies
  199. // ---
  200. // summary: Remove an issue dependency
  201. // produces:
  202. // - application/json
  203. // parameters:
  204. // - name: owner
  205. // in: path
  206. // description: owner of the repo
  207. // type: string
  208. // required: true
  209. // - name: repo
  210. // in: path
  211. // description: name of the repo
  212. // type: string
  213. // required: true
  214. // - name: index
  215. // in: path
  216. // description: index of the issue
  217. // type: string
  218. // required: true
  219. // - name: body
  220. // in: body
  221. // schema:
  222. // "$ref": "#/definitions/IssueMeta"
  223. // responses:
  224. // "200":
  225. // "$ref": "#/responses/Issue"
  226. // "404":
  227. // "$ref": "#/responses/notFound"
  228. // "423":
  229. // "$ref": "#/responses/repoArchivedError"
  230. // We want to make <:index> depend on <Form>, i.e. <:index> is the target
  231. target := getParamsIssue(ctx)
  232. if ctx.Written() {
  233. return
  234. }
  235. // and <Form> represents the dependency
  236. form := web.GetForm(ctx).(*api.IssueMeta)
  237. dependency := getFormIssue(ctx, form)
  238. if ctx.Written() {
  239. return
  240. }
  241. dependencyPerm := getPermissionForRepo(ctx, target.Repo)
  242. if ctx.Written() {
  243. return
  244. }
  245. removeIssueDependency(ctx, target, dependency, ctx.Repo.Permission, *dependencyPerm)
  246. if ctx.Written() {
  247. return
  248. }
  249. ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, target))
  250. }
  251. // GetIssueBlocks list issues that are blocked by this issue
  252. func GetIssueBlocks(ctx *context.APIContext) {
  253. // swagger:operation GET /repos/{owner}/{repo}/issues/{index}/blocks issue issueListBlocks
  254. // ---
  255. // summary: List issues that are blocked by this issue
  256. // produces:
  257. // - application/json
  258. // parameters:
  259. // - name: owner
  260. // in: path
  261. // description: owner of the repo
  262. // type: string
  263. // required: true
  264. // - name: repo
  265. // in: path
  266. // description: name of the repo
  267. // type: string
  268. // required: true
  269. // - name: index
  270. // in: path
  271. // description: index of the issue
  272. // type: string
  273. // required: true
  274. // - name: page
  275. // in: query
  276. // description: page number of results to return (1-based)
  277. // type: integer
  278. // - name: limit
  279. // in: query
  280. // description: page size of results
  281. // type: integer
  282. // responses:
  283. // "200":
  284. // "$ref": "#/responses/IssueList"
  285. // "404":
  286. // "$ref": "#/responses/notFound"
  287. // We need to list the issues that DEPEND on this issue not the other way round
  288. // Therefore whether dependencies are enabled or not in this repository is potentially irrelevant.
  289. issue := getParamsIssue(ctx)
  290. if ctx.Written() {
  291. return
  292. }
  293. if !ctx.Repo.Permission.CanReadIssuesOrPulls(issue.IsPull) {
  294. ctx.APIErrorNotFound()
  295. return
  296. }
  297. page := max(ctx.FormInt("page"), 1)
  298. limit := ctx.FormInt("limit")
  299. if limit <= 1 {
  300. limit = setting.API.DefaultPagingNum
  301. }
  302. skip := (page - 1) * limit
  303. maxNum := page * limit
  304. deps, err := issue.BlockingDependencies(ctx)
  305. if err != nil {
  306. ctx.APIErrorInternal(err)
  307. return
  308. }
  309. var issues []*issues_model.Issue
  310. repoPerms := make(map[int64]access_model.Permission)
  311. repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
  312. for i, depMeta := range deps {
  313. if i < skip || i >= maxNum {
  314. continue
  315. }
  316. // Get the permissions for this repository
  317. // If the repo ID exists in the map, return the exist permissions
  318. // else get the permission and add it to the map
  319. var perm access_model.Permission
  320. existPerm, ok := repoPerms[depMeta.RepoID]
  321. if ok {
  322. perm = existPerm
  323. } else {
  324. var err error
  325. perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
  326. if err != nil {
  327. ctx.APIErrorInternal(err)
  328. return
  329. }
  330. repoPerms[depMeta.RepoID] = perm
  331. }
  332. if !perm.CanReadIssuesOrPulls(depMeta.Issue.IsPull) {
  333. continue
  334. }
  335. depMeta.Issue.Repo = &depMeta.Repository
  336. issues = append(issues, &depMeta.Issue)
  337. }
  338. ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
  339. }
  340. // CreateIssueBlocking block the issue given in the body by the issue in path
  341. func CreateIssueBlocking(ctx *context.APIContext) {
  342. // swagger:operation POST /repos/{owner}/{repo}/issues/{index}/blocks issue issueCreateIssueBlocking
  343. // ---
  344. // summary: Block the issue given in the body by the issue in path
  345. // produces:
  346. // - application/json
  347. // parameters:
  348. // - name: owner
  349. // in: path
  350. // description: owner of the repo
  351. // type: string
  352. // required: true
  353. // - name: repo
  354. // in: path
  355. // description: name of the repo
  356. // type: string
  357. // required: true
  358. // - name: index
  359. // in: path
  360. // description: index of the issue
  361. // type: string
  362. // required: true
  363. // - name: body
  364. // in: body
  365. // schema:
  366. // "$ref": "#/definitions/IssueMeta"
  367. // responses:
  368. // "201":
  369. // "$ref": "#/responses/Issue"
  370. // "404":
  371. // description: the issue does not exist
  372. dependency := getParamsIssue(ctx)
  373. if ctx.Written() {
  374. return
  375. }
  376. form := web.GetForm(ctx).(*api.IssueMeta)
  377. target := getFormIssue(ctx, form)
  378. if ctx.Written() {
  379. return
  380. }
  381. targetPerm := getPermissionForRepo(ctx, target.Repo)
  382. if ctx.Written() {
  383. return
  384. }
  385. createIssueDependency(ctx, target, dependency, *targetPerm, ctx.Repo.Permission)
  386. if ctx.Written() {
  387. return
  388. }
  389. ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, dependency))
  390. }
  391. // RemoveIssueBlocking unblock the issue given in the body by the issue in path
  392. func RemoveIssueBlocking(ctx *context.APIContext) {
  393. // swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/blocks issue issueRemoveIssueBlocking
  394. // ---
  395. // summary: Unblock the issue given in the body by the issue in path
  396. // produces:
  397. // - application/json
  398. // parameters:
  399. // - name: owner
  400. // in: path
  401. // description: owner of the repo
  402. // type: string
  403. // required: true
  404. // - name: repo
  405. // in: path
  406. // description: name of the repo
  407. // type: string
  408. // required: true
  409. // - name: index
  410. // in: path
  411. // description: index of the issue
  412. // type: string
  413. // required: true
  414. // - name: body
  415. // in: body
  416. // schema:
  417. // "$ref": "#/definitions/IssueMeta"
  418. // responses:
  419. // "200":
  420. // "$ref": "#/responses/Issue"
  421. // "404":
  422. // "$ref": "#/responses/notFound"
  423. dependency := getParamsIssue(ctx)
  424. if ctx.Written() {
  425. return
  426. }
  427. form := web.GetForm(ctx).(*api.IssueMeta)
  428. target := getFormIssue(ctx, form)
  429. if ctx.Written() {
  430. return
  431. }
  432. targetPerm := getPermissionForRepo(ctx, target.Repo)
  433. if ctx.Written() {
  434. return
  435. }
  436. removeIssueDependency(ctx, target, dependency, *targetPerm, ctx.Repo.Permission)
  437. if ctx.Written() {
  438. return
  439. }
  440. ctx.JSON(http.StatusCreated, convert.ToAPIIssue(ctx, ctx.Doer, dependency))
  441. }
  442. func getParamsIssue(ctx *context.APIContext) *issues_model.Issue {
  443. issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
  444. if err != nil {
  445. if issues_model.IsErrIssueNotExist(err) {
  446. ctx.APIErrorNotFound("IsErrIssueNotExist", err)
  447. } else {
  448. ctx.APIErrorInternal(err)
  449. }
  450. return nil
  451. }
  452. issue.Repo = ctx.Repo.Repository
  453. return issue
  454. }
  455. func getFormIssue(ctx *context.APIContext, form *api.IssueMeta) *issues_model.Issue {
  456. var repo *repo_model.Repository
  457. if form.Owner != ctx.Repo.Repository.OwnerName || form.Name != ctx.Repo.Repository.Name {
  458. if !setting.Service.AllowCrossRepositoryDependencies {
  459. ctx.JSON(http.StatusBadRequest, "CrossRepositoryDependencies not enabled")
  460. return nil
  461. }
  462. var err error
  463. repo, err = repo_model.GetRepositoryByOwnerAndName(ctx, form.Owner, form.Name)
  464. if err != nil {
  465. if repo_model.IsErrRepoNotExist(err) {
  466. ctx.APIErrorNotFound("IsErrRepoNotExist", err)
  467. } else {
  468. ctx.APIErrorInternal(err)
  469. }
  470. return nil
  471. }
  472. } else {
  473. repo = ctx.Repo.Repository
  474. }
  475. issue, err := issues_model.GetIssueByIndex(ctx, repo.ID, form.Index)
  476. if err != nil {
  477. if issues_model.IsErrIssueNotExist(err) {
  478. ctx.APIErrorNotFound("IsErrIssueNotExist", err)
  479. } else {
  480. ctx.APIErrorInternal(err)
  481. }
  482. return nil
  483. }
  484. issue.Repo = repo
  485. return issue
  486. }
  487. func getPermissionForRepo(ctx *context.APIContext, repo *repo_model.Repository) *access_model.Permission {
  488. if repo.ID == ctx.Repo.Repository.ID {
  489. return &ctx.Repo.Permission
  490. }
  491. perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
  492. if err != nil {
  493. ctx.APIErrorInternal(err)
  494. return nil
  495. }
  496. return &perm
  497. }
  498. func createIssueDependency(ctx *context.APIContext, target, dependency *issues_model.Issue, targetPerm, dependencyPerm access_model.Permission) {
  499. if target.Repo.IsArchived || !target.Repo.IsDependenciesEnabled(ctx) {
  500. // The target's repository doesn't have dependencies enabled
  501. ctx.APIErrorNotFound()
  502. return
  503. }
  504. if !targetPerm.CanWriteIssuesOrPulls(target.IsPull) {
  505. // We can't write to the target
  506. ctx.APIErrorNotFound()
  507. return
  508. }
  509. if !dependencyPerm.CanReadIssuesOrPulls(dependency.IsPull) {
  510. // We can't read the dependency
  511. ctx.APIErrorNotFound()
  512. return
  513. }
  514. err := issues_model.CreateIssueDependency(ctx, ctx.Doer, target, dependency)
  515. if err != nil {
  516. ctx.APIErrorInternal(err)
  517. return
  518. }
  519. }
  520. func removeIssueDependency(ctx *context.APIContext, target, dependency *issues_model.Issue, targetPerm, dependencyPerm access_model.Permission) {
  521. if target.Repo.IsArchived || !target.Repo.IsDependenciesEnabled(ctx) {
  522. // The target's repository doesn't have dependencies enabled
  523. ctx.APIErrorNotFound()
  524. return
  525. }
  526. if !targetPerm.CanWriteIssuesOrPulls(target.IsPull) {
  527. // We can't write to the target
  528. ctx.APIErrorNotFound()
  529. return
  530. }
  531. if !dependencyPerm.CanReadIssuesOrPulls(dependency.IsPull) {
  532. // We can't read the dependency
  533. ctx.APIErrorNotFound()
  534. return
  535. }
  536. err := issues_model.RemoveIssueDependency(ctx, ctx.Doer, target, dependency, issues_model.DependencyTypeBlockedBy)
  537. if err != nil {
  538. ctx.APIErrorInternal(err)
  539. return
  540. }
  541. }