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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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. "strings"
  8. "code.gitea.io/git"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/log"
  12. api "code.gitea.io/sdk/gitea"
  13. )
  14. // ListPullRequests returns a list of all PRs
  15. func ListPullRequests(ctx *context.APIContext, form api.ListPullRequestsOptions) {
  16. prs, maxResults, err := models.PullRequests(ctx.Repo.Repository.ID, &models.PullRequestsOptions{
  17. Page: ctx.QueryInt("page"),
  18. State: ctx.QueryTrim("state"),
  19. SortType: ctx.QueryTrim("sort"),
  20. Labels: ctx.QueryStrings("labels"),
  21. MilestoneID: ctx.QueryInt64("milestone"),
  22. })
  23. /*prs, maxResults, err := models.PullRequests(ctx.Repo.Repository.ID, &models.PullRequestsOptions{
  24. Page: form.Page,
  25. State: form.State,
  26. })*/
  27. if err != nil {
  28. ctx.Error(500, "PullRequests", err)
  29. return
  30. }
  31. apiPrs := make([]*api.PullRequest, len(prs))
  32. for i := range prs {
  33. prs[i].LoadIssue()
  34. prs[i].LoadAttributes()
  35. prs[i].GetBaseRepo()
  36. prs[i].GetHeadRepo()
  37. apiPrs[i] = prs[i].APIFormat()
  38. }
  39. ctx.SetLinkHeader(int(maxResults), models.ItemsPerPage)
  40. ctx.JSON(200, &apiPrs)
  41. }
  42. // GetPullRequest returns a single PR based on index
  43. func GetPullRequest(ctx *context.APIContext) {
  44. pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  45. if err != nil {
  46. if models.IsErrPullRequestNotExist(err) {
  47. ctx.Status(404)
  48. } else {
  49. ctx.Error(500, "GetPullRequestByIndex", err)
  50. }
  51. return
  52. }
  53. pr.GetBaseRepo()
  54. pr.GetHeadRepo()
  55. ctx.JSON(200, pr.APIFormat())
  56. }
  57. // CreatePullRequest does what it says
  58. func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption) {
  59. var (
  60. repo = ctx.Repo.Repository
  61. labelIDs []int64
  62. assigneeID int64
  63. milestoneID int64
  64. )
  65. // Get repo/branch information
  66. headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
  67. if ctx.Written() {
  68. return
  69. }
  70. // Check if another PR exists with the same targets
  71. existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch)
  72. if err != nil {
  73. if !models.IsErrPullRequestNotExist(err) {
  74. ctx.Error(500, "GetUnmergedPullRequest", err)
  75. return
  76. }
  77. } else {
  78. err = models.ErrPullRequestAlreadyExists{
  79. ID: existingPr.ID,
  80. IssueID: existingPr.Index,
  81. HeadRepoID: existingPr.HeadRepoID,
  82. BaseRepoID: existingPr.BaseRepoID,
  83. HeadBranch: existingPr.HeadBranch,
  84. BaseBranch: existingPr.BaseBranch,
  85. }
  86. ctx.Error(409, "GetUnmergedPullRequest", err)
  87. return
  88. }
  89. if len(form.Labels) > 0 {
  90. labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
  91. if err != nil {
  92. ctx.Error(500, "GetLabelsInRepoByIDs", err)
  93. return
  94. }
  95. labelIDs = make([]int64, len(labels))
  96. for i := range labels {
  97. labelIDs[i] = labels[i].ID
  98. }
  99. }
  100. if form.Milestone > 0 {
  101. milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
  102. if err != nil {
  103. if models.IsErrMilestoneNotExist(err) {
  104. ctx.Status(404)
  105. } else {
  106. ctx.Error(500, "GetMilestoneByRepoID", err)
  107. }
  108. return
  109. }
  110. milestoneID = milestone.ID
  111. }
  112. if len(form.Assignee) > 0 {
  113. assigneeUser, err := models.GetUserByName(form.Assignee)
  114. if err != nil {
  115. if models.IsErrUserNotExist(err) {
  116. ctx.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee))
  117. } else {
  118. ctx.Error(500, "GetUserByName", err)
  119. }
  120. return
  121. }
  122. assignee, err := repo.GetAssigneeByID(assigneeUser.ID)
  123. if err != nil {
  124. ctx.Error(500, "GetAssigneeByID", err)
  125. return
  126. }
  127. assigneeID = assignee.ID
  128. }
  129. patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch)
  130. if err != nil {
  131. ctx.Error(500, "GetPatch", err)
  132. return
  133. }
  134. prIssue := &models.Issue{
  135. RepoID: repo.ID,
  136. Index: repo.NextIssueIndex(),
  137. Title: form.Title,
  138. PosterID: ctx.User.ID,
  139. Poster: ctx.User,
  140. MilestoneID: milestoneID,
  141. AssigneeID: assigneeID,
  142. IsPull: true,
  143. Content: form.Body,
  144. }
  145. pr := &models.PullRequest{
  146. HeadRepoID: headRepo.ID,
  147. BaseRepoID: repo.ID,
  148. HeadUserName: headUser.Name,
  149. HeadBranch: headBranch,
  150. BaseBranch: baseBranch,
  151. HeadRepo: headRepo,
  152. BaseRepo: repo,
  153. MergeBase: prInfo.MergeBase,
  154. Type: models.PullRequestGitea,
  155. }
  156. if err := models.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, patch); err != nil {
  157. ctx.Error(500, "NewPullRequest", err)
  158. return
  159. } else if err := pr.PushToBaseRepo(); err != nil {
  160. ctx.Error(500, "PushToBaseRepo", err)
  161. return
  162. }
  163. log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
  164. ctx.JSON(201, pr.APIFormat())
  165. }
  166. // EditPullRequest does what it says
  167. func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
  168. pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  169. if err != nil {
  170. if models.IsErrPullRequestNotExist(err) {
  171. ctx.Status(404)
  172. } else {
  173. ctx.Error(500, "GetPullRequestByIndex", err)
  174. }
  175. return
  176. }
  177. pr.LoadIssue()
  178. issue := pr.Issue
  179. if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter() {
  180. ctx.Status(403)
  181. return
  182. }
  183. if len(form.Title) > 0 {
  184. issue.Title = form.Title
  185. }
  186. if len(form.Body) > 0 {
  187. issue.Content = form.Body
  188. }
  189. if ctx.Repo.IsWriter() && len(form.Assignee) > 0 &&
  190. (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(form.Assignee)) {
  191. if len(form.Assignee) == 0 {
  192. issue.AssigneeID = 0
  193. } else {
  194. assignee, err := models.GetUserByName(form.Assignee)
  195. if err != nil {
  196. if models.IsErrUserNotExist(err) {
  197. ctx.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee))
  198. } else {
  199. ctx.Error(500, "GetUserByName", err)
  200. }
  201. return
  202. }
  203. issue.AssigneeID = assignee.ID
  204. }
  205. if err = models.UpdateIssueUserByAssignee(issue); err != nil {
  206. ctx.Error(500, "UpdateIssueUserByAssignee", err)
  207. return
  208. }
  209. }
  210. if ctx.Repo.IsWriter() && form.Milestone != 0 &&
  211. issue.MilestoneID != form.Milestone {
  212. oldMilestoneID := issue.MilestoneID
  213. issue.MilestoneID = form.Milestone
  214. if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
  215. ctx.Error(500, "ChangeMilestoneAssign", err)
  216. return
  217. }
  218. }
  219. if err = models.UpdateIssue(issue); err != nil {
  220. ctx.Error(500, "UpdateIssue", err)
  221. return
  222. }
  223. if form.State != nil {
  224. if err = issue.ChangeStatus(ctx.User, ctx.Repo.Repository, api.StateClosed == api.StateType(*form.State)); err != nil {
  225. ctx.Error(500, "ChangeStatus", err)
  226. return
  227. }
  228. }
  229. // Refetch from database
  230. pr, err = models.GetPullRequestByIndex(ctx.Repo.Repository.ID, pr.Index)
  231. if err != nil {
  232. if models.IsErrPullRequestNotExist(err) {
  233. ctx.Status(404)
  234. } else {
  235. ctx.Error(500, "GetPullRequestByIndex", err)
  236. }
  237. return
  238. }
  239. ctx.JSON(201, pr.APIFormat())
  240. }
  241. // IsPullRequestMerged checks if a PR exists given an index
  242. // - Returns 204 if it exists
  243. // Otherwise 404
  244. func IsPullRequestMerged(ctx *context.APIContext) {
  245. pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  246. if err != nil {
  247. if models.IsErrPullRequestNotExist(err) {
  248. ctx.Status(404)
  249. } else {
  250. ctx.Error(500, "GetPullRequestByIndex", err)
  251. }
  252. return
  253. }
  254. if pr.HasMerged {
  255. ctx.Status(204)
  256. }
  257. ctx.Status(404)
  258. }
  259. // MergePullRequest merges a PR given an index
  260. func MergePullRequest(ctx *context.APIContext) {
  261. pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  262. if err != nil {
  263. if models.IsErrPullRequestNotExist(err) {
  264. ctx.Handle(404, "GetPullRequestByIndex", err)
  265. } else {
  266. ctx.Error(500, "GetPullRequestByIndex", err)
  267. }
  268. return
  269. }
  270. if err = pr.GetHeadRepo(); err != nil {
  271. ctx.Handle(500, "GetHeadRepo", err)
  272. return
  273. }
  274. pr.LoadIssue()
  275. pr.Issue.Repo = ctx.Repo.Repository
  276. if ctx.IsSigned {
  277. // Update issue-user.
  278. if err = pr.Issue.ReadBy(ctx.User.ID); err != nil {
  279. ctx.Error(500, "ReadBy", err)
  280. return
  281. }
  282. }
  283. if pr.Issue.IsClosed {
  284. ctx.Status(404)
  285. return
  286. }
  287. if !pr.CanAutoMerge() || pr.HasMerged {
  288. ctx.Status(405)
  289. return
  290. }
  291. if err := pr.Merge(ctx.User, ctx.Repo.GitRepo); err != nil {
  292. ctx.Error(500, "Merge", err)
  293. return
  294. }
  295. log.Trace("Pull request merged: %d", pr.ID)
  296. ctx.Status(200)
  297. }
  298. func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.PullRequestInfo, string, string) {
  299. baseRepo := ctx.Repo.Repository
  300. // Get compared branches information
  301. // format: <base branch>...[<head repo>:]<head branch>
  302. // base<-head: master...head:feature
  303. // same repo: master...feature
  304. // TODO: Validate form first?
  305. baseBranch := form.Base
  306. var (
  307. headUser *models.User
  308. headBranch string
  309. isSameRepo bool
  310. err error
  311. )
  312. // If there is no head repository, it means pull request between same repository.
  313. headInfos := strings.Split(form.Head, ":")
  314. if len(headInfos) == 1 {
  315. isSameRepo = true
  316. headUser = ctx.Repo.Owner
  317. headBranch = headInfos[0]
  318. } else if len(headInfos) == 2 {
  319. headUser, err = models.GetUserByName(headInfos[0])
  320. if err != nil {
  321. if models.IsErrUserNotExist(err) {
  322. ctx.Handle(404, "GetUserByName", nil)
  323. } else {
  324. ctx.Handle(500, "GetUserByName", err)
  325. }
  326. return nil, nil, nil, nil, "", ""
  327. }
  328. headBranch = headInfos[1]
  329. } else {
  330. ctx.Status(404)
  331. return nil, nil, nil, nil, "", ""
  332. }
  333. ctx.Repo.PullRequest.SameRepo = isSameRepo
  334. log.Info("Base branch: %s", baseBranch)
  335. log.Info("Repo path: %s", ctx.Repo.GitRepo.Path)
  336. // Check if base branch is valid.
  337. if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
  338. ctx.Status(404)
  339. return nil, nil, nil, nil, "", ""
  340. }
  341. // Check if current user has fork of repository or in the same repository.
  342. headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID)
  343. if !has && !isSameRepo {
  344. log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
  345. ctx.Status(404)
  346. return nil, nil, nil, nil, "", ""
  347. }
  348. var headGitRepo *git.Repository
  349. if isSameRepo {
  350. headRepo = ctx.Repo.Repository
  351. headGitRepo = ctx.Repo.GitRepo
  352. } else {
  353. headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
  354. if err != nil {
  355. ctx.Error(500, "OpenRepository", err)
  356. return nil, nil, nil, nil, "", ""
  357. }
  358. }
  359. if !ctx.User.IsWriterOfRepo(headRepo) && !ctx.User.IsAdmin {
  360. log.Trace("ParseCompareInfo[%d]: does not have write access or site admin", baseRepo.ID)
  361. ctx.Status(404)
  362. return nil, nil, nil, nil, "", ""
  363. }
  364. // Check if head branch is valid.
  365. if !headGitRepo.IsBranchExist(headBranch) {
  366. ctx.Status(404)
  367. return nil, nil, nil, nil, "", ""
  368. }
  369. prInfo, err := headGitRepo.GetPullRequestInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
  370. if err != nil {
  371. ctx.Error(500, "GetPullRequestInfo", err)
  372. return nil, nil, nil, nil, "", ""
  373. }
  374. return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch
  375. }