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