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

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