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

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