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.

repo.go 30 kB

Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
Template Repositories (#8768) * Start work on templates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Continue work Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix IsTemplate vs IsGenerated Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tabs vs spaces * Tabs vs Spaces * Add templates to API & start adding tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix integration tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Remove unused User Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move template tests to existing repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Minor re-check updates and cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test cleanup Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix optionalbool Signed-off-by: jolheiser <john.olheiser@gmail.com> * make fmt Signed-off-by: jolheiser <john.olheiser@gmail.com> * Test fixes and icon change Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add new user and repo for tests Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests (finally) Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update meta repo with env variables Signed-off-by: jolheiser <john.olheiser@gmail.com> * Move generation to create page Combine with repo create template Modify API search to prioritize owner for repo Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix tests and coverage Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix swagger and JS lint Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix API searching for own private repos Signed-off-by: jolheiser <john.olheiser@gmail.com> * Change wording Signed-off-by: jolheiser <john.olheiser@gmail.com> * Fix repo search test. User had a private repo that didn't show up Signed-off-by: jolheiser <john.olheiser@gmail.com> * Another search test fix Signed-off-by: jolheiser <john.olheiser@gmail.com> * Clarify git content Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Feedback updates Signed-off-by: jolheiser <john.olheiser@gmail.com> * Add topics WIP Signed-off-by: jolheiser <john.olheiser@gmail.com> * Finish adding topics Signed-off-by: jolheiser <john.olheiser@gmail.com> * Update locale Signed-off-by: jolheiser <john.olheiser@gmail.com>
6 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "bytes"
  8. "errors"
  9. "fmt"
  10. "net/http"
  11. "net/url"
  12. "strings"
  13. "code.gitea.io/gitea/models"
  14. "code.gitea.io/gitea/modules/auth"
  15. "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/convert"
  17. "code.gitea.io/gitea/modules/git"
  18. "code.gitea.io/gitea/modules/graceful"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/migrations"
  21. "code.gitea.io/gitea/modules/notification"
  22. "code.gitea.io/gitea/modules/setting"
  23. "code.gitea.io/gitea/modules/structs"
  24. api "code.gitea.io/gitea/modules/structs"
  25. "code.gitea.io/gitea/modules/util"
  26. "code.gitea.io/gitea/modules/validation"
  27. mirror_service "code.gitea.io/gitea/services/mirror"
  28. repo_service "code.gitea.io/gitea/services/repository"
  29. )
  30. var searchOrderByMap = map[string]map[string]models.SearchOrderBy{
  31. "asc": {
  32. "alpha": models.SearchOrderByAlphabetically,
  33. "created": models.SearchOrderByOldest,
  34. "updated": models.SearchOrderByLeastUpdated,
  35. "size": models.SearchOrderBySize,
  36. "id": models.SearchOrderByID,
  37. },
  38. "desc": {
  39. "alpha": models.SearchOrderByAlphabeticallyReverse,
  40. "created": models.SearchOrderByNewest,
  41. "updated": models.SearchOrderByRecentUpdated,
  42. "size": models.SearchOrderBySizeReverse,
  43. "id": models.SearchOrderByIDReverse,
  44. },
  45. }
  46. // Search repositories via options
  47. func Search(ctx *context.APIContext) {
  48. // swagger:operation GET /repos/search repository repoSearch
  49. // ---
  50. // summary: Search for repositories
  51. // produces:
  52. // - application/json
  53. // parameters:
  54. // - name: q
  55. // in: query
  56. // description: keyword
  57. // type: string
  58. // - name: topic
  59. // in: query
  60. // description: Limit search to repositories with keyword as topic
  61. // type: boolean
  62. // - name: includeDesc
  63. // in: query
  64. // description: include search of keyword within repository description
  65. // type: boolean
  66. // - name: uid
  67. // in: query
  68. // description: search only for repos that the user with the given id owns or contributes to
  69. // type: integer
  70. // format: int64
  71. // - name: priority_owner_id
  72. // in: query
  73. // description: repo owner to prioritize in the results
  74. // type: integer
  75. // format: int64
  76. // - name: starredBy
  77. // in: query
  78. // description: search only for repos that the user with the given id has starred
  79. // type: integer
  80. // format: int64
  81. // - name: private
  82. // in: query
  83. // description: include private repositories this user has access to (defaults to true)
  84. // type: boolean
  85. // - name: template
  86. // in: query
  87. // description: include template repositories this user has access to (defaults to true)
  88. // type: boolean
  89. // - name: page
  90. // in: query
  91. // description: page number of results to return (1-based)
  92. // type: integer
  93. // - name: limit
  94. // in: query
  95. // description: page size of results, maximum page size is 50
  96. // type: integer
  97. // - name: mode
  98. // in: query
  99. // description: type of repository to search for. Supported values are
  100. // "fork", "source", "mirror" and "collaborative"
  101. // type: string
  102. // - name: exclusive
  103. // in: query
  104. // description: if `uid` is given, search only for repos that the user owns
  105. // type: boolean
  106. // - name: sort
  107. // in: query
  108. // description: sort repos by attribute. Supported values are
  109. // "alpha", "created", "updated", "size", and "id".
  110. // Default is "alpha"
  111. // type: string
  112. // - name: order
  113. // in: query
  114. // description: sort order, either "asc" (ascending) or "desc" (descending).
  115. // Default is "asc", ignored if "sort" is not specified.
  116. // type: string
  117. // responses:
  118. // "200":
  119. // "$ref": "#/responses/SearchResults"
  120. // "422":
  121. // "$ref": "#/responses/validationError"
  122. opts := &models.SearchRepoOptions{
  123. Keyword: strings.Trim(ctx.Query("q"), " "),
  124. OwnerID: ctx.QueryInt64("uid"),
  125. PriorityOwnerID: ctx.QueryInt64("priority_owner_id"),
  126. Page: ctx.QueryInt("page"),
  127. PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
  128. TopicOnly: ctx.QueryBool("topic"),
  129. Collaborate: util.OptionalBoolNone,
  130. Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")),
  131. Template: util.OptionalBoolNone,
  132. UserIsAdmin: ctx.IsUserSiteAdmin(),
  133. UserID: ctx.Data["SignedUserID"].(int64),
  134. StarredByID: ctx.QueryInt64("starredBy"),
  135. IncludeDescription: ctx.QueryBool("includeDesc"),
  136. }
  137. if ctx.Query("template") != "" {
  138. opts.Template = util.OptionalBoolOf(ctx.QueryBool("template"))
  139. }
  140. if ctx.QueryBool("exclusive") {
  141. opts.Collaborate = util.OptionalBoolFalse
  142. }
  143. var mode = ctx.Query("mode")
  144. switch mode {
  145. case "source":
  146. opts.Fork = util.OptionalBoolFalse
  147. opts.Mirror = util.OptionalBoolFalse
  148. case "fork":
  149. opts.Fork = util.OptionalBoolTrue
  150. case "mirror":
  151. opts.Mirror = util.OptionalBoolTrue
  152. case "collaborative":
  153. opts.Mirror = util.OptionalBoolFalse
  154. opts.Collaborate = util.OptionalBoolTrue
  155. case "":
  156. default:
  157. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
  158. return
  159. }
  160. var sortMode = ctx.Query("sort")
  161. if len(sortMode) > 0 {
  162. var sortOrder = ctx.Query("order")
  163. if len(sortOrder) == 0 {
  164. sortOrder = "asc"
  165. }
  166. if searchModeMap, ok := searchOrderByMap[sortOrder]; ok {
  167. if orderBy, ok := searchModeMap[sortMode]; ok {
  168. opts.OrderBy = orderBy
  169. } else {
  170. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
  171. return
  172. }
  173. } else {
  174. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
  175. return
  176. }
  177. }
  178. var err error
  179. repos, count, err := models.SearchRepository(opts)
  180. if err != nil {
  181. ctx.JSON(500, api.SearchError{
  182. OK: false,
  183. Error: err.Error(),
  184. })
  185. return
  186. }
  187. results := make([]*api.Repository, len(repos))
  188. for i, repo := range repos {
  189. if err = repo.GetOwner(); err != nil {
  190. ctx.JSON(500, api.SearchError{
  191. OK: false,
  192. Error: err.Error(),
  193. })
  194. return
  195. }
  196. accessMode, err := models.AccessLevel(ctx.User, repo)
  197. if err != nil {
  198. ctx.JSON(500, api.SearchError{
  199. OK: false,
  200. Error: err.Error(),
  201. })
  202. }
  203. results[i] = repo.APIFormat(accessMode)
  204. }
  205. ctx.SetLinkHeader(int(count), setting.API.MaxResponseItems)
  206. ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count))
  207. ctx.JSON(200, api.SearchResults{
  208. OK: true,
  209. Data: results,
  210. })
  211. }
  212. // CreateUserRepo create a repository for a user
  213. func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateRepoOption) {
  214. if opt.AutoInit && opt.Readme == "" {
  215. opt.Readme = "Default"
  216. }
  217. repo, err := repo_service.CreateRepository(ctx.User, owner, models.CreateRepoOptions{
  218. Name: opt.Name,
  219. Description: opt.Description,
  220. IssueLabels: opt.IssueLabels,
  221. Gitignores: opt.Gitignores,
  222. License: opt.License,
  223. Readme: opt.Readme,
  224. IsPrivate: opt.Private,
  225. AutoInit: opt.AutoInit,
  226. })
  227. if err != nil {
  228. if models.IsErrRepoAlreadyExist(err) {
  229. ctx.Error(409, "", "The repository with the same name already exists.")
  230. } else if models.IsErrNameReserved(err) ||
  231. models.IsErrNamePatternNotAllowed(err) {
  232. ctx.Error(422, "", err)
  233. } else {
  234. ctx.Error(500, "CreateRepository", err)
  235. }
  236. return
  237. }
  238. ctx.JSON(201, repo.APIFormat(models.AccessModeOwner))
  239. }
  240. // Create one repository of mine
  241. func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
  242. // swagger:operation POST /user/repos repository user createCurrentUserRepo
  243. // ---
  244. // summary: Create a repository
  245. // consumes:
  246. // - application/json
  247. // produces:
  248. // - application/json
  249. // parameters:
  250. // - name: body
  251. // in: body
  252. // schema:
  253. // "$ref": "#/definitions/CreateRepoOption"
  254. // responses:
  255. // "201":
  256. // "$ref": "#/responses/Repository"
  257. // "409":
  258. // description: The repository with the same name already exists.
  259. // "422":
  260. // "$ref": "#/responses/validationError"
  261. if ctx.User.IsOrganization() {
  262. // Shouldn't reach this condition, but just in case.
  263. ctx.Error(422, "", "not allowed creating repository for organization")
  264. return
  265. }
  266. CreateUserRepo(ctx, ctx.User, opt)
  267. }
  268. // CreateOrgRepo create one repository of the organization
  269. func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
  270. // swagger:operation POST /org/{org}/repos organization createOrgRepo
  271. // ---
  272. // summary: Create a repository in an organization
  273. // consumes:
  274. // - application/json
  275. // produces:
  276. // - application/json
  277. // parameters:
  278. // - name: org
  279. // in: path
  280. // description: name of organization
  281. // type: string
  282. // required: true
  283. // - name: body
  284. // in: body
  285. // schema:
  286. // "$ref": "#/definitions/CreateRepoOption"
  287. // responses:
  288. // "201":
  289. // "$ref": "#/responses/Repository"
  290. // "422":
  291. // "$ref": "#/responses/validationError"
  292. // "403":
  293. // "$ref": "#/responses/forbidden"
  294. org, err := models.GetOrgByName(ctx.Params(":org"))
  295. if err != nil {
  296. if models.IsErrOrgNotExist(err) {
  297. ctx.Error(422, "", err)
  298. } else {
  299. ctx.Error(500, "GetOrgByName", err)
  300. }
  301. return
  302. }
  303. if !models.HasOrgVisible(org, ctx.User) {
  304. ctx.NotFound("HasOrgVisible", nil)
  305. return
  306. }
  307. if !ctx.User.IsAdmin {
  308. canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
  309. if err != nil {
  310. ctx.ServerError("CanCreateOrgRepo", err)
  311. return
  312. } else if !canCreate {
  313. ctx.Error(403, "", "Given user is not allowed to create repository in organization.")
  314. return
  315. }
  316. }
  317. CreateUserRepo(ctx, org, opt)
  318. }
  319. // Migrate migrate remote git repository to gitea
  320. func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
  321. // swagger:operation POST /repos/migrate repository repoMigrate
  322. // ---
  323. // summary: Migrate a remote git repository
  324. // consumes:
  325. // - application/json
  326. // produces:
  327. // - application/json
  328. // parameters:
  329. // - name: body
  330. // in: body
  331. // schema:
  332. // "$ref": "#/definitions/MigrateRepoForm"
  333. // responses:
  334. // "201":
  335. // "$ref": "#/responses/Repository"
  336. ctxUser := ctx.User
  337. // Not equal means context user is an organization,
  338. // or is another user/organization if current user is admin.
  339. if form.UID != ctxUser.ID {
  340. org, err := models.GetUserByID(form.UID)
  341. if err != nil {
  342. if models.IsErrUserNotExist(err) {
  343. ctx.Error(422, "", err)
  344. } else {
  345. ctx.Error(500, "GetUserByID", err)
  346. }
  347. return
  348. }
  349. ctxUser = org
  350. }
  351. if ctx.HasError() {
  352. ctx.Error(422, "", ctx.GetErrMsg())
  353. return
  354. }
  355. if !ctx.User.IsAdmin {
  356. if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID {
  357. ctx.Error(403, "", "Given user is not an organization.")
  358. return
  359. }
  360. if ctxUser.IsOrganization() {
  361. // Check ownership of organization.
  362. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  363. if err != nil {
  364. ctx.Error(500, "IsOwnedBy", err)
  365. return
  366. } else if !isOwner {
  367. ctx.Error(403, "", "Given user is not owner of organization.")
  368. return
  369. }
  370. }
  371. }
  372. remoteAddr, err := form.ParseRemoteAddr(ctx.User)
  373. if err != nil {
  374. if models.IsErrInvalidCloneAddr(err) {
  375. addrErr := err.(models.ErrInvalidCloneAddr)
  376. switch {
  377. case addrErr.IsURLError:
  378. ctx.Error(422, "", err)
  379. case addrErr.IsPermissionDenied:
  380. ctx.Error(422, "", "You are not allowed to import local repositories.")
  381. case addrErr.IsInvalidPath:
  382. ctx.Error(422, "", "Invalid local path, it does not exist or not a directory.")
  383. default:
  384. ctx.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
  385. }
  386. } else {
  387. ctx.Error(500, "ParseRemoteAddr", err)
  388. }
  389. return
  390. }
  391. var gitServiceType = structs.PlainGitService
  392. u, err := url.Parse(remoteAddr)
  393. if err == nil && strings.EqualFold(u.Host, "github.com") {
  394. gitServiceType = structs.GithubService
  395. }
  396. var opts = migrations.MigrateOptions{
  397. CloneAddr: remoteAddr,
  398. RepoName: form.RepoName,
  399. Description: form.Description,
  400. Private: form.Private || setting.Repository.ForcePrivate,
  401. Mirror: form.Mirror,
  402. AuthUsername: form.AuthUsername,
  403. AuthPassword: form.AuthPassword,
  404. Wiki: form.Wiki,
  405. Issues: form.Issues,
  406. Milestones: form.Milestones,
  407. Labels: form.Labels,
  408. Comments: true,
  409. PullRequests: form.PullRequests,
  410. Releases: form.Releases,
  411. GitServiceType: gitServiceType,
  412. }
  413. if opts.Mirror {
  414. opts.Issues = false
  415. opts.Milestones = false
  416. opts.Labels = false
  417. opts.Comments = false
  418. opts.PullRequests = false
  419. opts.Releases = false
  420. }
  421. repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
  422. Name: opts.RepoName,
  423. Description: opts.Description,
  424. OriginalURL: form.CloneAddr,
  425. IsPrivate: opts.Private,
  426. IsMirror: opts.Mirror,
  427. Status: models.RepositoryBeingMigrated,
  428. })
  429. if err != nil {
  430. handleMigrateError(ctx, ctxUser, remoteAddr, err)
  431. return
  432. }
  433. opts.MigrateToRepoID = repo.ID
  434. defer func() {
  435. if e := recover(); e != nil {
  436. var buf bytes.Buffer
  437. fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
  438. err = errors.New(buf.String())
  439. }
  440. if err == nil {
  441. repo.Status = models.RepositoryReady
  442. if err := models.UpdateRepositoryCols(repo, "status"); err == nil {
  443. notification.NotifyMigrateRepository(ctx.User, ctxUser, repo)
  444. return
  445. }
  446. }
  447. if repo != nil {
  448. if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
  449. log.Error("DeleteRepository: %v", errDelete)
  450. }
  451. }
  452. }()
  453. if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, ctxUser.Name, opts); err != nil {
  454. handleMigrateError(ctx, ctxUser, remoteAddr, err)
  455. return
  456. }
  457. log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
  458. ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
  459. }
  460. func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
  461. switch {
  462. case models.IsErrRepoAlreadyExist(err):
  463. ctx.Error(409, "", "The repository with the same name already exists.")
  464. case migrations.IsRateLimitError(err):
  465. ctx.Error(422, "", "Remote visit addressed rate limitation.")
  466. case migrations.IsTwoFactorAuthError(err):
  467. ctx.Error(422, "", "Remote visit required two factors authentication.")
  468. case models.IsErrReachLimitOfRepo(err):
  469. ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
  470. case models.IsErrNameReserved(err):
  471. ctx.Error(422, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
  472. case models.IsErrNamePatternNotAllowed(err):
  473. ctx.Error(422, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
  474. default:
  475. err = util.URLSanitizedError(err, remoteAddr)
  476. if strings.Contains(err.Error(), "Authentication failed") ||
  477. strings.Contains(err.Error(), "Bad credentials") ||
  478. strings.Contains(err.Error(), "could not read Username") {
  479. ctx.Error(422, "", fmt.Sprintf("Authentication failed: %v.", err))
  480. } else if strings.Contains(err.Error(), "fatal:") {
  481. ctx.Error(422, "", fmt.Sprintf("Migration failed: %v.", err))
  482. } else {
  483. ctx.Error(500, "MigrateRepository", err)
  484. }
  485. }
  486. }
  487. // Get one repository
  488. func Get(ctx *context.APIContext) {
  489. // swagger:operation GET /repos/{owner}/{repo} repository repoGet
  490. // ---
  491. // summary: Get a repository
  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. // responses:
  506. // "200":
  507. // "$ref": "#/responses/Repository"
  508. ctx.JSON(200, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  509. }
  510. // GetByID returns a single Repository
  511. func GetByID(ctx *context.APIContext) {
  512. // swagger:operation GET /repositories/{id} repository repoGetByID
  513. // ---
  514. // summary: Get a repository by id
  515. // produces:
  516. // - application/json
  517. // parameters:
  518. // - name: id
  519. // in: path
  520. // description: id of the repo to get
  521. // type: integer
  522. // format: int64
  523. // required: true
  524. // responses:
  525. // "200":
  526. // "$ref": "#/responses/Repository"
  527. repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
  528. if err != nil {
  529. if models.IsErrRepoNotExist(err) {
  530. ctx.NotFound()
  531. } else {
  532. ctx.Error(500, "GetRepositoryByID", err)
  533. }
  534. return
  535. }
  536. perm, err := models.GetUserRepoPermission(repo, ctx.User)
  537. if err != nil {
  538. ctx.Error(500, "AccessLevel", err)
  539. return
  540. } else if !perm.HasAccess() {
  541. ctx.NotFound()
  542. return
  543. }
  544. ctx.JSON(200, repo.APIFormat(perm.AccessMode))
  545. }
  546. // Edit edit repository properties
  547. func Edit(ctx *context.APIContext, opts api.EditRepoOption) {
  548. // swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit
  549. // ---
  550. // summary: Edit a repository's properties. Only fields that are set will be changed.
  551. // produces:
  552. // - application/json
  553. // parameters:
  554. // - name: owner
  555. // in: path
  556. // description: owner of the repo to edit
  557. // type: string
  558. // required: true
  559. // - name: repo
  560. // in: path
  561. // description: name of the repo to edit
  562. // type: string
  563. // required: true
  564. // required: true
  565. // - name: body
  566. // in: body
  567. // description: "Properties of a repo that you can edit"
  568. // schema:
  569. // "$ref": "#/definitions/EditRepoOption"
  570. // responses:
  571. // "200":
  572. // "$ref": "#/responses/Repository"
  573. // "403":
  574. // "$ref": "#/responses/forbidden"
  575. // "422":
  576. // "$ref": "#/responses/validationError"
  577. if err := updateBasicProperties(ctx, opts); err != nil {
  578. return
  579. }
  580. if err := updateRepoUnits(ctx, opts); err != nil {
  581. return
  582. }
  583. if opts.Archived != nil {
  584. if err := updateRepoArchivedState(ctx, opts); err != nil {
  585. return
  586. }
  587. }
  588. ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  589. }
  590. // updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility
  591. func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) error {
  592. owner := ctx.Repo.Owner
  593. repo := ctx.Repo.Repository
  594. newRepoName := repo.Name
  595. if opts.Name != nil {
  596. newRepoName = *opts.Name
  597. }
  598. // Check if repository name has been changed and not just a case change
  599. if repo.LowerName != strings.ToLower(newRepoName) {
  600. if err := repo_service.ChangeRepositoryName(ctx.User, repo, newRepoName); err != nil {
  601. switch {
  602. case models.IsErrRepoAlreadyExist(err):
  603. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err)
  604. case models.IsErrNameReserved(err):
  605. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err)
  606. case models.IsErrNamePatternNotAllowed(err):
  607. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(models.ErrNamePatternNotAllowed).Pattern), err)
  608. default:
  609. ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err)
  610. }
  611. return err
  612. }
  613. log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
  614. }
  615. // Update the name in the repo object for the response
  616. repo.Name = newRepoName
  617. repo.LowerName = strings.ToLower(newRepoName)
  618. if opts.Description != nil {
  619. repo.Description = *opts.Description
  620. }
  621. if opts.Website != nil {
  622. repo.Website = *opts.Website
  623. }
  624. visibilityChanged := false
  625. if opts.Private != nil {
  626. // Visibility of forked repository is forced sync with base repository.
  627. if repo.IsFork {
  628. *opts.Private = repo.BaseRepo.IsPrivate
  629. }
  630. visibilityChanged = repo.IsPrivate != *opts.Private
  631. // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
  632. if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.User.IsAdmin {
  633. err := fmt.Errorf("cannot change private repository to public")
  634. ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err)
  635. return err
  636. }
  637. repo.IsPrivate = *opts.Private
  638. }
  639. if opts.Template != nil {
  640. repo.IsTemplate = *opts.Template
  641. }
  642. // Default branch only updated if changed and exist
  643. if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch) {
  644. if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
  645. if !git.IsErrUnsupportedVersion(err) {
  646. ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
  647. return err
  648. }
  649. }
  650. repo.DefaultBranch = *opts.DefaultBranch
  651. }
  652. if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
  653. ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
  654. return err
  655. }
  656. log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
  657. return nil
  658. }
  659. // updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings
  660. func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
  661. owner := ctx.Repo.Owner
  662. repo := ctx.Repo.Repository
  663. var units []models.RepoUnit
  664. for _, tp := range models.MustRepoUnits {
  665. units = append(units, models.RepoUnit{
  666. RepoID: repo.ID,
  667. Type: tp,
  668. Config: new(models.UnitConfig),
  669. })
  670. }
  671. if opts.HasIssues == nil {
  672. // If HasIssues setting not touched, rewrite existing repo unit
  673. if unit, err := repo.GetUnit(models.UnitTypeIssues); err == nil {
  674. units = append(units, *unit)
  675. } else if unit, err := repo.GetUnit(models.UnitTypeExternalTracker); err == nil {
  676. units = append(units, *unit)
  677. }
  678. } else if *opts.HasIssues {
  679. if opts.ExternalTracker != nil {
  680. // Check that values are valid
  681. if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
  682. err := fmt.Errorf("External tracker URL not valid")
  683. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL", err)
  684. return err
  685. }
  686. if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
  687. err := fmt.Errorf("External tracker URL format not valid")
  688. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL format", err)
  689. return err
  690. }
  691. units = append(units, models.RepoUnit{
  692. RepoID: repo.ID,
  693. Type: models.UnitTypeExternalTracker,
  694. Config: &models.ExternalTrackerConfig{
  695. ExternalTrackerURL: opts.ExternalTracker.ExternalTrackerURL,
  696. ExternalTrackerFormat: opts.ExternalTracker.ExternalTrackerFormat,
  697. ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle,
  698. },
  699. })
  700. } else {
  701. // Default to built-in tracker
  702. var config *models.IssuesConfig
  703. if opts.InternalTracker != nil {
  704. config = &models.IssuesConfig{
  705. EnableTimetracker: opts.InternalTracker.EnableTimeTracker,
  706. AllowOnlyContributorsToTrackTime: opts.InternalTracker.AllowOnlyContributorsToTrackTime,
  707. EnableDependencies: opts.InternalTracker.EnableIssueDependencies,
  708. }
  709. } else if unit, err := repo.GetUnit(models.UnitTypeIssues); err != nil {
  710. // Unit type doesn't exist so we make a new config file with default values
  711. config = &models.IssuesConfig{
  712. EnableTimetracker: true,
  713. AllowOnlyContributorsToTrackTime: true,
  714. EnableDependencies: true,
  715. }
  716. } else {
  717. config = unit.IssuesConfig()
  718. }
  719. units = append(units, models.RepoUnit{
  720. RepoID: repo.ID,
  721. Type: models.UnitTypeIssues,
  722. Config: config,
  723. })
  724. }
  725. }
  726. if opts.HasWiki == nil {
  727. // If HasWiki setting not touched, rewrite existing repo unit
  728. if unit, err := repo.GetUnit(models.UnitTypeWiki); err == nil {
  729. units = append(units, *unit)
  730. } else if unit, err := repo.GetUnit(models.UnitTypeExternalWiki); err == nil {
  731. units = append(units, *unit)
  732. }
  733. } else if *opts.HasWiki {
  734. if opts.ExternalWiki != nil {
  735. // Check that values are valid
  736. if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
  737. err := fmt.Errorf("External wiki URL not valid")
  738. ctx.Error(http.StatusUnprocessableEntity, "", "Invalid external wiki URL")
  739. return err
  740. }
  741. units = append(units, models.RepoUnit{
  742. RepoID: repo.ID,
  743. Type: models.UnitTypeExternalWiki,
  744. Config: &models.ExternalWikiConfig{
  745. ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL,
  746. },
  747. })
  748. } else {
  749. config := &models.UnitConfig{}
  750. units = append(units, models.RepoUnit{
  751. RepoID: repo.ID,
  752. Type: models.UnitTypeWiki,
  753. Config: config,
  754. })
  755. }
  756. }
  757. if opts.HasPullRequests == nil {
  758. // If HasPullRequest setting not touched, rewrite existing repo unit
  759. if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil {
  760. units = append(units, *unit)
  761. }
  762. } else if *opts.HasPullRequests {
  763. // We do allow setting individual PR settings through the API, so
  764. // we get the config settings and then set them
  765. // if those settings were provided in the opts.
  766. unit, err := repo.GetUnit(models.UnitTypePullRequests)
  767. var config *models.PullRequestsConfig
  768. if err != nil {
  769. // Unit type doesn't exist so we make a new config file with default values
  770. config = &models.PullRequestsConfig{
  771. IgnoreWhitespaceConflicts: false,
  772. AllowMerge: true,
  773. AllowRebase: true,
  774. AllowRebaseMerge: true,
  775. AllowSquash: true,
  776. }
  777. } else {
  778. config = unit.PullRequestsConfig()
  779. }
  780. if opts.IgnoreWhitespaceConflicts != nil {
  781. config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
  782. }
  783. if opts.AllowMerge != nil {
  784. config.AllowMerge = *opts.AllowMerge
  785. }
  786. if opts.AllowRebase != nil {
  787. config.AllowRebase = *opts.AllowRebase
  788. }
  789. if opts.AllowRebaseMerge != nil {
  790. config.AllowRebaseMerge = *opts.AllowRebaseMerge
  791. }
  792. if opts.AllowSquash != nil {
  793. config.AllowSquash = *opts.AllowSquash
  794. }
  795. units = append(units, models.RepoUnit{
  796. RepoID: repo.ID,
  797. Type: models.UnitTypePullRequests,
  798. Config: config,
  799. })
  800. }
  801. if err := models.UpdateRepositoryUnits(repo, units); err != nil {
  802. ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
  803. return err
  804. }
  805. log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name)
  806. return nil
  807. }
  808. // updateRepoArchivedState updates repo's archive state
  809. func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) error {
  810. repo := ctx.Repo.Repository
  811. // archive / un-archive
  812. if opts.Archived != nil {
  813. if repo.IsMirror {
  814. err := fmt.Errorf("repo is a mirror, cannot archive/un-archive")
  815. ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
  816. return err
  817. }
  818. if *opts.Archived {
  819. if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
  820. log.Error("Tried to archive a repo: %s", err)
  821. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  822. return err
  823. }
  824. log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  825. } else {
  826. if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
  827. log.Error("Tried to un-archive a repo: %s", err)
  828. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  829. return err
  830. }
  831. log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  832. }
  833. }
  834. return nil
  835. }
  836. // Delete one repository
  837. func Delete(ctx *context.APIContext) {
  838. // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
  839. // ---
  840. // summary: Delete a repository
  841. // produces:
  842. // - application/json
  843. // parameters:
  844. // - name: owner
  845. // in: path
  846. // description: owner of the repo to delete
  847. // type: string
  848. // required: true
  849. // - name: repo
  850. // in: path
  851. // description: name of the repo to delete
  852. // type: string
  853. // required: true
  854. // responses:
  855. // "204":
  856. // "$ref": "#/responses/empty"
  857. // "403":
  858. // "$ref": "#/responses/forbidden"
  859. owner := ctx.Repo.Owner
  860. repo := ctx.Repo.Repository
  861. canDelete, err := repo.CanUserDelete(ctx.User)
  862. if err != nil {
  863. ctx.Error(500, "CanUserDelete", err)
  864. return
  865. } else if !canDelete {
  866. ctx.Error(403, "", "Given user is not owner of organization.")
  867. return
  868. }
  869. if err := repo_service.DeleteRepository(ctx.User, repo); err != nil {
  870. ctx.Error(500, "DeleteRepository", err)
  871. return
  872. }
  873. log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
  874. ctx.Status(204)
  875. }
  876. // MirrorSync adds a mirrored repository to the sync queue
  877. func MirrorSync(ctx *context.APIContext) {
  878. // swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
  879. // ---
  880. // summary: Sync a mirrored repository
  881. // produces:
  882. // - application/json
  883. // parameters:
  884. // - name: owner
  885. // in: path
  886. // description: owner of the repo to sync
  887. // type: string
  888. // required: true
  889. // - name: repo
  890. // in: path
  891. // description: name of the repo to sync
  892. // type: string
  893. // required: true
  894. // responses:
  895. // "200":
  896. // "$ref": "#/responses/empty"
  897. repo := ctx.Repo.Repository
  898. if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  899. ctx.Error(403, "MirrorSync", "Must have write access")
  900. }
  901. mirror_service.StartToMirror(repo.ID)
  902. ctx.Status(200)
  903. }