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 17 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
Better logging (#6038) (#6095) * Panic don't fatal on create new logger Fixes #5854 Signed-off-by: Andrew Thornton <art27@cantab.net> * partial broken * Update the logging infrastrcture Signed-off-by: Andrew Thornton <art27@cantab.net> * Reset the skip levels for Fatal and Error Signed-off-by: Andrew Thornton <art27@cantab.net> * broken ncsa * More log.Error fixes Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove nal * set log-levels to lowercase * Make console_test test all levels * switch to lowercased levels * OK now working * Fix vetting issues * Fix lint * Fix tests * change default logging to match current gitea * Improve log testing Signed-off-by: Andrew Thornton <art27@cantab.net> * reset error skip levels to 0 * Update documentation and access logger configuration * Redirect the router log back to gitea if redirect macaron log but also allow setting the log level - i.e. TRACE * Fix broken level caching * Refactor the router log * Add Router logger * Add colorizing options * Adjust router colors * Only create logger if they will be used * update app.ini.sample * rename Attribute ColorAttribute * Change from white to green for function * Set fatal/error levels * Restore initial trace logger * Fix Trace arguments in modules/auth/auth.go * Properly handle XORMLogger * Improve admin/config page * fix fmt * Add auto-compression of old logs * Update error log levels * Remove the unnecessary skip argument from Error, Fatal and Critical * Add stacktrace support * Fix tests * Remove x/sync from vendors? * Add stderr option to console logger * Use filepath.ToSlash to protect against Windows in tests * Remove prefixed underscores from names in colors.go * Remove not implemented database logger This was removed from Gogs on 4 Mar 2016 but left in the configuration since then. * Ensure that log paths are relative to ROOT_PATH * use path.Join * rename jsonConfig to logConfig * Rename "config" to "jsonConfig" to make it clearer * Requested changes * Requested changes: XormLogger * Try to color the windows terminal If successful default to colorizing the console logs * fixup * Colorize initially too * update vendor * Colorize logs on default and remove if this is not a colorizing logger * Fix documentation * fix test * Use go-isatty to detect if on windows we are on msys or cygwin * Fix spelling mistake * Add missing vendors * More changes * Rationalise the ANSI writer protection * Adjust colors on advice from @0x5c * Make Flags a comma separated list * Move to use the windows constant for ENABLE_VIRTUAL_TERMINAL_PROCESSING * Ensure matching is done on the non-colored message - to simpify EXPRESSION
6 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
10 years ago
Better logging (#6038) (#6095) * Panic don't fatal on create new logger Fixes #5854 Signed-off-by: Andrew Thornton <art27@cantab.net> * partial broken * Update the logging infrastrcture Signed-off-by: Andrew Thornton <art27@cantab.net> * Reset the skip levels for Fatal and Error Signed-off-by: Andrew Thornton <art27@cantab.net> * broken ncsa * More log.Error fixes Signed-off-by: Andrew Thornton <art27@cantab.net> * Remove nal * set log-levels to lowercase * Make console_test test all levels * switch to lowercased levels * OK now working * Fix vetting issues * Fix lint * Fix tests * change default logging to match current gitea * Improve log testing Signed-off-by: Andrew Thornton <art27@cantab.net> * reset error skip levels to 0 * Update documentation and access logger configuration * Redirect the router log back to gitea if redirect macaron log but also allow setting the log level - i.e. TRACE * Fix broken level caching * Refactor the router log * Add Router logger * Add colorizing options * Adjust router colors * Only create logger if they will be used * update app.ini.sample * rename Attribute ColorAttribute * Change from white to green for function * Set fatal/error levels * Restore initial trace logger * Fix Trace arguments in modules/auth/auth.go * Properly handle XORMLogger * Improve admin/config page * fix fmt * Add auto-compression of old logs * Update error log levels * Remove the unnecessary skip argument from Error, Fatal and Critical * Add stacktrace support * Fix tests * Remove x/sync from vendors? * Add stderr option to console logger * Use filepath.ToSlash to protect against Windows in tests * Remove prefixed underscores from names in colors.go * Remove not implemented database logger This was removed from Gogs on 4 Mar 2016 but left in the configuration since then. * Ensure that log paths are relative to ROOT_PATH * use path.Join * rename jsonConfig to logConfig * Rename "config" to "jsonConfig" to make it clearer * Requested changes * Requested changes: XormLogger * Try to color the windows terminal If successful default to colorizing the console logs * fixup * Colorize initially too * update vendor * Colorize logs on default and remove if this is not a colorizing logger * Fix documentation * fix test * Use go-isatty to detect if on windows we are on msys or cygwin * Fix spelling mistake * Add missing vendors * More changes * Rationalise the ANSI writer protection * Adjust colors on advice from @0x5c * Make Flags a comma separated list * Move to use the windows constant for ENABLE_VIRTUAL_TERMINAL_PROCESSING * Ensure matching is done on the non-colored message - to simpify EXPRESSION
6 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  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. "fmt"
  8. "net/http"
  9. "strings"
  10. "code.gitea.io/gitea/models"
  11. "code.gitea.io/gitea/modules/auth"
  12. "code.gitea.io/gitea/modules/context"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/migrations"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/util"
  17. "code.gitea.io/gitea/routers/api/v1/convert"
  18. api "code.gitea.io/sdk/gitea"
  19. )
  20. var searchOrderByMap = map[string]map[string]models.SearchOrderBy{
  21. "asc": {
  22. "alpha": models.SearchOrderByAlphabetically,
  23. "created": models.SearchOrderByOldest,
  24. "updated": models.SearchOrderByLeastUpdated,
  25. "size": models.SearchOrderBySize,
  26. "id": models.SearchOrderByID,
  27. },
  28. "desc": {
  29. "alpha": models.SearchOrderByAlphabeticallyReverse,
  30. "created": models.SearchOrderByNewest,
  31. "updated": models.SearchOrderByRecentUpdated,
  32. "size": models.SearchOrderBySizeReverse,
  33. "id": models.SearchOrderByIDReverse,
  34. },
  35. }
  36. // Search repositories via options
  37. func Search(ctx *context.APIContext) {
  38. // swagger:operation GET /repos/search repository repoSearch
  39. // ---
  40. // summary: Search for repositories
  41. // produces:
  42. // - application/json
  43. // parameters:
  44. // - name: q
  45. // in: query
  46. // description: keyword
  47. // type: string
  48. // - name: uid
  49. // in: query
  50. // description: search only for repos that the user with the given id owns or contributes to
  51. // type: integer
  52. // format: int64
  53. // - name: page
  54. // in: query
  55. // description: page number of results to return (1-based)
  56. // type: integer
  57. // - name: limit
  58. // in: query
  59. // description: page size of results, maximum page size is 50
  60. // type: integer
  61. // - name: mode
  62. // in: query
  63. // description: type of repository to search for. Supported values are
  64. // "fork", "source", "mirror" and "collaborative"
  65. // type: string
  66. // - name: exclusive
  67. // in: query
  68. // description: if `uid` is given, search only for repos that the user owns
  69. // type: boolean
  70. // - name: sort
  71. // in: query
  72. // description: sort repos by attribute. Supported values are
  73. // "alpha", "created", "updated", "size", and "id".
  74. // Default is "alpha"
  75. // type: string
  76. // - name: order
  77. // in: query
  78. // description: sort order, either "asc" (ascending) or "desc" (descending).
  79. // Default is "asc", ignored if "sort" is not specified.
  80. // type: string
  81. // responses:
  82. // "200":
  83. // "$ref": "#/responses/SearchResults"
  84. // "422":
  85. // "$ref": "#/responses/validationError"
  86. opts := &models.SearchRepoOptions{
  87. Keyword: strings.Trim(ctx.Query("q"), " "),
  88. OwnerID: ctx.QueryInt64("uid"),
  89. Page: ctx.QueryInt("page"),
  90. PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
  91. TopicOnly: ctx.QueryBool("topic"),
  92. Collaborate: util.OptionalBoolNone,
  93. }
  94. if ctx.QueryBool("exclusive") {
  95. opts.Collaborate = util.OptionalBoolFalse
  96. }
  97. var mode = ctx.Query("mode")
  98. switch mode {
  99. case "source":
  100. opts.Fork = util.OptionalBoolFalse
  101. opts.Mirror = util.OptionalBoolFalse
  102. case "fork":
  103. opts.Fork = util.OptionalBoolTrue
  104. case "mirror":
  105. opts.Mirror = util.OptionalBoolTrue
  106. case "collaborative":
  107. opts.Mirror = util.OptionalBoolFalse
  108. opts.Collaborate = util.OptionalBoolTrue
  109. case "":
  110. default:
  111. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
  112. return
  113. }
  114. var sortMode = ctx.Query("sort")
  115. if len(sortMode) > 0 {
  116. var sortOrder = ctx.Query("order")
  117. if len(sortOrder) == 0 {
  118. sortOrder = "asc"
  119. }
  120. if searchModeMap, ok := searchOrderByMap[sortOrder]; ok {
  121. if orderBy, ok := searchModeMap[sortMode]; ok {
  122. opts.OrderBy = orderBy
  123. } else {
  124. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
  125. return
  126. }
  127. } else {
  128. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
  129. return
  130. }
  131. }
  132. var err error
  133. if opts.OwnerID > 0 {
  134. var repoOwner *models.User
  135. if ctx.User != nil && ctx.User.ID == opts.OwnerID {
  136. repoOwner = ctx.User
  137. } else {
  138. repoOwner, err = models.GetUserByID(opts.OwnerID)
  139. if err != nil {
  140. ctx.JSON(500, api.SearchError{
  141. OK: false,
  142. Error: err.Error(),
  143. })
  144. return
  145. }
  146. }
  147. if repoOwner.IsOrganization() {
  148. opts.Collaborate = util.OptionalBoolFalse
  149. }
  150. // Check visibility.
  151. if ctx.IsSigned {
  152. if ctx.User.ID == repoOwner.ID {
  153. opts.Private = true
  154. } else if repoOwner.IsOrganization() {
  155. opts.Private, err = repoOwner.IsOwnedBy(ctx.User.ID)
  156. if err != nil {
  157. ctx.JSON(500, api.SearchError{
  158. OK: false,
  159. Error: err.Error(),
  160. })
  161. return
  162. }
  163. }
  164. }
  165. }
  166. repos, count, err := models.SearchRepositoryByName(opts)
  167. if err != nil {
  168. ctx.JSON(500, api.SearchError{
  169. OK: false,
  170. Error: err.Error(),
  171. })
  172. return
  173. }
  174. results := make([]*api.Repository, len(repos))
  175. for i, repo := range repos {
  176. if err = repo.GetOwner(); err != nil {
  177. ctx.JSON(500, api.SearchError{
  178. OK: false,
  179. Error: err.Error(),
  180. })
  181. return
  182. }
  183. accessMode, err := models.AccessLevel(ctx.User, repo)
  184. if err != nil {
  185. ctx.JSON(500, api.SearchError{
  186. OK: false,
  187. Error: err.Error(),
  188. })
  189. }
  190. results[i] = repo.APIFormat(accessMode)
  191. }
  192. ctx.SetLinkHeader(int(count), setting.API.MaxResponseItems)
  193. ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count))
  194. ctx.JSON(200, api.SearchResults{
  195. OK: true,
  196. Data: results,
  197. })
  198. }
  199. // CreateUserRepo create a repository for a user
  200. func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateRepoOption) {
  201. if opt.AutoInit && opt.Readme == "" {
  202. opt.Readme = "Default"
  203. }
  204. repo, err := models.CreateRepository(ctx.User, owner, models.CreateRepoOptions{
  205. Name: opt.Name,
  206. Description: opt.Description,
  207. Gitignores: opt.Gitignores,
  208. License: opt.License,
  209. Readme: opt.Readme,
  210. IsPrivate: opt.Private,
  211. AutoInit: opt.AutoInit,
  212. })
  213. if err != nil {
  214. if models.IsErrRepoAlreadyExist(err) {
  215. ctx.Error(409, "", "The repository with the same name already exists.")
  216. } else if models.IsErrNameReserved(err) ||
  217. models.IsErrNamePatternNotAllowed(err) {
  218. ctx.Error(422, "", err)
  219. } else {
  220. if repo != nil {
  221. if err = models.DeleteRepository(ctx.User, ctx.User.ID, repo.ID); err != nil {
  222. log.Error("DeleteRepository: %v", err)
  223. }
  224. }
  225. ctx.Error(500, "CreateRepository", err)
  226. }
  227. return
  228. }
  229. ctx.JSON(201, repo.APIFormat(models.AccessModeOwner))
  230. }
  231. // Create one repository of mine
  232. func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
  233. // swagger:operation POST /user/repos repository user createCurrentUserRepo
  234. // ---
  235. // summary: Create a repository
  236. // consumes:
  237. // - application/json
  238. // produces:
  239. // - application/json
  240. // parameters:
  241. // - name: body
  242. // in: body
  243. // schema:
  244. // "$ref": "#/definitions/CreateRepoOption"
  245. // responses:
  246. // "201":
  247. // "$ref": "#/responses/Repository"
  248. if ctx.User.IsOrganization() {
  249. // Shouldn't reach this condition, but just in case.
  250. ctx.Error(422, "", "not allowed creating repository for organization")
  251. return
  252. }
  253. CreateUserRepo(ctx, ctx.User, opt)
  254. }
  255. // CreateOrgRepo create one repository of the organization
  256. func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
  257. // swagger:operation POST /org/{org}/repos organization createOrgRepo
  258. // ---
  259. // summary: Create a repository in an organization
  260. // consumes:
  261. // - application/json
  262. // produces:
  263. // - application/json
  264. // parameters:
  265. // - name: org
  266. // in: path
  267. // description: name of organization
  268. // type: string
  269. // required: true
  270. // - name: body
  271. // in: body
  272. // schema:
  273. // "$ref": "#/definitions/CreateRepoOption"
  274. // responses:
  275. // "201":
  276. // "$ref": "#/responses/Repository"
  277. // "422":
  278. // "$ref": "#/responses/validationError"
  279. // "403":
  280. // "$ref": "#/responses/forbidden"
  281. org, err := models.GetOrgByName(ctx.Params(":org"))
  282. if err != nil {
  283. if models.IsErrOrgNotExist(err) {
  284. ctx.Error(422, "", err)
  285. } else {
  286. ctx.Error(500, "GetOrgByName", err)
  287. }
  288. return
  289. }
  290. if !models.HasOrgVisible(org, ctx.User) {
  291. ctx.NotFound("HasOrgVisible", nil)
  292. return
  293. }
  294. if !ctx.User.IsAdmin {
  295. isOwner, err := org.IsOwnedBy(ctx.User.ID)
  296. if err != nil {
  297. ctx.ServerError("IsOwnedBy", err)
  298. return
  299. } else if !isOwner {
  300. ctx.Error(403, "", "Given user is not owner of organization.")
  301. return
  302. }
  303. }
  304. CreateUserRepo(ctx, org, opt)
  305. }
  306. // Migrate migrate remote git repository to gitea
  307. func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
  308. // swagger:operation POST /repos/migrate repository repoMigrate
  309. // ---
  310. // summary: Migrate a remote git repository
  311. // consumes:
  312. // - application/json
  313. // produces:
  314. // - application/json
  315. // parameters:
  316. // - name: body
  317. // in: body
  318. // schema:
  319. // "$ref": "#/definitions/MigrateRepoForm"
  320. // responses:
  321. // "201":
  322. // "$ref": "#/responses/Repository"
  323. ctxUser := ctx.User
  324. // Not equal means context user is an organization,
  325. // or is another user/organization if current user is admin.
  326. if form.UID != ctxUser.ID {
  327. org, err := models.GetUserByID(form.UID)
  328. if err != nil {
  329. if models.IsErrUserNotExist(err) {
  330. ctx.Error(422, "", err)
  331. } else {
  332. ctx.Error(500, "GetUserByID", err)
  333. }
  334. return
  335. }
  336. ctxUser = org
  337. }
  338. if ctx.HasError() {
  339. ctx.Error(422, "", ctx.GetErrMsg())
  340. return
  341. }
  342. if !ctx.User.IsAdmin {
  343. if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID {
  344. ctx.Error(403, "", "Given user is not an organization.")
  345. return
  346. }
  347. if ctxUser.IsOrganization() {
  348. // Check ownership of organization.
  349. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  350. if err != nil {
  351. ctx.Error(500, "IsOwnedBy", err)
  352. return
  353. } else if !isOwner {
  354. ctx.Error(403, "", "Given user is not owner of organization.")
  355. return
  356. }
  357. }
  358. }
  359. remoteAddr, err := form.ParseRemoteAddr(ctx.User)
  360. if err != nil {
  361. if models.IsErrInvalidCloneAddr(err) {
  362. addrErr := err.(models.ErrInvalidCloneAddr)
  363. switch {
  364. case addrErr.IsURLError:
  365. ctx.Error(422, "", err)
  366. case addrErr.IsPermissionDenied:
  367. ctx.Error(422, "", "You are not allowed to import local repositories.")
  368. case addrErr.IsInvalidPath:
  369. ctx.Error(422, "", "Invalid local path, it does not exist or not a directory.")
  370. default:
  371. ctx.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
  372. }
  373. } else {
  374. ctx.Error(500, "ParseRemoteAddr", err)
  375. }
  376. return
  377. }
  378. var opts = migrations.MigrateOptions{
  379. RemoteURL: remoteAddr,
  380. Name: form.RepoName,
  381. Description: form.Description,
  382. Private: form.Private || setting.Repository.ForcePrivate,
  383. Mirror: form.Mirror,
  384. AuthUsername: form.AuthUsername,
  385. AuthPassword: form.AuthPassword,
  386. Wiki: form.Wiki,
  387. Issues: form.Issues,
  388. Milestones: form.Milestones,
  389. Labels: form.Labels,
  390. Comments: true,
  391. PullRequests: form.PullRequests,
  392. Releases: form.Releases,
  393. }
  394. if opts.Mirror {
  395. opts.Issues = false
  396. opts.Milestones = false
  397. opts.Labels = false
  398. opts.Comments = false
  399. opts.PullRequests = false
  400. opts.Releases = false
  401. }
  402. repo, err := migrations.MigrateRepository(ctx.User, ctxUser.Name, opts)
  403. if err == nil {
  404. log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
  405. ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
  406. return
  407. }
  408. switch {
  409. case models.IsErrRepoAlreadyExist(err):
  410. ctx.Error(409, "", "The repository with the same name already exists.")
  411. case migrations.IsRateLimitError(err):
  412. ctx.Error(422, "", "Remote visit addressed rate limitation.")
  413. case migrations.IsTwoFactorAuthError(err):
  414. ctx.Error(422, "", "Remote visit required two factors authentication.")
  415. case models.IsErrReachLimitOfRepo(err):
  416. ctx.Error(422, "", fmt.Sprintf("You have already reached your limit of %d repositories.", ctxUser.MaxCreationLimit()))
  417. case models.IsErrNameReserved(err):
  418. ctx.Error(422, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
  419. case models.IsErrNamePatternNotAllowed(err):
  420. ctx.Error(422, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
  421. default:
  422. err = util.URLSanitizedError(err, remoteAddr)
  423. if strings.Contains(err.Error(), "Authentication failed") ||
  424. strings.Contains(err.Error(), "Bad credentials") ||
  425. strings.Contains(err.Error(), "could not read Username") {
  426. ctx.Error(422, "", fmt.Sprintf("Authentication failed: %v.", err))
  427. } else if strings.Contains(err.Error(), "fatal:") {
  428. ctx.Error(422, "", fmt.Sprintf("Migration failed: %v.", err))
  429. } else {
  430. ctx.Error(500, "MigrateRepository", err)
  431. }
  432. }
  433. }
  434. // Get one repository
  435. func Get(ctx *context.APIContext) {
  436. // swagger:operation GET /repos/{owner}/{repo} repository repoGet
  437. // ---
  438. // summary: Get a repository
  439. // produces:
  440. // - application/json
  441. // parameters:
  442. // - name: owner
  443. // in: path
  444. // description: owner of the repo
  445. // type: string
  446. // required: true
  447. // - name: repo
  448. // in: path
  449. // description: name of the repo
  450. // type: string
  451. // required: true
  452. // responses:
  453. // "200":
  454. // "$ref": "#/responses/Repository"
  455. ctx.JSON(200, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  456. }
  457. // GetByID returns a single Repository
  458. func GetByID(ctx *context.APIContext) {
  459. // swagger:operation GET /repositories/{id} repository repoGetByID
  460. // ---
  461. // summary: Get a repository by id
  462. // produces:
  463. // - application/json
  464. // parameters:
  465. // - name: id
  466. // in: path
  467. // description: id of the repo to get
  468. // type: integer
  469. // format: int64
  470. // required: true
  471. // responses:
  472. // "200":
  473. // "$ref": "#/responses/Repository"
  474. repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
  475. if err != nil {
  476. if models.IsErrRepoNotExist(err) {
  477. ctx.NotFound()
  478. } else {
  479. ctx.Error(500, "GetRepositoryByID", err)
  480. }
  481. return
  482. }
  483. perm, err := models.GetUserRepoPermission(repo, ctx.User)
  484. if err != nil {
  485. ctx.Error(500, "AccessLevel", err)
  486. return
  487. } else if !perm.HasAccess() {
  488. ctx.NotFound()
  489. return
  490. }
  491. ctx.JSON(200, repo.APIFormat(perm.AccessMode))
  492. }
  493. // Delete one repository
  494. func Delete(ctx *context.APIContext) {
  495. // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
  496. // ---
  497. // summary: Delete a repository
  498. // produces:
  499. // - application/json
  500. // parameters:
  501. // - name: owner
  502. // in: path
  503. // description: owner of the repo to delete
  504. // type: string
  505. // required: true
  506. // - name: repo
  507. // in: path
  508. // description: name of the repo to delete
  509. // type: string
  510. // required: true
  511. // responses:
  512. // "204":
  513. // "$ref": "#/responses/empty"
  514. // "403":
  515. // "$ref": "#/responses/forbidden"
  516. owner := ctx.Repo.Owner
  517. repo := ctx.Repo.Repository
  518. if owner.IsOrganization() && !ctx.User.IsAdmin {
  519. isOwner, err := owner.IsOwnedBy(ctx.User.ID)
  520. if err != nil {
  521. ctx.Error(500, "IsOwnedBy", err)
  522. return
  523. } else if !isOwner {
  524. ctx.Error(403, "", "Given user is not owner of organization.")
  525. return
  526. }
  527. }
  528. if err := models.DeleteRepository(ctx.User, owner.ID, repo.ID); err != nil {
  529. ctx.Error(500, "DeleteRepository", err)
  530. return
  531. }
  532. log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
  533. ctx.Status(204)
  534. }
  535. // MirrorSync adds a mirrored repository to the sync queue
  536. func MirrorSync(ctx *context.APIContext) {
  537. // swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
  538. // ---
  539. // summary: Sync a mirrored repository
  540. // produces:
  541. // - application/json
  542. // parameters:
  543. // - name: owner
  544. // in: path
  545. // description: owner of the repo to sync
  546. // type: string
  547. // required: true
  548. // - name: repo
  549. // in: path
  550. // description: name of the repo to sync
  551. // type: string
  552. // required: true
  553. // responses:
  554. // "200":
  555. // "$ref": "#/responses/empty"
  556. repo := ctx.Repo.Repository
  557. if !ctx.Repo.CanWrite(models.UnitTypeCode) {
  558. ctx.Error(403, "MirrorSync", "Must have write access")
  559. }
  560. go models.MirrorQueue.Add(repo.ID)
  561. ctx.Status(200)
  562. }
  563. // TopicSearch search for creating topic
  564. func TopicSearch(ctx *context.Context) {
  565. // swagger:operation GET /topics/search repository topicSearch
  566. // ---
  567. // summary: search topics via keyword
  568. // produces:
  569. // - application/json
  570. // parameters:
  571. // - name: q
  572. // in: query
  573. // description: keywords to search
  574. // required: true
  575. // type: string
  576. // responses:
  577. // "200":
  578. // "$ref": "#/responses/Repository"
  579. if ctx.User == nil {
  580. ctx.JSON(403, map[string]interface{}{
  581. "message": "Only owners could change the topics.",
  582. })
  583. return
  584. }
  585. kw := ctx.Query("q")
  586. topics, err := models.FindTopics(&models.FindTopicOptions{
  587. Keyword: kw,
  588. Limit: 10,
  589. })
  590. if err != nil {
  591. log.Error("SearchTopics failed: %v", err)
  592. ctx.JSON(500, map[string]interface{}{
  593. "message": "Search topics failed.",
  594. })
  595. return
  596. }
  597. ctx.JSON(200, map[string]interface{}{
  598. "topics": topics,
  599. })
  600. }