You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

pull.go 21 kB

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