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.

pull.go 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. // Copyright 2016 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/auth"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/git"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/notification"
  15. "code.gitea.io/gitea/modules/pull"
  16. api "code.gitea.io/gitea/modules/structs"
  17. "code.gitea.io/gitea/modules/util"
  18. )
  19. // ListPullRequests returns a list of all PRs
  20. func ListPullRequests(ctx *context.APIContext, form api.ListPullRequestsOptions) {
  21. // swagger:operation GET /repos/{owner}/{repo}/pulls repository repoListPullRequests
  22. // ---
  23. // summary: List a repo's pull requests
  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: page
  38. // in: query
  39. // description: Page number
  40. // type: integer
  41. // - name: state
  42. // in: query
  43. // description: "State of pull request: open or closed (optional)"
  44. // type: string
  45. // enum: [closed, open, all]
  46. // - name: sort
  47. // in: query
  48. // description: "Type of sort"
  49. // type: string
  50. // enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
  51. // - name: milestone
  52. // in: query
  53. // description: "ID of the milestone"
  54. // type: integer
  55. // format: int64
  56. // - name: labels
  57. // in: query
  58. // description: "Label IDs"
  59. // type: array
  60. // collectionFormat: multi
  61. // items:
  62. // type: integer
  63. // format: int64
  64. // responses:
  65. // "200":
  66. // "$ref": "#/responses/PullRequestList"
  67. prs, maxResults, err := models.PullRequests(ctx.Repo.Repository.ID, &models.PullRequestsOptions{
  68. Page: ctx.QueryInt("page"),
  69. State: ctx.QueryTrim("state"),
  70. SortType: ctx.QueryTrim("sort"),
  71. Labels: ctx.QueryStrings("labels"),
  72. MilestoneID: ctx.QueryInt64("milestone"),
  73. })
  74. if err != nil {
  75. ctx.Error(500, "PullRequests", err)
  76. return
  77. }
  78. apiPrs := make([]*api.PullRequest, len(prs))
  79. for i := range prs {
  80. if err = prs[i].LoadIssue(); err != nil {
  81. ctx.Error(500, "LoadIssue", err)
  82. return
  83. }
  84. if err = prs[i].LoadAttributes(); err != nil {
  85. ctx.Error(500, "LoadAttributes", err)
  86. return
  87. }
  88. if err = prs[i].GetBaseRepo(); err != nil {
  89. ctx.Error(500, "GetBaseRepo", err)
  90. return
  91. }
  92. if err = prs[i].GetHeadRepo(); err != nil {
  93. ctx.Error(500, "GetHeadRepo", err)
  94. return
  95. }
  96. apiPrs[i] = prs[i].APIFormat()
  97. }
  98. ctx.SetLinkHeader(int(maxResults), models.ItemsPerPage)
  99. ctx.JSON(200, &apiPrs)
  100. }
  101. // GetPullRequest returns a single PR based on index
  102. func GetPullRequest(ctx *context.APIContext) {
  103. // swagger:operation GET /repos/{owner}/{repo}/pulls/{index} repository repoGetPullRequest
  104. // ---
  105. // summary: Get a pull request
  106. // produces:
  107. // - application/json
  108. // parameters:
  109. // - name: owner
  110. // in: path
  111. // description: owner of the repo
  112. // type: string
  113. // required: true
  114. // - name: repo
  115. // in: path
  116. // description: name of the repo
  117. // type: string
  118. // required: true
  119. // - name: index
  120. // in: path
  121. // description: index of the pull request to get
  122. // type: integer
  123. // format: int64
  124. // required: true
  125. // responses:
  126. // "200":
  127. // "$ref": "#/responses/PullRequest"
  128. pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  129. if err != nil {
  130. if models.IsErrPullRequestNotExist(err) {
  131. ctx.NotFound()
  132. } else {
  133. ctx.Error(500, "GetPullRequestByIndex", err)
  134. }
  135. return
  136. }
  137. if err = pr.GetBaseRepo(); err != nil {
  138. ctx.Error(500, "GetBaseRepo", err)
  139. return
  140. }
  141. if err = pr.GetHeadRepo(); err != nil {
  142. ctx.Error(500, "GetHeadRepo", err)
  143. return
  144. }
  145. ctx.JSON(200, pr.APIFormat())
  146. }
  147. // CreatePullRequest does what it says
  148. func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption) {
  149. // swagger:operation POST /repos/{owner}/{repo}/pulls repository repoCreatePullRequest
  150. // ---
  151. // summary: Create a pull request
  152. // consumes:
  153. // - application/json
  154. // produces:
  155. // - application/json
  156. // parameters:
  157. // - name: owner
  158. // in: path
  159. // description: owner of the repo
  160. // type: string
  161. // required: true
  162. // - name: repo
  163. // in: path
  164. // description: name of the repo
  165. // type: string
  166. // required: true
  167. // - name: body
  168. // in: body
  169. // schema:
  170. // "$ref": "#/definitions/CreatePullRequestOption"
  171. // responses:
  172. // "201":
  173. // "$ref": "#/responses/PullRequest"
  174. var (
  175. repo = ctx.Repo.Repository
  176. labelIDs []int64
  177. assigneeID int64
  178. milestoneID int64
  179. )
  180. // Get repo/branch information
  181. headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
  182. if ctx.Written() {
  183. return
  184. }
  185. defer headGitRepo.Close()
  186. // Check if another PR exists with the same targets
  187. existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch)
  188. if err != nil {
  189. if !models.IsErrPullRequestNotExist(err) {
  190. ctx.Error(500, "GetUnmergedPullRequest", err)
  191. return
  192. }
  193. } else {
  194. err = models.ErrPullRequestAlreadyExists{
  195. ID: existingPr.ID,
  196. IssueID: existingPr.Index,
  197. HeadRepoID: existingPr.HeadRepoID,
  198. BaseRepoID: existingPr.BaseRepoID,
  199. HeadBranch: existingPr.HeadBranch,
  200. BaseBranch: existingPr.BaseBranch,
  201. }
  202. ctx.Error(409, "GetUnmergedPullRequest", err)
  203. return
  204. }
  205. if len(form.Labels) > 0 {
  206. labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
  207. if err != nil {
  208. ctx.Error(500, "GetLabelsInRepoByIDs", err)
  209. return
  210. }
  211. labelIDs = make([]int64, len(labels))
  212. for i := range labels {
  213. labelIDs[i] = labels[i].ID
  214. }
  215. }
  216. if form.Milestone > 0 {
  217. milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
  218. if err != nil {
  219. if models.IsErrMilestoneNotExist(err) {
  220. ctx.NotFound()
  221. } else {
  222. ctx.Error(500, "GetMilestoneByRepoID", err)
  223. }
  224. return
  225. }
  226. milestoneID = milestone.ID
  227. }
  228. patch, err := headGitRepo.GetPatch(compareInfo.MergeBase, headBranch)
  229. if err != nil {
  230. ctx.Error(500, "GetPatch", err)
  231. return
  232. }
  233. var deadlineUnix util.TimeStamp
  234. if form.Deadline != nil {
  235. deadlineUnix = util.TimeStamp(form.Deadline.Unix())
  236. }
  237. maxIndex, err := models.GetMaxIndexOfIssue(repo.ID)
  238. if err != nil {
  239. ctx.ServerError("GetPatch", err)
  240. return
  241. }
  242. prIssue := &models.Issue{
  243. RepoID: repo.ID,
  244. Index: maxIndex + 1,
  245. Title: form.Title,
  246. PosterID: ctx.User.ID,
  247. Poster: ctx.User,
  248. MilestoneID: milestoneID,
  249. AssigneeID: assigneeID,
  250. IsPull: true,
  251. Content: form.Body,
  252. DeadlineUnix: deadlineUnix,
  253. }
  254. pr := &models.PullRequest{
  255. HeadRepoID: headRepo.ID,
  256. BaseRepoID: repo.ID,
  257. HeadUserName: headUser.Name,
  258. HeadBranch: headBranch,
  259. BaseBranch: baseBranch,
  260. HeadRepo: headRepo,
  261. BaseRepo: repo,
  262. MergeBase: compareInfo.MergeBase,
  263. Type: models.PullRequestGitea,
  264. }
  265. // Get all assignee IDs
  266. assigneeIDs, err := models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
  267. if err != nil {
  268. if models.IsErrUserNotExist(err) {
  269. ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
  270. } else {
  271. ctx.Error(500, "AddAssigneeByName", err)
  272. }
  273. return
  274. }
  275. if err := models.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, patch, assigneeIDs); err != nil {
  276. if models.IsErrUserDoesNotHaveAccessToRepo(err) {
  277. ctx.Error(400, "UserDoesNotHaveAccessToRepo", err)
  278. return
  279. }
  280. ctx.Error(500, "NewPullRequest", err)
  281. return
  282. } else if err := pr.PushToBaseRepo(); err != nil {
  283. ctx.Error(500, "PushToBaseRepo", err)
  284. return
  285. }
  286. notification.NotifyNewPullRequest(pr)
  287. log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
  288. ctx.JSON(201, pr.APIFormat())
  289. }
  290. // EditPullRequest does what it says
  291. func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
  292. // swagger:operation PATCH /repos/{owner}/{repo}/pulls/{index} repository repoEditPullRequest
  293. // ---
  294. // summary: Update a pull request
  295. // consumes:
  296. // - application/json
  297. // produces:
  298. // - application/json
  299. // parameters:
  300. // - name: owner
  301. // in: path
  302. // description: owner of the repo
  303. // type: string
  304. // required: true
  305. // - name: repo
  306. // in: path
  307. // description: name of the repo
  308. // type: string
  309. // required: true
  310. // - name: index
  311. // in: path
  312. // description: index of the pull request to edit
  313. // type: integer
  314. // format: int64
  315. // required: true
  316. // - name: body
  317. // in: body
  318. // schema:
  319. // "$ref": "#/definitions/EditPullRequestOption"
  320. // responses:
  321. // "201":
  322. // "$ref": "#/responses/PullRequest"
  323. pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  324. if err != nil {
  325. if models.IsErrPullRequestNotExist(err) {
  326. ctx.NotFound()
  327. } else {
  328. ctx.Error(500, "GetPullRequestByIndex", err)
  329. }
  330. return
  331. }
  332. err = pr.LoadIssue()
  333. if err != nil {
  334. ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
  335. return
  336. }
  337. issue := pr.Issue
  338. issue.Repo = ctx.Repo.Repository
  339. if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypePullRequests) {
  340. ctx.Status(403)
  341. return
  342. }
  343. if len(form.Title) > 0 {
  344. issue.Title = form.Title
  345. }
  346. if len(form.Body) > 0 {
  347. issue.Content = form.Body
  348. }
  349. // Update Deadline
  350. if form.Deadline != nil && !form.Deadline.IsZero() {
  351. deadlineUnix := util.TimeStamp(form.Deadline.Unix())
  352. if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
  353. ctx.Error(500, "UpdateIssueDeadline", err)
  354. return
  355. }
  356. issue.DeadlineUnix = deadlineUnix
  357. }
  358. // Add/delete assignees
  359. // Deleting is done the GitHub way (quote from their api documentation):
  360. // https://developer.github.com/v3/issues/#edit-an-issue
  361. // "assignees" (array): Logins for Users to assign to this issue.
  362. // Pass one or more user logins to replace the set of assignees on this Issue.
  363. // Send an empty array ([]) to clear all assignees from the Issue.
  364. if ctx.Repo.CanWrite(models.UnitTypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) {
  365. err = models.UpdateAPIAssignee(issue, form.Assignee, form.Assignees, ctx.User)
  366. if err != nil {
  367. if models.IsErrUserNotExist(err) {
  368. ctx.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
  369. } else {
  370. ctx.Error(500, "UpdateAPIAssignee", err)
  371. }
  372. return
  373. }
  374. }
  375. if ctx.Repo.CanWrite(models.UnitTypePullRequests) && form.Milestone != 0 &&
  376. issue.MilestoneID != form.Milestone {
  377. oldMilestoneID := issue.MilestoneID
  378. issue.MilestoneID = form.Milestone
  379. if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
  380. ctx.Error(500, "ChangeMilestoneAssign", err)
  381. return
  382. }
  383. }
  384. if ctx.Repo.CanWrite(models.UnitTypePullRequests) && form.Labels != nil {
  385. labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
  386. if err != nil {
  387. ctx.Error(500, "GetLabelsInRepoByIDsError", err)
  388. return
  389. }
  390. if err = issue.ReplaceLabels(labels, ctx.User); err != nil {
  391. ctx.Error(500, "ReplaceLabelsError", err)
  392. return
  393. }
  394. }
  395. if err = models.UpdateIssue(issue); err != nil {
  396. ctx.Error(500, "UpdateIssue", err)
  397. return
  398. }
  399. if form.State != nil {
  400. if err = issue.ChangeStatus(ctx.User, api.StateClosed == api.StateType(*form.State)); err != nil {
  401. if models.IsErrDependenciesLeft(err) {
  402. ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
  403. return
  404. }
  405. ctx.Error(500, "ChangeStatus", err)
  406. return
  407. }
  408. notification.NotifyIssueChangeStatus(ctx.User, issue, api.StateClosed == api.StateType(*form.State))
  409. }
  410. // Refetch from database
  411. pr, err = models.GetPullRequestByIndex(ctx.Repo.Repository.ID, pr.Index)
  412. if err != nil {
  413. if models.IsErrPullRequestNotExist(err) {
  414. ctx.NotFound()
  415. } else {
  416. ctx.Error(500, "GetPullRequestByIndex", err)
  417. }
  418. return
  419. }
  420. // TODO this should be 200, not 201
  421. ctx.JSON(201, pr.APIFormat())
  422. }
  423. // IsPullRequestMerged checks if a PR exists given an index
  424. func IsPullRequestMerged(ctx *context.APIContext) {
  425. // swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/merge repository repoPullRequestIsMerged
  426. // ---
  427. // summary: Check if a pull request has been merged
  428. // produces:
  429. // - application/json
  430. // parameters:
  431. // - name: owner
  432. // in: path
  433. // description: owner of the repo
  434. // type: string
  435. // required: true
  436. // - name: repo
  437. // in: path
  438. // description: name of the repo
  439. // type: string
  440. // required: true
  441. // - name: index
  442. // in: path
  443. // description: index of the pull request
  444. // type: integer
  445. // format: int64
  446. // required: true
  447. // responses:
  448. // "204":
  449. // description: pull request has been merged
  450. // "404":
  451. // description: pull request has not been merged
  452. pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  453. if err != nil {
  454. if models.IsErrPullRequestNotExist(err) {
  455. ctx.NotFound()
  456. } else {
  457. ctx.Error(500, "GetPullRequestByIndex", err)
  458. }
  459. return
  460. }
  461. if pr.HasMerged {
  462. ctx.Status(204)
  463. }
  464. ctx.NotFound()
  465. }
  466. // MergePullRequest merges a PR given an index
  467. func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
  468. // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/merge repository repoMergePullRequest
  469. // ---
  470. // summary: Merge a pull request
  471. // produces:
  472. // - application/json
  473. // parameters:
  474. // - name: owner
  475. // in: path
  476. // description: owner of the repo
  477. // type: string
  478. // required: true
  479. // - name: repo
  480. // in: path
  481. // description: name of the repo
  482. // type: string
  483. // required: true
  484. // - name: index
  485. // in: path
  486. // description: index of the pull request to merge
  487. // type: integer
  488. // format: int64
  489. // required: true
  490. // - name: body
  491. // in: body
  492. // schema:
  493. // $ref: "#/definitions/MergePullRequestOption"
  494. // responses:
  495. // "200":
  496. // "$ref": "#/responses/empty"
  497. // "405":
  498. // "$ref": "#/responses/empty"
  499. pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  500. if err != nil {
  501. if models.IsErrPullRequestNotExist(err) {
  502. ctx.NotFound("GetPullRequestByIndex", err)
  503. } else {
  504. ctx.Error(500, "GetPullRequestByIndex", err)
  505. }
  506. return
  507. }
  508. if err = pr.GetHeadRepo(); err != nil {
  509. ctx.ServerError("GetHeadRepo", err)
  510. return
  511. }
  512. err = pr.LoadIssue()
  513. if err != nil {
  514. ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
  515. return
  516. }
  517. pr.Issue.Repo = ctx.Repo.Repository
  518. if ctx.IsSigned {
  519. // Update issue-user.
  520. if err = pr.Issue.ReadBy(ctx.User.ID); err != nil {
  521. ctx.Error(500, "ReadBy", err)
  522. return
  523. }
  524. }
  525. if pr.Issue.IsClosed {
  526. ctx.NotFound()
  527. return
  528. }
  529. if !pr.CanAutoMerge() || pr.HasMerged || pr.IsWorkInProgress() {
  530. ctx.Status(405)
  531. return
  532. }
  533. if len(form.Do) == 0 {
  534. form.Do = string(models.MergeStyleMerge)
  535. }
  536. message := strings.TrimSpace(form.MergeTitleField)
  537. if len(message) == 0 {
  538. if models.MergeStyle(form.Do) == models.MergeStyleMerge {
  539. message = pr.GetDefaultMergeMessage()
  540. }
  541. if models.MergeStyle(form.Do) == models.MergeStyleSquash {
  542. message = pr.GetDefaultSquashMessage()
  543. }
  544. }
  545. form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
  546. if len(form.MergeMessageField) > 0 {
  547. message += "\n\n" + form.MergeMessageField
  548. }
  549. if err := pull.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
  550. if models.IsErrInvalidMergeStyle(err) {
  551. ctx.Status(405)
  552. return
  553. }
  554. ctx.Error(500, "Merge", err)
  555. return
  556. }
  557. log.Trace("Pull request merged: %d", pr.ID)
  558. ctx.Status(200)
  559. }
  560. func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) {
  561. baseRepo := ctx.Repo.Repository
  562. // Get compared branches information
  563. // format: <base branch>...[<head repo>:]<head branch>
  564. // base<-head: master...head:feature
  565. // same repo: master...feature
  566. // TODO: Validate form first?
  567. baseBranch := form.Base
  568. var (
  569. headUser *models.User
  570. headBranch string
  571. isSameRepo bool
  572. err error
  573. )
  574. // If there is no head repository, it means pull request between same repository.
  575. headInfos := strings.Split(form.Head, ":")
  576. if len(headInfos) == 1 {
  577. isSameRepo = true
  578. headUser = ctx.Repo.Owner
  579. headBranch = headInfos[0]
  580. } else if len(headInfos) == 2 {
  581. headUser, err = models.GetUserByName(headInfos[0])
  582. if err != nil {
  583. if models.IsErrUserNotExist(err) {
  584. ctx.NotFound("GetUserByName")
  585. } else {
  586. ctx.ServerError("GetUserByName", err)
  587. }
  588. return nil, nil, nil, nil, "", ""
  589. }
  590. headBranch = headInfos[1]
  591. } else {
  592. ctx.NotFound()
  593. return nil, nil, nil, nil, "", ""
  594. }
  595. ctx.Repo.PullRequest.SameRepo = isSameRepo
  596. log.Info("Base branch: %s", baseBranch)
  597. log.Info("Repo path: %s", ctx.Repo.GitRepo.Path)
  598. // Check if base branch is valid.
  599. if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
  600. ctx.NotFound("IsBranchExist")
  601. return nil, nil, nil, nil, "", ""
  602. }
  603. // Check if current user has fork of repository or in the same repository.
  604. headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID)
  605. if !has && !isSameRepo {
  606. log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
  607. ctx.NotFound("HasForkedRepo")
  608. return nil, nil, nil, nil, "", ""
  609. }
  610. var headGitRepo *git.Repository
  611. if isSameRepo {
  612. headRepo = ctx.Repo.Repository
  613. headGitRepo = ctx.Repo.GitRepo
  614. } else {
  615. headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
  616. if err != nil {
  617. ctx.Error(500, "OpenRepository", err)
  618. return nil, nil, nil, nil, "", ""
  619. }
  620. }
  621. // user should have permission to read baseRepo's codes and pulls, NOT headRepo's
  622. permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
  623. if err != nil {
  624. headGitRepo.Close()
  625. ctx.ServerError("GetUserRepoPermission", err)
  626. return nil, nil, nil, nil, "", ""
  627. }
  628. if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) {
  629. if log.IsTrace() {
  630. log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v",
  631. ctx.User,
  632. baseRepo,
  633. permBase)
  634. }
  635. headGitRepo.Close()
  636. ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
  637. return nil, nil, nil, nil, "", ""
  638. }
  639. // user should have permission to read headrepo's codes
  640. permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
  641. if err != nil {
  642. headGitRepo.Close()
  643. ctx.ServerError("GetUserRepoPermission", err)
  644. return nil, nil, nil, nil, "", ""
  645. }
  646. if !permHead.CanRead(models.UnitTypeCode) {
  647. if log.IsTrace() {
  648. log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
  649. ctx.User,
  650. headRepo,
  651. permHead)
  652. }
  653. headGitRepo.Close()
  654. ctx.NotFound("Can't read headRepo UnitTypeCode")
  655. return nil, nil, nil, nil, "", ""
  656. }
  657. // Check if head branch is valid.
  658. if !headGitRepo.IsBranchExist(headBranch) {
  659. headGitRepo.Close()
  660. ctx.NotFound()
  661. return nil, nil, nil, nil, "", ""
  662. }
  663. compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
  664. if err != nil {
  665. headGitRepo.Close()
  666. ctx.Error(500, "GetCompareInfo", err)
  667. return nil, nil, nil, nil, "", ""
  668. }
  669. return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
  670. }