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 31 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>
5 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>
5 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>
5 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>
5 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>
5 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>
5 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995
  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(http.StatusInternalServerError, 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(http.StatusInternalServerError, 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(http.StatusInternalServerError, 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(http.StatusOK, 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(http.StatusConflict, "", "The repository with the same name already exists.")
  230. } else if models.IsErrNameReserved(err) ||
  231. models.IsErrNamePatternNotAllowed(err) {
  232. ctx.Error(http.StatusUnprocessableEntity, "", err)
  233. } else {
  234. ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
  235. }
  236. return
  237. }
  238. ctx.JSON(http.StatusCreated, 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(http.StatusUnprocessableEntity, "", "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(http.StatusUnprocessableEntity, "", err)
  298. } else {
  299. ctx.Error(http.StatusInternalServerError, "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(http.StatusForbidden, "", "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. // "403":
  337. // "$ref": "#/responses/forbidden"
  338. // "422":
  339. // "$ref": "#/responses/validationError"
  340. ctxUser := ctx.User
  341. // Not equal means context user is an organization,
  342. // or is another user/organization if current user is admin.
  343. if form.UID != ctxUser.ID {
  344. org, err := models.GetUserByID(form.UID)
  345. if err != nil {
  346. if models.IsErrUserNotExist(err) {
  347. ctx.Error(http.StatusUnprocessableEntity, "", err)
  348. } else {
  349. ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
  350. }
  351. return
  352. }
  353. ctxUser = org
  354. }
  355. if ctx.HasError() {
  356. ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
  357. return
  358. }
  359. if !ctx.User.IsAdmin {
  360. if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID {
  361. ctx.Error(http.StatusForbidden, "", "Given user is not an organization.")
  362. return
  363. }
  364. if ctxUser.IsOrganization() {
  365. // Check ownership of organization.
  366. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  367. if err != nil {
  368. ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err)
  369. return
  370. } else if !isOwner {
  371. ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
  372. return
  373. }
  374. }
  375. }
  376. remoteAddr, err := form.ParseRemoteAddr(ctx.User)
  377. if err != nil {
  378. if models.IsErrInvalidCloneAddr(err) {
  379. addrErr := err.(models.ErrInvalidCloneAddr)
  380. switch {
  381. case addrErr.IsURLError:
  382. ctx.Error(http.StatusUnprocessableEntity, "", err)
  383. case addrErr.IsPermissionDenied:
  384. ctx.Error(http.StatusUnprocessableEntity, "", "You are not allowed to import local repositories.")
  385. case addrErr.IsInvalidPath:
  386. ctx.Error(http.StatusUnprocessableEntity, "", "Invalid local path, it does not exist or not a directory.")
  387. default:
  388. ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
  389. }
  390. } else {
  391. ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", err)
  392. }
  393. return
  394. }
  395. var gitServiceType = structs.PlainGitService
  396. u, err := url.Parse(remoteAddr)
  397. if err == nil && strings.EqualFold(u.Host, "github.com") {
  398. gitServiceType = structs.GithubService
  399. }
  400. var opts = migrations.MigrateOptions{
  401. CloneAddr: remoteAddr,
  402. RepoName: form.RepoName,
  403. Description: form.Description,
  404. Private: form.Private || setting.Repository.ForcePrivate,
  405. Mirror: form.Mirror,
  406. AuthUsername: form.AuthUsername,
  407. AuthPassword: form.AuthPassword,
  408. Wiki: form.Wiki,
  409. Issues: form.Issues,
  410. Milestones: form.Milestones,
  411. Labels: form.Labels,
  412. Comments: true,
  413. PullRequests: form.PullRequests,
  414. Releases: form.Releases,
  415. GitServiceType: gitServiceType,
  416. }
  417. if opts.Mirror {
  418. opts.Issues = false
  419. opts.Milestones = false
  420. opts.Labels = false
  421. opts.Comments = false
  422. opts.PullRequests = false
  423. opts.Releases = false
  424. }
  425. repo, err := models.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
  426. Name: opts.RepoName,
  427. Description: opts.Description,
  428. OriginalURL: form.CloneAddr,
  429. IsPrivate: opts.Private,
  430. IsMirror: opts.Mirror,
  431. Status: models.RepositoryBeingMigrated,
  432. })
  433. if err != nil {
  434. handleMigrateError(ctx, ctxUser, remoteAddr, err)
  435. return
  436. }
  437. opts.MigrateToRepoID = repo.ID
  438. defer func() {
  439. if e := recover(); e != nil {
  440. var buf bytes.Buffer
  441. fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
  442. err = errors.New(buf.String())
  443. }
  444. if err == nil {
  445. repo.Status = models.RepositoryReady
  446. if err := models.UpdateRepositoryCols(repo, "status"); err == nil {
  447. notification.NotifyMigrateRepository(ctx.User, ctxUser, repo)
  448. return
  449. }
  450. }
  451. if repo != nil {
  452. if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
  453. log.Error("DeleteRepository: %v", errDelete)
  454. }
  455. }
  456. }()
  457. if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, ctxUser.Name, opts); err != nil {
  458. handleMigrateError(ctx, ctxUser, remoteAddr, err)
  459. return
  460. }
  461. log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
  462. ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeAdmin))
  463. }
  464. func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
  465. switch {
  466. case models.IsErrRepoAlreadyExist(err):
  467. ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
  468. case migrations.IsRateLimitError(err):
  469. ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.")
  470. case migrations.IsTwoFactorAuthError(err):
  471. ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit required two factors authentication.")
  472. case models.IsErrReachLimitOfRepo(err):
  473. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
  474. case models.IsErrNameReserved(err):
  475. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
  476. case models.IsErrNamePatternNotAllowed(err):
  477. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
  478. default:
  479. err = util.URLSanitizedError(err, remoteAddr)
  480. if strings.Contains(err.Error(), "Authentication failed") ||
  481. strings.Contains(err.Error(), "Bad credentials") ||
  482. strings.Contains(err.Error(), "could not read Username") {
  483. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Authentication failed: %v.", err))
  484. } else if strings.Contains(err.Error(), "fatal:") {
  485. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Migration failed: %v.", err))
  486. } else {
  487. ctx.Error(http.StatusInternalServerError, "MigrateRepository", err)
  488. }
  489. }
  490. }
  491. // Get one repository
  492. func Get(ctx *context.APIContext) {
  493. // swagger:operation GET /repos/{owner}/{repo} repository repoGet
  494. // ---
  495. // summary: Get a repository
  496. // produces:
  497. // - application/json
  498. // parameters:
  499. // - name: owner
  500. // in: path
  501. // description: owner of the repo
  502. // type: string
  503. // required: true
  504. // - name: repo
  505. // in: path
  506. // description: name of the repo
  507. // type: string
  508. // required: true
  509. // responses:
  510. // "200":
  511. // "$ref": "#/responses/Repository"
  512. ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  513. }
  514. // GetByID returns a single Repository
  515. func GetByID(ctx *context.APIContext) {
  516. // swagger:operation GET /repositories/{id} repository repoGetByID
  517. // ---
  518. // summary: Get a repository by id
  519. // produces:
  520. // - application/json
  521. // parameters:
  522. // - name: id
  523. // in: path
  524. // description: id of the repo to get
  525. // type: integer
  526. // format: int64
  527. // required: true
  528. // responses:
  529. // "200":
  530. // "$ref": "#/responses/Repository"
  531. repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
  532. if err != nil {
  533. if models.IsErrRepoNotExist(err) {
  534. ctx.NotFound()
  535. } else {
  536. ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
  537. }
  538. return
  539. }
  540. perm, err := models.GetUserRepoPermission(repo, ctx.User)
  541. if err != nil {
  542. ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
  543. return
  544. } else if !perm.HasAccess() {
  545. ctx.NotFound()
  546. return
  547. }
  548. ctx.JSON(http.StatusOK, repo.APIFormat(perm.AccessMode))
  549. }
  550. // Edit edit repository properties
  551. func Edit(ctx *context.APIContext, opts api.EditRepoOption) {
  552. // swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit
  553. // ---
  554. // summary: Edit a repository's properties. Only fields that are set will be changed.
  555. // produces:
  556. // - application/json
  557. // parameters:
  558. // - name: owner
  559. // in: path
  560. // description: owner of the repo to edit
  561. // type: string
  562. // required: true
  563. // - name: repo
  564. // in: path
  565. // description: name of the repo to edit
  566. // type: string
  567. // required: true
  568. // required: true
  569. // - name: body
  570. // in: body
  571. // description: "Properties of a repo that you can edit"
  572. // schema:
  573. // "$ref": "#/definitions/EditRepoOption"
  574. // responses:
  575. // "200":
  576. // "$ref": "#/responses/Repository"
  577. // "403":
  578. // "$ref": "#/responses/forbidden"
  579. // "422":
  580. // "$ref": "#/responses/validationError"
  581. if err := updateBasicProperties(ctx, opts); err != nil {
  582. return
  583. }
  584. if err := updateRepoUnits(ctx, opts); err != nil {
  585. return
  586. }
  587. if opts.Archived != nil {
  588. if err := updateRepoArchivedState(ctx, opts); err != nil {
  589. return
  590. }
  591. }
  592. ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  593. }
  594. // updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility
  595. func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) error {
  596. owner := ctx.Repo.Owner
  597. repo := ctx.Repo.Repository
  598. newRepoName := repo.Name
  599. if opts.Name != nil {
  600. newRepoName = *opts.Name
  601. }
  602. // Check if repository name has been changed and not just a case change
  603. if repo.LowerName != strings.ToLower(newRepoName) {
  604. if err := repo_service.ChangeRepositoryName(ctx.User, repo, newRepoName); err != nil {
  605. switch {
  606. case models.IsErrRepoAlreadyExist(err):
  607. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err)
  608. case models.IsErrNameReserved(err):
  609. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err)
  610. case models.IsErrNamePatternNotAllowed(err):
  611. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(models.ErrNamePatternNotAllowed).Pattern), err)
  612. default:
  613. ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err)
  614. }
  615. return err
  616. }
  617. log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
  618. }
  619. // Update the name in the repo object for the response
  620. repo.Name = newRepoName
  621. repo.LowerName = strings.ToLower(newRepoName)
  622. if opts.Description != nil {
  623. repo.Description = *opts.Description
  624. }
  625. if opts.Website != nil {
  626. repo.Website = *opts.Website
  627. }
  628. visibilityChanged := false
  629. if opts.Private != nil {
  630. // Visibility of forked repository is forced sync with base repository.
  631. if repo.IsFork {
  632. *opts.Private = repo.BaseRepo.IsPrivate
  633. }
  634. visibilityChanged = repo.IsPrivate != *opts.Private
  635. // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
  636. if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.User.IsAdmin {
  637. err := fmt.Errorf("cannot change private repository to public")
  638. ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err)
  639. return err
  640. }
  641. repo.IsPrivate = *opts.Private
  642. }
  643. if opts.Template != nil {
  644. repo.IsTemplate = *opts.Template
  645. }
  646. // Default branch only updated if changed and exist
  647. if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch) {
  648. if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
  649. if !git.IsErrUnsupportedVersion(err) {
  650. ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
  651. return err
  652. }
  653. }
  654. repo.DefaultBranch = *opts.DefaultBranch
  655. }
  656. if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
  657. ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
  658. return err
  659. }
  660. log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
  661. return nil
  662. }
  663. // updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings
  664. func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
  665. owner := ctx.Repo.Owner
  666. repo := ctx.Repo.Repository
  667. var units []models.RepoUnit
  668. for _, tp := range models.MustRepoUnits {
  669. units = append(units, models.RepoUnit{
  670. RepoID: repo.ID,
  671. Type: tp,
  672. Config: new(models.UnitConfig),
  673. })
  674. }
  675. if opts.HasIssues == nil {
  676. // If HasIssues setting not touched, rewrite existing repo unit
  677. if unit, err := repo.GetUnit(models.UnitTypeIssues); err == nil {
  678. units = append(units, *unit)
  679. } else if unit, err := repo.GetUnit(models.UnitTypeExternalTracker); err == nil {
  680. units = append(units, *unit)
  681. }
  682. } else if *opts.HasIssues {
  683. if opts.ExternalTracker != nil {
  684. // Check that values are valid
  685. if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
  686. err := fmt.Errorf("External tracker URL not valid")
  687. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL", err)
  688. return err
  689. }
  690. if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
  691. err := fmt.Errorf("External tracker URL format not valid")
  692. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL format", err)
  693. return err
  694. }
  695. units = append(units, models.RepoUnit{
  696. RepoID: repo.ID,
  697. Type: models.UnitTypeExternalTracker,
  698. Config: &models.ExternalTrackerConfig{
  699. ExternalTrackerURL: opts.ExternalTracker.ExternalTrackerURL,
  700. ExternalTrackerFormat: opts.ExternalTracker.ExternalTrackerFormat,
  701. ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle,
  702. },
  703. })
  704. } else {
  705. // Default to built-in tracker
  706. var config *models.IssuesConfig
  707. if opts.InternalTracker != nil {
  708. config = &models.IssuesConfig{
  709. EnableTimetracker: opts.InternalTracker.EnableTimeTracker,
  710. AllowOnlyContributorsToTrackTime: opts.InternalTracker.AllowOnlyContributorsToTrackTime,
  711. EnableDependencies: opts.InternalTracker.EnableIssueDependencies,
  712. }
  713. } else if unit, err := repo.GetUnit(models.UnitTypeIssues); err != nil {
  714. // Unit type doesn't exist so we make a new config file with default values
  715. config = &models.IssuesConfig{
  716. EnableTimetracker: true,
  717. AllowOnlyContributorsToTrackTime: true,
  718. EnableDependencies: true,
  719. }
  720. } else {
  721. config = unit.IssuesConfig()
  722. }
  723. units = append(units, models.RepoUnit{
  724. RepoID: repo.ID,
  725. Type: models.UnitTypeIssues,
  726. Config: config,
  727. })
  728. }
  729. }
  730. if opts.HasWiki == nil {
  731. // If HasWiki setting not touched, rewrite existing repo unit
  732. if unit, err := repo.GetUnit(models.UnitTypeWiki); err == nil {
  733. units = append(units, *unit)
  734. } else if unit, err := repo.GetUnit(models.UnitTypeExternalWiki); err == nil {
  735. units = append(units, *unit)
  736. }
  737. } else if *opts.HasWiki {
  738. if opts.ExternalWiki != nil {
  739. // Check that values are valid
  740. if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
  741. err := fmt.Errorf("External wiki URL not valid")
  742. ctx.Error(http.StatusUnprocessableEntity, "", "Invalid external wiki URL")
  743. return err
  744. }
  745. units = append(units, models.RepoUnit{
  746. RepoID: repo.ID,
  747. Type: models.UnitTypeExternalWiki,
  748. Config: &models.ExternalWikiConfig{
  749. ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL,
  750. },
  751. })
  752. } else {
  753. config := &models.UnitConfig{}
  754. units = append(units, models.RepoUnit{
  755. RepoID: repo.ID,
  756. Type: models.UnitTypeWiki,
  757. Config: config,
  758. })
  759. }
  760. }
  761. if opts.HasPullRequests == nil {
  762. // If HasPullRequest setting not touched, rewrite existing repo unit
  763. if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil {
  764. units = append(units, *unit)
  765. }
  766. } else if *opts.HasPullRequests {
  767. // We do allow setting individual PR settings through the API, so
  768. // we get the config settings and then set them
  769. // if those settings were provided in the opts.
  770. unit, err := repo.GetUnit(models.UnitTypePullRequests)
  771. var config *models.PullRequestsConfig
  772. if err != nil {
  773. // Unit type doesn't exist so we make a new config file with default values
  774. config = &models.PullRequestsConfig{
  775. IgnoreWhitespaceConflicts: false,
  776. AllowMerge: true,
  777. AllowRebase: true,
  778. AllowRebaseMerge: true,
  779. AllowSquash: true,
  780. }
  781. } else {
  782. config = unit.PullRequestsConfig()
  783. }
  784. if opts.IgnoreWhitespaceConflicts != nil {
  785. config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
  786. }
  787. if opts.AllowMerge != nil {
  788. config.AllowMerge = *opts.AllowMerge
  789. }
  790. if opts.AllowRebase != nil {
  791. config.AllowRebase = *opts.AllowRebase
  792. }
  793. if opts.AllowRebaseMerge != nil {
  794. config.AllowRebaseMerge = *opts.AllowRebaseMerge
  795. }
  796. if opts.AllowSquash != nil {
  797. config.AllowSquash = *opts.AllowSquash
  798. }
  799. units = append(units, models.RepoUnit{
  800. RepoID: repo.ID,
  801. Type: models.UnitTypePullRequests,
  802. Config: config,
  803. })
  804. }
  805. if err := models.UpdateRepositoryUnits(repo, units); err != nil {
  806. ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
  807. return err
  808. }
  809. log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name)
  810. return nil
  811. }
  812. // updateRepoArchivedState updates repo's archive state
  813. func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) error {
  814. repo := ctx.Repo.Repository
  815. // archive / un-archive
  816. if opts.Archived != nil {
  817. if repo.IsMirror {
  818. err := fmt.Errorf("repo is a mirror, cannot archive/un-archive")
  819. ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
  820. return err
  821. }
  822. if *opts.Archived {
  823. if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
  824. log.Error("Tried to archive a repo: %s", err)
  825. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  826. return err
  827. }
  828. log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  829. } else {
  830. if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
  831. log.Error("Tried to un-archive a repo: %s", err)
  832. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  833. return err
  834. }
  835. log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  836. }
  837. }
  838. return nil
  839. }
  840. // Delete one repository
  841. func Delete(ctx *context.APIContext) {
  842. // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
  843. // ---
  844. // summary: Delete a repository
  845. // produces:
  846. // - application/json
  847. // parameters:
  848. // - name: owner
  849. // in: path
  850. // description: owner of the repo to delete
  851. // type: string
  852. // required: true
  853. // - name: repo
  854. // in: path
  855. // description: name of the repo to delete
  856. // type: string
  857. // required: true
  858. // responses:
  859. // "204":
  860. // "$ref": "#/responses/empty"
  861. // "403":
  862. // "$ref": "#/responses/forbidden"
  863. owner := ctx.Repo.Owner
  864. repo := ctx.Repo.Repository
  865. canDelete, err := repo.CanUserDelete(ctx.User)
  866. if err != nil {
  867. ctx.Error(http.StatusInternalServerError, "CanUserDelete", err)
  868. return
  869. } else if !canDelete {
  870. ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
  871. return
  872. }
  873. if err := repo_service.DeleteRepository(ctx.User, repo); err != nil {
  874. ctx.Error(http.StatusInternalServerError, "DeleteRepository", err)
  875. return
  876. }
  877. log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
  878. ctx.Status(http.StatusNoContent)
  879. }
  880. // MirrorSync adds a mirrored repository to the sync queue
  881. func MirrorSync(ctx *context.APIContext) {
  882. // swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
  883. // ---
  884. // summary: Sync a mirrored repository
  885. // produces:
  886. // - application/json
  887. // parameters:
  888. // - name: owner
  889. // in: path
  890. // description: owner of the repo to sync
  891. // type: string
  892. // required: true
  893. // - name: repo
  894. // in: path
  895. // description: name of the repo to sync
  896. // type: string
  897. // required: true
  898. // responses:
  899. // "200":
  900. // "$ref": "#/responses/empty"
  901. // "403":
  902. // "$ref": "#/responses/forbidden"
  903. repo := ctx.Repo.Repository
  904. if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  905. ctx.Error(http.StatusForbidden, "MirrorSync", "Must have write access")
  906. }
  907. mirror_service.StartToMirror(repo.ID)
  908. ctx.Status(http.StatusOK)
  909. }