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>
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
Restricted users (#6274) * Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
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>
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  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. Actor: ctx.User,
  124. Keyword: strings.Trim(ctx.Query("q"), " "),
  125. OwnerID: ctx.QueryInt64("uid"),
  126. PriorityOwnerID: ctx.QueryInt64("priority_owner_id"),
  127. Page: ctx.QueryInt("page"),
  128. PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
  129. TopicOnly: ctx.QueryBool("topic"),
  130. Collaborate: util.OptionalBoolNone,
  131. Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")),
  132. Template: util.OptionalBoolNone,
  133. StarredByID: ctx.QueryInt64("starredBy"),
  134. IncludeDescription: ctx.QueryBool("includeDesc"),
  135. }
  136. if ctx.Query("template") != "" {
  137. opts.Template = util.OptionalBoolOf(ctx.QueryBool("template"))
  138. }
  139. if ctx.QueryBool("exclusive") {
  140. opts.Collaborate = util.OptionalBoolFalse
  141. }
  142. var mode = ctx.Query("mode")
  143. switch mode {
  144. case "source":
  145. opts.Fork = util.OptionalBoolFalse
  146. opts.Mirror = util.OptionalBoolFalse
  147. case "fork":
  148. opts.Fork = util.OptionalBoolTrue
  149. case "mirror":
  150. opts.Mirror = util.OptionalBoolTrue
  151. case "collaborative":
  152. opts.Mirror = util.OptionalBoolFalse
  153. opts.Collaborate = util.OptionalBoolTrue
  154. case "":
  155. default:
  156. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
  157. return
  158. }
  159. var sortMode = ctx.Query("sort")
  160. if len(sortMode) > 0 {
  161. var sortOrder = ctx.Query("order")
  162. if len(sortOrder) == 0 {
  163. sortOrder = "asc"
  164. }
  165. if searchModeMap, ok := searchOrderByMap[sortOrder]; ok {
  166. if orderBy, ok := searchModeMap[sortMode]; ok {
  167. opts.OrderBy = orderBy
  168. } else {
  169. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
  170. return
  171. }
  172. } else {
  173. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
  174. return
  175. }
  176. }
  177. var err error
  178. repos, count, err := models.SearchRepository(opts)
  179. if err != nil {
  180. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  181. OK: false,
  182. Error: err.Error(),
  183. })
  184. return
  185. }
  186. results := make([]*api.Repository, len(repos))
  187. for i, repo := range repos {
  188. if err = repo.GetOwner(); err != nil {
  189. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  190. OK: false,
  191. Error: err.Error(),
  192. })
  193. return
  194. }
  195. accessMode, err := models.AccessLevel(ctx.User, repo)
  196. if err != nil {
  197. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  198. OK: false,
  199. Error: err.Error(),
  200. })
  201. }
  202. results[i] = repo.APIFormat(accessMode)
  203. }
  204. ctx.SetLinkHeader(int(count), setting.API.MaxResponseItems)
  205. ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count))
  206. ctx.JSON(http.StatusOK, api.SearchResults{
  207. OK: true,
  208. Data: results,
  209. })
  210. }
  211. // CreateUserRepo create a repository for a user
  212. func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateRepoOption) {
  213. if opt.AutoInit && opt.Readme == "" {
  214. opt.Readme = "Default"
  215. }
  216. repo, err := repo_service.CreateRepository(ctx.User, owner, models.CreateRepoOptions{
  217. Name: opt.Name,
  218. Description: opt.Description,
  219. IssueLabels: opt.IssueLabels,
  220. Gitignores: opt.Gitignores,
  221. License: opt.License,
  222. Readme: opt.Readme,
  223. IsPrivate: opt.Private,
  224. AutoInit: opt.AutoInit,
  225. })
  226. if err != nil {
  227. if models.IsErrRepoAlreadyExist(err) {
  228. ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
  229. } else if models.IsErrNameReserved(err) ||
  230. models.IsErrNamePatternNotAllowed(err) {
  231. ctx.Error(http.StatusUnprocessableEntity, "", err)
  232. } else {
  233. ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
  234. }
  235. return
  236. }
  237. ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeOwner))
  238. }
  239. // Create one repository of mine
  240. func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
  241. // swagger:operation POST /user/repos repository user createCurrentUserRepo
  242. // ---
  243. // summary: Create a repository
  244. // consumes:
  245. // - application/json
  246. // produces:
  247. // - application/json
  248. // parameters:
  249. // - name: body
  250. // in: body
  251. // schema:
  252. // "$ref": "#/definitions/CreateRepoOption"
  253. // responses:
  254. // "201":
  255. // "$ref": "#/responses/Repository"
  256. // "409":
  257. // description: The repository with the same name already exists.
  258. // "422":
  259. // "$ref": "#/responses/validationError"
  260. if ctx.User.IsOrganization() {
  261. // Shouldn't reach this condition, but just in case.
  262. ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
  263. return
  264. }
  265. CreateUserRepo(ctx, ctx.User, opt)
  266. }
  267. // CreateOrgRepoDeprecated create one repository of the organization
  268. func CreateOrgRepoDeprecated(ctx *context.APIContext, opt api.CreateRepoOption) {
  269. // swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
  270. // ---
  271. // summary: Create a repository in an organization
  272. // deprecated: true
  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. CreateOrgRepo(ctx, opt)
  295. }
  296. // CreateOrgRepo create one repository of the organization
  297. func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
  298. // swagger:operation POST /orgs/{org}/repos organization createOrgRepo
  299. // ---
  300. // summary: Create a repository in an organization
  301. // consumes:
  302. // - application/json
  303. // produces:
  304. // - application/json
  305. // parameters:
  306. // - name: org
  307. // in: path
  308. // description: name of organization
  309. // type: string
  310. // required: true
  311. // - name: body
  312. // in: body
  313. // schema:
  314. // "$ref": "#/definitions/CreateRepoOption"
  315. // responses:
  316. // "201":
  317. // "$ref": "#/responses/Repository"
  318. // "404":
  319. // "$ref": "#/responses/notFound"
  320. // "403":
  321. // "$ref": "#/responses/forbidden"
  322. org, err := models.GetOrgByName(ctx.Params(":org"))
  323. if err != nil {
  324. if models.IsErrOrgNotExist(err) {
  325. ctx.Error(http.StatusUnprocessableEntity, "", err)
  326. } else {
  327. ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
  328. }
  329. return
  330. }
  331. if !models.HasOrgVisible(org, ctx.User) {
  332. ctx.NotFound("HasOrgVisible", nil)
  333. return
  334. }
  335. if !ctx.User.IsAdmin {
  336. canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
  337. if err != nil {
  338. ctx.ServerError("CanCreateOrgRepo", err)
  339. return
  340. } else if !canCreate {
  341. ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
  342. return
  343. }
  344. }
  345. CreateUserRepo(ctx, org, opt)
  346. }
  347. // Migrate migrate remote git repository to gitea
  348. func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
  349. // swagger:operation POST /repos/migrate repository repoMigrate
  350. // ---
  351. // summary: Migrate a remote git repository
  352. // consumes:
  353. // - application/json
  354. // produces:
  355. // - application/json
  356. // parameters:
  357. // - name: body
  358. // in: body
  359. // schema:
  360. // "$ref": "#/definitions/MigrateRepoForm"
  361. // responses:
  362. // "201":
  363. // "$ref": "#/responses/Repository"
  364. // "403":
  365. // "$ref": "#/responses/forbidden"
  366. // "422":
  367. // "$ref": "#/responses/validationError"
  368. ctxUser := ctx.User
  369. // Not equal means context user is an organization,
  370. // or is another user/organization if current user is admin.
  371. if form.UID != ctxUser.ID {
  372. org, err := models.GetUserByID(form.UID)
  373. if err != nil {
  374. if models.IsErrUserNotExist(err) {
  375. ctx.Error(http.StatusUnprocessableEntity, "", err)
  376. } else {
  377. ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
  378. }
  379. return
  380. }
  381. ctxUser = org
  382. }
  383. if ctx.HasError() {
  384. ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
  385. return
  386. }
  387. if !ctx.User.IsAdmin {
  388. if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID {
  389. ctx.Error(http.StatusForbidden, "", "Given user is not an organization.")
  390. return
  391. }
  392. if ctxUser.IsOrganization() {
  393. // Check ownership of organization.
  394. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  395. if err != nil {
  396. ctx.Error(http.StatusInternalServerError, "IsOwnedBy", err)
  397. return
  398. } else if !isOwner {
  399. ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
  400. return
  401. }
  402. }
  403. }
  404. remoteAddr, err := form.ParseRemoteAddr(ctx.User)
  405. if err != nil {
  406. if models.IsErrInvalidCloneAddr(err) {
  407. addrErr := err.(models.ErrInvalidCloneAddr)
  408. switch {
  409. case addrErr.IsURLError:
  410. ctx.Error(http.StatusUnprocessableEntity, "", err)
  411. case addrErr.IsPermissionDenied:
  412. ctx.Error(http.StatusUnprocessableEntity, "", "You are not allowed to import local repositories.")
  413. case addrErr.IsInvalidPath:
  414. ctx.Error(http.StatusUnprocessableEntity, "", "Invalid local path, it does not exist or not a directory.")
  415. default:
  416. ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
  417. }
  418. } else {
  419. ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", err)
  420. }
  421. return
  422. }
  423. var gitServiceType = api.PlainGitService
  424. u, err := url.Parse(remoteAddr)
  425. if err == nil && strings.EqualFold(u.Host, "github.com") {
  426. gitServiceType = api.GithubService
  427. }
  428. var opts = migrations.MigrateOptions{
  429. CloneAddr: remoteAddr,
  430. RepoName: form.RepoName,
  431. Description: form.Description,
  432. Private: form.Private || setting.Repository.ForcePrivate,
  433. Mirror: form.Mirror,
  434. AuthUsername: form.AuthUsername,
  435. AuthPassword: form.AuthPassword,
  436. Wiki: form.Wiki,
  437. Issues: form.Issues,
  438. Milestones: form.Milestones,
  439. Labels: form.Labels,
  440. Comments: true,
  441. PullRequests: form.PullRequests,
  442. Releases: form.Releases,
  443. GitServiceType: gitServiceType,
  444. }
  445. if opts.Mirror {
  446. opts.Issues = false
  447. opts.Milestones = false
  448. opts.Labels = false
  449. opts.Comments = false
  450. opts.PullRequests = false
  451. opts.Releases = false
  452. }
  453. repo, err := repo_module.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
  454. Name: opts.RepoName,
  455. Description: opts.Description,
  456. OriginalURL: form.CloneAddr,
  457. GitServiceType: gitServiceType,
  458. IsPrivate: opts.Private,
  459. IsMirror: opts.Mirror,
  460. Status: models.RepositoryBeingMigrated,
  461. })
  462. if err != nil {
  463. handleMigrateError(ctx, ctxUser, remoteAddr, err)
  464. return
  465. }
  466. opts.MigrateToRepoID = repo.ID
  467. defer func() {
  468. if e := recover(); e != nil {
  469. var buf bytes.Buffer
  470. fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
  471. err = errors.New(buf.String())
  472. }
  473. if err == nil {
  474. repo.Status = models.RepositoryReady
  475. if err := models.UpdateRepositoryCols(repo, "status"); err == nil {
  476. notification.NotifyMigrateRepository(ctx.User, ctxUser, repo)
  477. return
  478. }
  479. }
  480. if repo != nil {
  481. if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
  482. log.Error("DeleteRepository: %v", errDelete)
  483. }
  484. }
  485. }()
  486. if _, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.User, ctxUser.Name, opts); err != nil {
  487. handleMigrateError(ctx, ctxUser, remoteAddr, err)
  488. return
  489. }
  490. log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
  491. ctx.JSON(http.StatusCreated, repo.APIFormat(models.AccessModeAdmin))
  492. }
  493. func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) {
  494. switch {
  495. case models.IsErrRepoAlreadyExist(err):
  496. ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
  497. case migrations.IsRateLimitError(err):
  498. ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.")
  499. case migrations.IsTwoFactorAuthError(err):
  500. ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit required two factors authentication.")
  501. case models.IsErrReachLimitOfRepo(err):
  502. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
  503. case models.IsErrNameReserved(err):
  504. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
  505. case models.IsErrNamePatternNotAllowed(err):
  506. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
  507. default:
  508. err = util.URLSanitizedError(err, remoteAddr)
  509. if strings.Contains(err.Error(), "Authentication failed") ||
  510. strings.Contains(err.Error(), "Bad credentials") ||
  511. strings.Contains(err.Error(), "could not read Username") {
  512. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Authentication failed: %v.", err))
  513. } else if strings.Contains(err.Error(), "fatal:") {
  514. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Migration failed: %v.", err))
  515. } else {
  516. ctx.Error(http.StatusInternalServerError, "MigrateRepository", err)
  517. }
  518. }
  519. }
  520. // Get one repository
  521. func Get(ctx *context.APIContext) {
  522. // swagger:operation GET /repos/{owner}/{repo} repository repoGet
  523. // ---
  524. // summary: Get a repository
  525. // produces:
  526. // - application/json
  527. // parameters:
  528. // - name: owner
  529. // in: path
  530. // description: owner of the repo
  531. // type: string
  532. // required: true
  533. // - name: repo
  534. // in: path
  535. // description: name of the repo
  536. // type: string
  537. // required: true
  538. // responses:
  539. // "200":
  540. // "$ref": "#/responses/Repository"
  541. ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  542. }
  543. // GetByID returns a single Repository
  544. func GetByID(ctx *context.APIContext) {
  545. // swagger:operation GET /repositories/{id} repository repoGetByID
  546. // ---
  547. // summary: Get a repository by id
  548. // produces:
  549. // - application/json
  550. // parameters:
  551. // - name: id
  552. // in: path
  553. // description: id of the repo to get
  554. // type: integer
  555. // format: int64
  556. // required: true
  557. // responses:
  558. // "200":
  559. // "$ref": "#/responses/Repository"
  560. repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
  561. if err != nil {
  562. if models.IsErrRepoNotExist(err) {
  563. ctx.NotFound()
  564. } else {
  565. ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
  566. }
  567. return
  568. }
  569. perm, err := models.GetUserRepoPermission(repo, ctx.User)
  570. if err != nil {
  571. ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
  572. return
  573. } else if !perm.HasAccess() {
  574. ctx.NotFound()
  575. return
  576. }
  577. ctx.JSON(http.StatusOK, repo.APIFormat(perm.AccessMode))
  578. }
  579. // Edit edit repository properties
  580. func Edit(ctx *context.APIContext, opts api.EditRepoOption) {
  581. // swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit
  582. // ---
  583. // summary: Edit a repository's properties. Only fields that are set will be changed.
  584. // produces:
  585. // - application/json
  586. // parameters:
  587. // - name: owner
  588. // in: path
  589. // description: owner of the repo to edit
  590. // type: string
  591. // required: true
  592. // - name: repo
  593. // in: path
  594. // description: name of the repo to edit
  595. // type: string
  596. // required: true
  597. // required: true
  598. // - name: body
  599. // in: body
  600. // description: "Properties of a repo that you can edit"
  601. // schema:
  602. // "$ref": "#/definitions/EditRepoOption"
  603. // responses:
  604. // "200":
  605. // "$ref": "#/responses/Repository"
  606. // "403":
  607. // "$ref": "#/responses/forbidden"
  608. // "422":
  609. // "$ref": "#/responses/validationError"
  610. if err := updateBasicProperties(ctx, opts); err != nil {
  611. return
  612. }
  613. if err := updateRepoUnits(ctx, opts); err != nil {
  614. return
  615. }
  616. if opts.Archived != nil {
  617. if err := updateRepoArchivedState(ctx, opts); err != nil {
  618. return
  619. }
  620. }
  621. ctx.JSON(http.StatusOK, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  622. }
  623. // updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility
  624. func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) error {
  625. owner := ctx.Repo.Owner
  626. repo := ctx.Repo.Repository
  627. newRepoName := repo.Name
  628. if opts.Name != nil {
  629. newRepoName = *opts.Name
  630. }
  631. // Check if repository name has been changed and not just a case change
  632. if repo.LowerName != strings.ToLower(newRepoName) {
  633. if err := repo_service.ChangeRepositoryName(ctx.User, repo, newRepoName); err != nil {
  634. switch {
  635. case models.IsErrRepoAlreadyExist(err):
  636. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err)
  637. case models.IsErrNameReserved(err):
  638. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err)
  639. case models.IsErrNamePatternNotAllowed(err):
  640. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(models.ErrNamePatternNotAllowed).Pattern), err)
  641. default:
  642. ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err)
  643. }
  644. return err
  645. }
  646. log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
  647. }
  648. // Update the name in the repo object for the response
  649. repo.Name = newRepoName
  650. repo.LowerName = strings.ToLower(newRepoName)
  651. if opts.Description != nil {
  652. repo.Description = *opts.Description
  653. }
  654. if opts.Website != nil {
  655. repo.Website = *opts.Website
  656. }
  657. visibilityChanged := false
  658. if opts.Private != nil {
  659. // Visibility of forked repository is forced sync with base repository.
  660. if repo.IsFork {
  661. *opts.Private = repo.BaseRepo.IsPrivate
  662. }
  663. visibilityChanged = repo.IsPrivate != *opts.Private
  664. // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
  665. if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.User.IsAdmin {
  666. err := fmt.Errorf("cannot change private repository to public")
  667. ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err)
  668. return err
  669. }
  670. repo.IsPrivate = *opts.Private
  671. }
  672. if opts.Template != nil {
  673. repo.IsTemplate = *opts.Template
  674. }
  675. // Default branch only updated if changed and exist
  676. if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch) {
  677. if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
  678. if !git.IsErrUnsupportedVersion(err) {
  679. ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
  680. return err
  681. }
  682. }
  683. repo.DefaultBranch = *opts.DefaultBranch
  684. }
  685. if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
  686. ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
  687. return err
  688. }
  689. log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
  690. return nil
  691. }
  692. // updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings
  693. func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
  694. owner := ctx.Repo.Owner
  695. repo := ctx.Repo.Repository
  696. var units []models.RepoUnit
  697. for _, tp := range models.MustRepoUnits {
  698. units = append(units, models.RepoUnit{
  699. RepoID: repo.ID,
  700. Type: tp,
  701. Config: new(models.UnitConfig),
  702. })
  703. }
  704. if opts.HasIssues == nil {
  705. // If HasIssues setting not touched, rewrite existing repo unit
  706. if unit, err := repo.GetUnit(models.UnitTypeIssues); err == nil {
  707. units = append(units, *unit)
  708. } else if unit, err := repo.GetUnit(models.UnitTypeExternalTracker); err == nil {
  709. units = append(units, *unit)
  710. }
  711. } else if *opts.HasIssues {
  712. if opts.ExternalTracker != nil {
  713. // Check that values are valid
  714. if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
  715. err := fmt.Errorf("External tracker URL not valid")
  716. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL", err)
  717. return err
  718. }
  719. if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
  720. err := fmt.Errorf("External tracker URL format not valid")
  721. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL format", err)
  722. return err
  723. }
  724. units = append(units, models.RepoUnit{
  725. RepoID: repo.ID,
  726. Type: models.UnitTypeExternalTracker,
  727. Config: &models.ExternalTrackerConfig{
  728. ExternalTrackerURL: opts.ExternalTracker.ExternalTrackerURL,
  729. ExternalTrackerFormat: opts.ExternalTracker.ExternalTrackerFormat,
  730. ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle,
  731. },
  732. })
  733. } else {
  734. // Default to built-in tracker
  735. var config *models.IssuesConfig
  736. if opts.InternalTracker != nil {
  737. config = &models.IssuesConfig{
  738. EnableTimetracker: opts.InternalTracker.EnableTimeTracker,
  739. AllowOnlyContributorsToTrackTime: opts.InternalTracker.AllowOnlyContributorsToTrackTime,
  740. EnableDependencies: opts.InternalTracker.EnableIssueDependencies,
  741. }
  742. } else if unit, err := repo.GetUnit(models.UnitTypeIssues); err != nil {
  743. // Unit type doesn't exist so we make a new config file with default values
  744. config = &models.IssuesConfig{
  745. EnableTimetracker: true,
  746. AllowOnlyContributorsToTrackTime: true,
  747. EnableDependencies: true,
  748. }
  749. } else {
  750. config = unit.IssuesConfig()
  751. }
  752. units = append(units, models.RepoUnit{
  753. RepoID: repo.ID,
  754. Type: models.UnitTypeIssues,
  755. Config: config,
  756. })
  757. }
  758. }
  759. if opts.HasWiki == nil {
  760. // If HasWiki setting not touched, rewrite existing repo unit
  761. if unit, err := repo.GetUnit(models.UnitTypeWiki); err == nil {
  762. units = append(units, *unit)
  763. } else if unit, err := repo.GetUnit(models.UnitTypeExternalWiki); err == nil {
  764. units = append(units, *unit)
  765. }
  766. } else if *opts.HasWiki {
  767. if opts.ExternalWiki != nil {
  768. // Check that values are valid
  769. if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
  770. err := fmt.Errorf("External wiki URL not valid")
  771. ctx.Error(http.StatusUnprocessableEntity, "", "Invalid external wiki URL")
  772. return err
  773. }
  774. units = append(units, models.RepoUnit{
  775. RepoID: repo.ID,
  776. Type: models.UnitTypeExternalWiki,
  777. Config: &models.ExternalWikiConfig{
  778. ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL,
  779. },
  780. })
  781. } else {
  782. config := &models.UnitConfig{}
  783. units = append(units, models.RepoUnit{
  784. RepoID: repo.ID,
  785. Type: models.UnitTypeWiki,
  786. Config: config,
  787. })
  788. }
  789. }
  790. if opts.HasPullRequests == nil {
  791. // If HasPullRequest setting not touched, rewrite existing repo unit
  792. if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil {
  793. units = append(units, *unit)
  794. }
  795. } else if *opts.HasPullRequests {
  796. // We do allow setting individual PR settings through the API, so
  797. // we get the config settings and then set them
  798. // if those settings were provided in the opts.
  799. unit, err := repo.GetUnit(models.UnitTypePullRequests)
  800. var config *models.PullRequestsConfig
  801. if err != nil {
  802. // Unit type doesn't exist so we make a new config file with default values
  803. config = &models.PullRequestsConfig{
  804. IgnoreWhitespaceConflicts: false,
  805. AllowMerge: true,
  806. AllowRebase: true,
  807. AllowRebaseMerge: true,
  808. AllowSquash: true,
  809. }
  810. } else {
  811. config = unit.PullRequestsConfig()
  812. }
  813. if opts.IgnoreWhitespaceConflicts != nil {
  814. config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
  815. }
  816. if opts.AllowMerge != nil {
  817. config.AllowMerge = *opts.AllowMerge
  818. }
  819. if opts.AllowRebase != nil {
  820. config.AllowRebase = *opts.AllowRebase
  821. }
  822. if opts.AllowRebaseMerge != nil {
  823. config.AllowRebaseMerge = *opts.AllowRebaseMerge
  824. }
  825. if opts.AllowSquash != nil {
  826. config.AllowSquash = *opts.AllowSquash
  827. }
  828. units = append(units, models.RepoUnit{
  829. RepoID: repo.ID,
  830. Type: models.UnitTypePullRequests,
  831. Config: config,
  832. })
  833. }
  834. if err := models.UpdateRepositoryUnits(repo, units); err != nil {
  835. ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
  836. return err
  837. }
  838. log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name)
  839. return nil
  840. }
  841. // updateRepoArchivedState updates repo's archive state
  842. func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) error {
  843. repo := ctx.Repo.Repository
  844. // archive / un-archive
  845. if opts.Archived != nil {
  846. if repo.IsMirror {
  847. err := fmt.Errorf("repo is a mirror, cannot archive/un-archive")
  848. ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
  849. return err
  850. }
  851. if *opts.Archived {
  852. if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
  853. log.Error("Tried to archive a repo: %s", err)
  854. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  855. return err
  856. }
  857. log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  858. } else {
  859. if err := repo.SetArchiveRepoState(*opts.Archived); err != nil {
  860. log.Error("Tried to un-archive a repo: %s", err)
  861. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  862. return err
  863. }
  864. log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  865. }
  866. }
  867. return nil
  868. }
  869. // Delete one repository
  870. func Delete(ctx *context.APIContext) {
  871. // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
  872. // ---
  873. // summary: Delete a repository
  874. // produces:
  875. // - application/json
  876. // parameters:
  877. // - name: owner
  878. // in: path
  879. // description: owner of the repo to delete
  880. // type: string
  881. // required: true
  882. // - name: repo
  883. // in: path
  884. // description: name of the repo to delete
  885. // type: string
  886. // required: true
  887. // responses:
  888. // "204":
  889. // "$ref": "#/responses/empty"
  890. // "403":
  891. // "$ref": "#/responses/forbidden"
  892. owner := ctx.Repo.Owner
  893. repo := ctx.Repo.Repository
  894. canDelete, err := repo.CanUserDelete(ctx.User)
  895. if err != nil {
  896. ctx.Error(http.StatusInternalServerError, "CanUserDelete", err)
  897. return
  898. } else if !canDelete {
  899. ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
  900. return
  901. }
  902. if err := repo_service.DeleteRepository(ctx.User, repo); err != nil {
  903. ctx.Error(http.StatusInternalServerError, "DeleteRepository", err)
  904. return
  905. }
  906. log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
  907. ctx.Status(http.StatusNoContent)
  908. }
  909. // MirrorSync adds a mirrored repository to the sync queue
  910. func MirrorSync(ctx *context.APIContext) {
  911. // swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
  912. // ---
  913. // summary: Sync a mirrored repository
  914. // produces:
  915. // - application/json
  916. // parameters:
  917. // - name: owner
  918. // in: path
  919. // description: owner of the repo to sync
  920. // type: string
  921. // required: true
  922. // - name: repo
  923. // in: path
  924. // description: name of the repo to sync
  925. // type: string
  926. // required: true
  927. // responses:
  928. // "200":
  929. // "$ref": "#/responses/empty"
  930. // "403":
  931. // "$ref": "#/responses/forbidden"
  932. repo := ctx.Repo.Repository
  933. if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  934. ctx.Error(http.StatusForbidden, "MirrorSync", "Must have write access")
  935. }
  936. mirror_service.StartToMirror(repo.ID)
  937. ctx.Status(http.StatusOK)
  938. }