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

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