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

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