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

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