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

11 years ago
11 years ago
11 years ago
11 years ago
11 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
11 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "fmt"
  7. "net/http"
  8. "strings"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/auth"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/setting"
  14. "code.gitea.io/gitea/modules/util"
  15. "code.gitea.io/gitea/routers/api/v1/convert"
  16. api "code.gitea.io/sdk/gitea"
  17. )
  18. // Search repositories via options
  19. func Search(ctx *context.APIContext) {
  20. // swagger:operation GET /repos/search repository repoSearch
  21. // ---
  22. // summary: Search for repositories
  23. // produces:
  24. // - application/json
  25. // parameters:
  26. // - name: q
  27. // in: query
  28. // description: keyword
  29. // type: string
  30. // - name: uid
  31. // in: query
  32. // description: search only for repos that the user with the given id owns or contributes to
  33. // type: integer
  34. // - name: page
  35. // in: query
  36. // description: page number of results to return (1-based)
  37. // type: integer
  38. // - name: limit
  39. // in: query
  40. // description: page size of results, maximum page size is 50
  41. // type: integer
  42. // - name: mode
  43. // in: query
  44. // description: type of repository to search for. Supported values are
  45. // "fork", "source", "mirror" and "collaborative"
  46. // type: string
  47. // - name: exclusive
  48. // in: query
  49. // description: if `uid` is given, search only for repos that the user owns
  50. // type: boolean
  51. // responses:
  52. // "200":
  53. // "$ref": "#/responses/SearchResults"
  54. // "422":
  55. // "$ref": "#/responses/validationError"
  56. opts := &models.SearchRepoOptions{
  57. Keyword: strings.Trim(ctx.Query("q"), " "),
  58. OwnerID: ctx.QueryInt64("uid"),
  59. Page: ctx.QueryInt("page"),
  60. PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
  61. Collaborate: util.OptionalBoolNone,
  62. }
  63. if ctx.QueryBool("exclusive") {
  64. opts.Collaborate = util.OptionalBoolFalse
  65. }
  66. var mode = ctx.Query("mode")
  67. switch mode {
  68. case "source":
  69. opts.Fork = util.OptionalBoolFalse
  70. opts.Mirror = util.OptionalBoolFalse
  71. case "fork":
  72. opts.Fork = util.OptionalBoolTrue
  73. case "mirror":
  74. opts.Mirror = util.OptionalBoolTrue
  75. case "collaborative":
  76. opts.Mirror = util.OptionalBoolFalse
  77. opts.Collaborate = util.OptionalBoolTrue
  78. case "":
  79. default:
  80. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
  81. return
  82. }
  83. var err error
  84. if opts.OwnerID > 0 {
  85. var repoOwner *models.User
  86. if ctx.User != nil && ctx.User.ID == opts.OwnerID {
  87. repoOwner = ctx.User
  88. } else {
  89. repoOwner, err = models.GetUserByID(opts.OwnerID)
  90. if err != nil {
  91. ctx.JSON(500, api.SearchError{
  92. OK: false,
  93. Error: err.Error(),
  94. })
  95. return
  96. }
  97. }
  98. if repoOwner.IsOrganization() {
  99. opts.Collaborate = util.OptionalBoolFalse
  100. }
  101. // Check visibility.
  102. if ctx.IsSigned {
  103. if ctx.User.ID == repoOwner.ID {
  104. opts.Private = true
  105. } else if repoOwner.IsOrganization() {
  106. opts.Private, err = repoOwner.IsOwnedBy(ctx.User.ID)
  107. if err != nil {
  108. ctx.JSON(500, api.SearchError{
  109. OK: false,
  110. Error: err.Error(),
  111. })
  112. return
  113. }
  114. }
  115. }
  116. }
  117. repos, count, err := models.SearchRepositoryByName(opts)
  118. if err != nil {
  119. ctx.JSON(500, api.SearchError{
  120. OK: false,
  121. Error: err.Error(),
  122. })
  123. return
  124. }
  125. var userID int64
  126. if ctx.IsSigned {
  127. userID = ctx.User.ID
  128. }
  129. results := make([]*api.Repository, len(repos))
  130. for i, repo := range repos {
  131. if err = repo.GetOwner(); err != nil {
  132. ctx.JSON(500, api.SearchError{
  133. OK: false,
  134. Error: err.Error(),
  135. })
  136. return
  137. }
  138. accessMode, err := models.AccessLevel(userID, repo)
  139. if err != nil {
  140. ctx.JSON(500, api.SearchError{
  141. OK: false,
  142. Error: err.Error(),
  143. })
  144. }
  145. results[i] = repo.APIFormat(accessMode)
  146. }
  147. ctx.SetLinkHeader(int(count), setting.API.MaxResponseItems)
  148. ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count))
  149. ctx.JSON(200, api.SearchResults{
  150. OK: true,
  151. Data: results,
  152. })
  153. }
  154. // CreateUserRepo create a repository for a user
  155. func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateRepoOption) {
  156. repo, err := models.CreateRepository(ctx.User, owner, models.CreateRepoOptions{
  157. Name: opt.Name,
  158. Description: opt.Description,
  159. Gitignores: opt.Gitignores,
  160. License: opt.License,
  161. Readme: opt.Readme,
  162. IsPrivate: opt.Private,
  163. AutoInit: opt.AutoInit,
  164. })
  165. if err != nil {
  166. if models.IsErrRepoAlreadyExist(err) ||
  167. models.IsErrNameReserved(err) ||
  168. models.IsErrNamePatternNotAllowed(err) {
  169. ctx.Error(422, "", err)
  170. } else {
  171. if repo != nil {
  172. if err = models.DeleteRepository(ctx.User, ctx.User.ID, repo.ID); err != nil {
  173. log.Error(4, "DeleteRepository: %v", err)
  174. }
  175. }
  176. ctx.Error(500, "CreateRepository", err)
  177. }
  178. return
  179. }
  180. ctx.JSON(201, repo.APIFormat(models.AccessModeOwner))
  181. }
  182. // Create one repository of mine
  183. func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
  184. // swagger:operation POST /user/repos repository user createCurrentUserRepo
  185. // ---
  186. // summary: Create a repository
  187. // consumes:
  188. // - application/json
  189. // produces:
  190. // - application/json
  191. // parameters:
  192. // - name: body
  193. // in: body
  194. // schema:
  195. // "$ref": "#/definitions/CreateRepoOption"
  196. // responses:
  197. // "201":
  198. // "$ref": "#/responses/Repository"
  199. if ctx.User.IsOrganization() {
  200. // Shouldn't reach this condition, but just in case.
  201. ctx.Error(422, "", "not allowed creating repository for organization")
  202. return
  203. }
  204. CreateUserRepo(ctx, ctx.User, opt)
  205. }
  206. // CreateOrgRepo create one repository of the organization
  207. func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
  208. // swagger:operation POST /org/{org}/repos organization createOrgRepo
  209. // ---
  210. // summary: Create a repository in an organization
  211. // consumes:
  212. // - application/json
  213. // produces:
  214. // - application/json
  215. // parameters:
  216. // - name: org
  217. // in: path
  218. // description: name of organization
  219. // type: string
  220. // required: true
  221. // - name: body
  222. // in: body
  223. // schema:
  224. // "$ref": "#/definitions/CreateRepoOption"
  225. // responses:
  226. // "201":
  227. // "$ref": "#/responses/Repository"
  228. // "422":
  229. // "$ref": "#/responses/validationError"
  230. // "403":
  231. // "$ref": "#/responses/forbidden"
  232. org, err := models.GetOrgByName(ctx.Params(":org"))
  233. if err != nil {
  234. if models.IsErrOrgNotExist(err) {
  235. ctx.Error(422, "", err)
  236. } else {
  237. ctx.Error(500, "GetOrgByName", err)
  238. }
  239. return
  240. }
  241. isOwner, err := org.IsOwnedBy(ctx.User.ID)
  242. if err != nil {
  243. ctx.Handle(500, "IsOwnedBy", err)
  244. return
  245. } else if !isOwner {
  246. ctx.Error(403, "", "Given user is not owner of organization.")
  247. return
  248. }
  249. CreateUserRepo(ctx, org, opt)
  250. }
  251. // Migrate migrate remote git repository to gitea
  252. func Migrate(ctx *context.APIContext, form auth.MigrateRepoForm) {
  253. // swagger:operation POST /repos/migrate repository repoMigrate
  254. // ---
  255. // summary: Migrate a remote git repository
  256. // consumes:
  257. // - application/json
  258. // produces:
  259. // - application/json
  260. // parameters:
  261. // - name: body
  262. // in: body
  263. // schema:
  264. // "$ref": "#/definitions/MigrateRepoForm"
  265. // responses:
  266. // "201":
  267. // "$ref": "#/responses/Repository"
  268. ctxUser := ctx.User
  269. // Not equal means context user is an organization,
  270. // or is another user/organization if current user is admin.
  271. if form.UID != ctxUser.ID {
  272. org, err := models.GetUserByID(form.UID)
  273. if err != nil {
  274. if models.IsErrUserNotExist(err) {
  275. ctx.Error(422, "", err)
  276. } else {
  277. ctx.Error(500, "GetUserByID", err)
  278. }
  279. return
  280. }
  281. ctxUser = org
  282. }
  283. if ctx.HasError() {
  284. ctx.Error(422, "", ctx.GetErrMsg())
  285. return
  286. }
  287. if ctxUser.IsOrganization() && !ctx.User.IsAdmin {
  288. // Check ownership of organization.
  289. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  290. if err != nil {
  291. ctx.Error(500, "IsOwnedBy", err)
  292. return
  293. } else if !isOwner {
  294. ctx.Error(403, "", "Given user is not owner of organization.")
  295. return
  296. }
  297. }
  298. remoteAddr, err := form.ParseRemoteAddr(ctx.User)
  299. if err != nil {
  300. if models.IsErrInvalidCloneAddr(err) {
  301. addrErr := err.(models.ErrInvalidCloneAddr)
  302. switch {
  303. case addrErr.IsURLError:
  304. ctx.Error(422, "", err)
  305. case addrErr.IsPermissionDenied:
  306. ctx.Error(422, "", "You are not allowed to import local repositories.")
  307. case addrErr.IsInvalidPath:
  308. ctx.Error(422, "", "Invalid local path, it does not exist or not a directory.")
  309. default:
  310. ctx.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
  311. }
  312. } else {
  313. ctx.Error(500, "ParseRemoteAddr", err)
  314. }
  315. return
  316. }
  317. repo, err := models.MigrateRepository(ctx.User, ctxUser, models.MigrateRepoOptions{
  318. Name: form.RepoName,
  319. Description: form.Description,
  320. IsPrivate: form.Private || setting.Repository.ForcePrivate,
  321. IsMirror: form.Mirror,
  322. RemoteAddr: remoteAddr,
  323. })
  324. if err != nil {
  325. err = util.URLSanitizedError(err, remoteAddr)
  326. if repo != nil {
  327. if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
  328. log.Error(4, "DeleteRepository: %v", errDelete)
  329. }
  330. }
  331. ctx.Error(500, "MigrateRepository", err)
  332. return
  333. }
  334. log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
  335. ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
  336. }
  337. // Get one repository
  338. func Get(ctx *context.APIContext) {
  339. // swagger:operation GET /repos/{owner}/{repo} repository repoGet
  340. // ---
  341. // summary: Get a repository
  342. // produces:
  343. // - application/json
  344. // parameters:
  345. // - name: owner
  346. // in: path
  347. // description: owner of the repo
  348. // type: string
  349. // required: true
  350. // - name: repo
  351. // in: path
  352. // description: name of the repo
  353. // type: string
  354. // required: true
  355. // responses:
  356. // "200":
  357. // "$ref": "#/responses/Repository"
  358. ctx.JSON(200, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  359. }
  360. // GetByID returns a single Repository
  361. func GetByID(ctx *context.APIContext) {
  362. // swagger:operation GET /repositories/{id} repository repoGetByID
  363. // ---
  364. // summary: Get a repository by id
  365. // produces:
  366. // - application/json
  367. // parameters:
  368. // - name: id
  369. // in: path
  370. // description: id of the repo to get
  371. // type: integer
  372. // required: true
  373. // responses:
  374. // "200":
  375. // "$ref": "#/responses/Repository"
  376. repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
  377. if err != nil {
  378. if models.IsErrRepoNotExist(err) {
  379. ctx.Status(404)
  380. } else {
  381. ctx.Error(500, "GetRepositoryByID", err)
  382. }
  383. return
  384. }
  385. access, err := models.AccessLevel(ctx.User.ID, repo)
  386. if err != nil {
  387. ctx.Error(500, "AccessLevel", err)
  388. return
  389. } else if access < models.AccessModeRead {
  390. ctx.Status(404)
  391. return
  392. }
  393. ctx.JSON(200, repo.APIFormat(access))
  394. }
  395. // Delete one repository
  396. func Delete(ctx *context.APIContext) {
  397. // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
  398. // ---
  399. // summary: Delete a repository
  400. // produces:
  401. // - application/json
  402. // parameters:
  403. // - name: owner
  404. // in: path
  405. // description: owner of the repo to delete
  406. // type: string
  407. // required: true
  408. // - name: repo
  409. // in: path
  410. // description: name of the repo to delete
  411. // type: string
  412. // required: true
  413. // responses:
  414. // "204":
  415. // "$ref": "#/responses/empty"
  416. // "403":
  417. // "$ref": "#/responses/forbidden"
  418. if !ctx.Repo.IsAdmin() {
  419. ctx.Error(403, "", "Must have admin rights")
  420. return
  421. }
  422. owner := ctx.Repo.Owner
  423. repo := ctx.Repo.Repository
  424. if owner.IsOrganization() {
  425. isOwner, err := owner.IsOwnedBy(ctx.User.ID)
  426. if err != nil {
  427. ctx.Error(500, "IsOwnedBy", err)
  428. return
  429. } else if !isOwner {
  430. ctx.Error(403, "", "Given user is not owner of organization.")
  431. return
  432. }
  433. }
  434. if err := models.DeleteRepository(ctx.User, owner.ID, repo.ID); err != nil {
  435. ctx.Error(500, "DeleteRepository", err)
  436. return
  437. }
  438. log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
  439. ctx.Status(204)
  440. }
  441. // MirrorSync adds a mirrored repository to the sync queue
  442. func MirrorSync(ctx *context.APIContext) {
  443. // swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
  444. // ---
  445. // summary: Sync a mirrored repository
  446. // produces:
  447. // - application/json
  448. // parameters:
  449. // - name: owner
  450. // in: path
  451. // description: owner of the repo to sync
  452. // type: string
  453. // required: true
  454. // - name: repo
  455. // in: path
  456. // description: name of the repo to sync
  457. // type: string
  458. // required: true
  459. // responses:
  460. // "200":
  461. // "$ref": "#/responses/empty"
  462. repo := ctx.Repo.Repository
  463. if !ctx.Repo.IsWriter() {
  464. ctx.Error(403, "MirrorSync", "Must have write access")
  465. }
  466. go models.MirrorQueue.Add(repo.ID)
  467. ctx.Status(200)
  468. }