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 14 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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  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.ServerError("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 !ctx.User.IsAdmin {
  288. if !ctxUser.IsOrganization() && ctx.User.ID != ctxUser.ID {
  289. ctx.Error(403, "", "Given user is not an organization.")
  290. return
  291. }
  292. if ctxUser.IsOrganization() {
  293. // Check ownership of organization.
  294. isOwner, err := ctxUser.IsOwnedBy(ctx.User.ID)
  295. if err != nil {
  296. ctx.Error(500, "IsOwnedBy", err)
  297. return
  298. } else if !isOwner {
  299. ctx.Error(403, "", "Given user is not owner of organization.")
  300. return
  301. }
  302. }
  303. }
  304. remoteAddr, err := form.ParseRemoteAddr(ctx.User)
  305. if err != nil {
  306. if models.IsErrInvalidCloneAddr(err) {
  307. addrErr := err.(models.ErrInvalidCloneAddr)
  308. switch {
  309. case addrErr.IsURLError:
  310. ctx.Error(422, "", err)
  311. case addrErr.IsPermissionDenied:
  312. ctx.Error(422, "", "You are not allowed to import local repositories.")
  313. case addrErr.IsInvalidPath:
  314. ctx.Error(422, "", "Invalid local path, it does not exist or not a directory.")
  315. default:
  316. ctx.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
  317. }
  318. } else {
  319. ctx.Error(500, "ParseRemoteAddr", err)
  320. }
  321. return
  322. }
  323. repo, err := models.MigrateRepository(ctx.User, ctxUser, models.MigrateRepoOptions{
  324. Name: form.RepoName,
  325. Description: form.Description,
  326. IsPrivate: form.Private || setting.Repository.ForcePrivate,
  327. IsMirror: form.Mirror,
  328. RemoteAddr: remoteAddr,
  329. })
  330. if err != nil {
  331. err = util.URLSanitizedError(err, remoteAddr)
  332. if repo != nil {
  333. if errDelete := models.DeleteRepository(ctx.User, ctxUser.ID, repo.ID); errDelete != nil {
  334. log.Error(4, "DeleteRepository: %v", errDelete)
  335. }
  336. }
  337. ctx.Error(500, "MigrateRepository", err)
  338. return
  339. }
  340. log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
  341. ctx.JSON(201, repo.APIFormat(models.AccessModeAdmin))
  342. }
  343. // Get one repository
  344. func Get(ctx *context.APIContext) {
  345. // swagger:operation GET /repos/{owner}/{repo} repository repoGet
  346. // ---
  347. // summary: Get a repository
  348. // produces:
  349. // - application/json
  350. // parameters:
  351. // - name: owner
  352. // in: path
  353. // description: owner of the repo
  354. // type: string
  355. // required: true
  356. // - name: repo
  357. // in: path
  358. // description: name of the repo
  359. // type: string
  360. // required: true
  361. // responses:
  362. // "200":
  363. // "$ref": "#/responses/Repository"
  364. ctx.JSON(200, ctx.Repo.Repository.APIFormat(ctx.Repo.AccessMode))
  365. }
  366. // GetByID returns a single Repository
  367. func GetByID(ctx *context.APIContext) {
  368. // swagger:operation GET /repositories/{id} repository repoGetByID
  369. // ---
  370. // summary: Get a repository by id
  371. // produces:
  372. // - application/json
  373. // parameters:
  374. // - name: id
  375. // in: path
  376. // description: id of the repo to get
  377. // type: integer
  378. // required: true
  379. // responses:
  380. // "200":
  381. // "$ref": "#/responses/Repository"
  382. repo, err := models.GetRepositoryByID(ctx.ParamsInt64(":id"))
  383. if err != nil {
  384. if models.IsErrRepoNotExist(err) {
  385. ctx.Status(404)
  386. } else {
  387. ctx.Error(500, "GetRepositoryByID", err)
  388. }
  389. return
  390. }
  391. access, err := models.AccessLevel(ctx.User.ID, repo)
  392. if err != nil {
  393. ctx.Error(500, "AccessLevel", err)
  394. return
  395. } else if access < models.AccessModeRead {
  396. ctx.Status(404)
  397. return
  398. }
  399. ctx.JSON(200, repo.APIFormat(access))
  400. }
  401. // Delete one repository
  402. func Delete(ctx *context.APIContext) {
  403. // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
  404. // ---
  405. // summary: Delete a repository
  406. // produces:
  407. // - application/json
  408. // parameters:
  409. // - name: owner
  410. // in: path
  411. // description: owner of the repo to delete
  412. // type: string
  413. // required: true
  414. // - name: repo
  415. // in: path
  416. // description: name of the repo to delete
  417. // type: string
  418. // required: true
  419. // responses:
  420. // "204":
  421. // "$ref": "#/responses/empty"
  422. // "403":
  423. // "$ref": "#/responses/forbidden"
  424. if !ctx.Repo.IsAdmin() {
  425. ctx.Error(403, "", "Must have admin rights")
  426. return
  427. }
  428. owner := ctx.Repo.Owner
  429. repo := ctx.Repo.Repository
  430. if owner.IsOrganization() {
  431. isOwner, err := owner.IsOwnedBy(ctx.User.ID)
  432. if err != nil {
  433. ctx.Error(500, "IsOwnedBy", err)
  434. return
  435. } else if !isOwner {
  436. ctx.Error(403, "", "Given user is not owner of organization.")
  437. return
  438. }
  439. }
  440. if err := models.DeleteRepository(ctx.User, owner.ID, repo.ID); err != nil {
  441. ctx.Error(500, "DeleteRepository", err)
  442. return
  443. }
  444. log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
  445. ctx.Status(204)
  446. }
  447. // MirrorSync adds a mirrored repository to the sync queue
  448. func MirrorSync(ctx *context.APIContext) {
  449. // swagger:operation POST /repos/{owner}/{repo}/mirror-sync repository repoMirrorSync
  450. // ---
  451. // summary: Sync a mirrored repository
  452. // produces:
  453. // - application/json
  454. // parameters:
  455. // - name: owner
  456. // in: path
  457. // description: owner of the repo to sync
  458. // type: string
  459. // required: true
  460. // - name: repo
  461. // in: path
  462. // description: name of the repo to sync
  463. // type: string
  464. // required: true
  465. // responses:
  466. // "200":
  467. // "$ref": "#/responses/empty"
  468. repo := ctx.Repo.Repository
  469. if !ctx.Repo.IsWriter() {
  470. ctx.Error(403, "MirrorSync", "Must have write access")
  471. }
  472. go models.MirrorQueue.Add(repo.ID)
  473. ctx.Status(200)
  474. }
  475. // TopicSearch search for creating topic
  476. func TopicSearch(ctx *context.Context) {
  477. // swagger:operation GET /topics/search repository topicSearch
  478. // ---
  479. // summary: search topics via keyword
  480. // produces:
  481. // - application/json
  482. // parameters:
  483. // - name: q
  484. // in: query
  485. // description: keywords to search
  486. // required: true
  487. // type: string
  488. // responses:
  489. // "200":
  490. // "$ref": "#/responses/Repository"
  491. if ctx.User == nil {
  492. ctx.JSON(403, map[string]interface{}{
  493. "message": "Only owners could change the topics.",
  494. })
  495. return
  496. }
  497. kw := ctx.Query("q")
  498. topics, err := models.FindTopics(&models.FindTopicOptions{
  499. Keyword: kw,
  500. Limit: 10,
  501. })
  502. if err != nil {
  503. log.Error(2, "SearchTopics failed: %v", err)
  504. ctx.JSON(500, map[string]interface{}{
  505. "message": "Search topics failed.",
  506. })
  507. return
  508. }
  509. ctx.JSON(200, map[string]interface{}{
  510. "topics": topics,
  511. })
  512. }