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