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

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