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