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

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
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. }