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