You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo.go 39KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "fmt"
  7. "net/http"
  8. "slices"
  9. "strings"
  10. "time"
  11. activities_model "code.gitea.io/gitea/models/activities"
  12. "code.gitea.io/gitea/models/db"
  13. "code.gitea.io/gitea/models/organization"
  14. "code.gitea.io/gitea/models/perm"
  15. access_model "code.gitea.io/gitea/models/perm/access"
  16. repo_model "code.gitea.io/gitea/models/repo"
  17. unit_model "code.gitea.io/gitea/models/unit"
  18. user_model "code.gitea.io/gitea/models/user"
  19. "code.gitea.io/gitea/modules/context"
  20. "code.gitea.io/gitea/modules/git"
  21. "code.gitea.io/gitea/modules/label"
  22. "code.gitea.io/gitea/modules/log"
  23. repo_module "code.gitea.io/gitea/modules/repository"
  24. "code.gitea.io/gitea/modules/setting"
  25. api "code.gitea.io/gitea/modules/structs"
  26. "code.gitea.io/gitea/modules/util"
  27. "code.gitea.io/gitea/modules/validation"
  28. "code.gitea.io/gitea/modules/web"
  29. "code.gitea.io/gitea/routers/api/v1/utils"
  30. "code.gitea.io/gitea/services/convert"
  31. "code.gitea.io/gitea/services/issue"
  32. repo_service "code.gitea.io/gitea/services/repository"
  33. )
  34. // Search repositories via options
  35. func Search(ctx *context.APIContext) {
  36. // swagger:operation GET /repos/search repository repoSearch
  37. // ---
  38. // summary: Search for repositories
  39. // produces:
  40. // - application/json
  41. // parameters:
  42. // - name: q
  43. // in: query
  44. // description: keyword
  45. // type: string
  46. // - name: topic
  47. // in: query
  48. // description: Limit search to repositories with keyword as topic
  49. // type: boolean
  50. // - name: includeDesc
  51. // in: query
  52. // description: include search of keyword within repository description
  53. // type: boolean
  54. // - name: uid
  55. // in: query
  56. // description: search only for repos that the user with the given id owns or contributes to
  57. // type: integer
  58. // format: int64
  59. // - name: priority_owner_id
  60. // in: query
  61. // description: repo owner to prioritize in the results
  62. // type: integer
  63. // format: int64
  64. // - name: team_id
  65. // in: query
  66. // description: search only for repos that belong to the given team id
  67. // type: integer
  68. // format: int64
  69. // - name: starredBy
  70. // in: query
  71. // description: search only for repos that the user with the given id has starred
  72. // type: integer
  73. // format: int64
  74. // - name: private
  75. // in: query
  76. // description: include private repositories this user has access to (defaults to true)
  77. // type: boolean
  78. // - name: is_private
  79. // in: query
  80. // description: show only pubic, private or all repositories (defaults to all)
  81. // type: boolean
  82. // - name: template
  83. // in: query
  84. // description: include template repositories this user has access to (defaults to true)
  85. // type: boolean
  86. // - name: archived
  87. // in: query
  88. // description: show only archived, non-archived or all repositories (defaults to all)
  89. // type: boolean
  90. // - name: mode
  91. // in: query
  92. // description: type of repository to search for. Supported values are
  93. // "fork", "source", "mirror" and "collaborative"
  94. // type: string
  95. // - name: exclusive
  96. // in: query
  97. // description: if `uid` is given, search only for repos that the user owns
  98. // type: boolean
  99. // - name: sort
  100. // in: query
  101. // description: sort repos by attribute. Supported values are
  102. // "alpha", "created", "updated", "size", and "id".
  103. // Default is "alpha"
  104. // type: string
  105. // - name: order
  106. // in: query
  107. // description: sort order, either "asc" (ascending) or "desc" (descending).
  108. // Default is "asc", ignored if "sort" is not specified.
  109. // type: string
  110. // - name: page
  111. // in: query
  112. // description: page number of results to return (1-based)
  113. // type: integer
  114. // - name: limit
  115. // in: query
  116. // description: page size of results
  117. // type: integer
  118. // responses:
  119. // "200":
  120. // "$ref": "#/responses/SearchResults"
  121. // "422":
  122. // "$ref": "#/responses/validationError"
  123. opts := &repo_model.SearchRepoOptions{
  124. ListOptions: utils.GetListOptions(ctx),
  125. Actor: ctx.Doer,
  126. Keyword: ctx.FormTrim("q"),
  127. OwnerID: ctx.FormInt64("uid"),
  128. PriorityOwnerID: ctx.FormInt64("priority_owner_id"),
  129. TeamID: ctx.FormInt64("team_id"),
  130. TopicOnly: ctx.FormBool("topic"),
  131. Collaborate: util.OptionalBoolNone,
  132. Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
  133. Template: util.OptionalBoolNone,
  134. StarredByID: ctx.FormInt64("starredBy"),
  135. IncludeDescription: ctx.FormBool("includeDesc"),
  136. }
  137. if ctx.FormString("template") != "" {
  138. opts.Template = util.OptionalBoolOf(ctx.FormBool("template"))
  139. }
  140. if ctx.FormBool("exclusive") {
  141. opts.Collaborate = util.OptionalBoolFalse
  142. }
  143. mode := ctx.FormString("mode")
  144. switch mode {
  145. case "source":
  146. opts.Fork = util.OptionalBoolFalse
  147. opts.Mirror = util.OptionalBoolFalse
  148. case "fork":
  149. opts.Fork = util.OptionalBoolTrue
  150. case "mirror":
  151. opts.Mirror = util.OptionalBoolTrue
  152. case "collaborative":
  153. opts.Mirror = util.OptionalBoolFalse
  154. opts.Collaborate = util.OptionalBoolTrue
  155. case "":
  156. default:
  157. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
  158. return
  159. }
  160. if ctx.FormString("archived") != "" {
  161. opts.Archived = util.OptionalBoolOf(ctx.FormBool("archived"))
  162. }
  163. if ctx.FormString("is_private") != "" {
  164. opts.IsPrivate = util.OptionalBoolOf(ctx.FormBool("is_private"))
  165. }
  166. sortMode := ctx.FormString("sort")
  167. if len(sortMode) > 0 {
  168. sortOrder := ctx.FormString("order")
  169. if len(sortOrder) == 0 {
  170. sortOrder = "asc"
  171. }
  172. if searchModeMap, ok := repo_model.SearchOrderByMap[sortOrder]; ok {
  173. if orderBy, ok := searchModeMap[sortMode]; ok {
  174. opts.OrderBy = orderBy
  175. } else {
  176. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
  177. return
  178. }
  179. } else {
  180. ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
  181. return
  182. }
  183. }
  184. var err error
  185. repos, count, err := repo_model.SearchRepository(ctx, opts)
  186. if err != nil {
  187. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  188. OK: false,
  189. Error: err.Error(),
  190. })
  191. return
  192. }
  193. results := make([]*api.Repository, len(repos))
  194. for i, repo := range repos {
  195. if err = repo.LoadOwner(ctx); err != nil {
  196. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  197. OK: false,
  198. Error: err.Error(),
  199. })
  200. return
  201. }
  202. permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
  203. if err != nil {
  204. ctx.JSON(http.StatusInternalServerError, api.SearchError{
  205. OK: false,
  206. Error: err.Error(),
  207. })
  208. }
  209. results[i] = convert.ToRepo(ctx, repo, permission)
  210. }
  211. ctx.SetLinkHeader(int(count), opts.PageSize)
  212. ctx.SetTotalCountHeader(count)
  213. ctx.JSON(http.StatusOK, api.SearchResults{
  214. OK: true,
  215. Data: results,
  216. })
  217. }
  218. // CreateUserRepo create a repository for a user
  219. func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.CreateRepoOption) {
  220. if opt.AutoInit && opt.Readme == "" {
  221. opt.Readme = "Default"
  222. }
  223. // If the readme template does not exist, a 400 will be returned.
  224. if opt.AutoInit && len(opt.Readme) > 0 && !slices.Contains(repo_module.Readmes, opt.Readme) {
  225. ctx.Error(http.StatusBadRequest, "", fmt.Errorf("readme template does not exist, available templates: %v", repo_module.Readmes))
  226. return
  227. }
  228. repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_service.CreateRepoOptions{
  229. Name: opt.Name,
  230. Description: opt.Description,
  231. IssueLabels: opt.IssueLabels,
  232. Gitignores: opt.Gitignores,
  233. License: opt.License,
  234. Readme: opt.Readme,
  235. IsPrivate: opt.Private,
  236. AutoInit: opt.AutoInit,
  237. DefaultBranch: opt.DefaultBranch,
  238. TrustModel: repo_model.ToTrustModel(opt.TrustModel),
  239. IsTemplate: opt.Template,
  240. })
  241. if err != nil {
  242. if repo_model.IsErrRepoAlreadyExist(err) {
  243. ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
  244. } else if db.IsErrNameReserved(err) ||
  245. db.IsErrNamePatternNotAllowed(err) ||
  246. label.IsErrTemplateLoad(err) {
  247. ctx.Error(http.StatusUnprocessableEntity, "", err)
  248. } else {
  249. ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
  250. }
  251. return
  252. }
  253. // reload repo from db to get a real state after creation
  254. repo, err = repo_model.GetRepositoryByID(ctx, repo.ID)
  255. if err != nil {
  256. ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
  257. }
  258. ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}))
  259. }
  260. // Create one repository of mine
  261. func Create(ctx *context.APIContext) {
  262. // swagger:operation POST /user/repos repository user createCurrentUserRepo
  263. // ---
  264. // summary: Create a repository
  265. // consumes:
  266. // - application/json
  267. // produces:
  268. // - application/json
  269. // parameters:
  270. // - name: body
  271. // in: body
  272. // schema:
  273. // "$ref": "#/definitions/CreateRepoOption"
  274. // responses:
  275. // "201":
  276. // "$ref": "#/responses/Repository"
  277. // "400":
  278. // "$ref": "#/responses/error"
  279. // "409":
  280. // description: The repository with the same name already exists.
  281. // "422":
  282. // "$ref": "#/responses/validationError"
  283. opt := web.GetForm(ctx).(*api.CreateRepoOption)
  284. if ctx.Doer.IsOrganization() {
  285. // Shouldn't reach this condition, but just in case.
  286. ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
  287. return
  288. }
  289. CreateUserRepo(ctx, ctx.Doer, *opt)
  290. }
  291. // Generate Create a repository using a template
  292. func Generate(ctx *context.APIContext) {
  293. // swagger:operation POST /repos/{template_owner}/{template_repo}/generate repository generateRepo
  294. // ---
  295. // summary: Create a repository using a template
  296. // consumes:
  297. // - application/json
  298. // produces:
  299. // - application/json
  300. // parameters:
  301. // - name: template_owner
  302. // in: path
  303. // description: name of the template repository owner
  304. // type: string
  305. // required: true
  306. // - name: template_repo
  307. // in: path
  308. // description: name of the template repository
  309. // type: string
  310. // required: true
  311. // - name: body
  312. // in: body
  313. // schema:
  314. // "$ref": "#/definitions/GenerateRepoOption"
  315. // responses:
  316. // "201":
  317. // "$ref": "#/responses/Repository"
  318. // "403":
  319. // "$ref": "#/responses/forbidden"
  320. // "404":
  321. // "$ref": "#/responses/notFound"
  322. // "409":
  323. // description: The repository with the same name already exists.
  324. // "422":
  325. // "$ref": "#/responses/validationError"
  326. form := web.GetForm(ctx).(*api.GenerateRepoOption)
  327. if !ctx.Repo.Repository.IsTemplate {
  328. ctx.Error(http.StatusUnprocessableEntity, "", "this is not a template repo")
  329. return
  330. }
  331. if ctx.Doer.IsOrganization() {
  332. ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
  333. return
  334. }
  335. opts := repo_module.GenerateRepoOptions{
  336. Name: form.Name,
  337. DefaultBranch: form.DefaultBranch,
  338. Description: form.Description,
  339. Private: form.Private,
  340. GitContent: form.GitContent,
  341. Topics: form.Topics,
  342. GitHooks: form.GitHooks,
  343. Webhooks: form.Webhooks,
  344. Avatar: form.Avatar,
  345. IssueLabels: form.Labels,
  346. ProtectedBranch: form.ProtectedBranch,
  347. }
  348. if !opts.IsValid() {
  349. ctx.Error(http.StatusUnprocessableEntity, "", "must select at least one template item")
  350. return
  351. }
  352. ctxUser := ctx.Doer
  353. var err error
  354. if form.Owner != ctxUser.Name {
  355. ctxUser, err = user_model.GetUserByName(ctx, form.Owner)
  356. if err != nil {
  357. if user_model.IsErrUserNotExist(err) {
  358. ctx.JSON(http.StatusNotFound, map[string]any{
  359. "error": "request owner `" + form.Owner + "` does not exist",
  360. })
  361. return
  362. }
  363. ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
  364. return
  365. }
  366. if !ctx.Doer.IsAdmin && !ctxUser.IsOrganization() {
  367. ctx.Error(http.StatusForbidden, "", "Only admin can generate repository for other user.")
  368. return
  369. }
  370. if !ctx.Doer.IsAdmin {
  371. canCreate, err := organization.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx.Doer.ID)
  372. if err != nil {
  373. ctx.ServerError("CanCreateOrgRepo", err)
  374. return
  375. } else if !canCreate {
  376. ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
  377. return
  378. }
  379. }
  380. }
  381. repo, err := repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, ctx.Repo.Repository, opts)
  382. if err != nil {
  383. if repo_model.IsErrRepoAlreadyExist(err) {
  384. ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
  385. } else if db.IsErrNameReserved(err) ||
  386. db.IsErrNamePatternNotAllowed(err) {
  387. ctx.Error(http.StatusUnprocessableEntity, "", err)
  388. } else {
  389. ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
  390. }
  391. return
  392. }
  393. log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
  394. ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}))
  395. }
  396. // CreateOrgRepoDeprecated create one repository of the organization
  397. func CreateOrgRepoDeprecated(ctx *context.APIContext) {
  398. // swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
  399. // ---
  400. // summary: Create a repository in an organization
  401. // deprecated: true
  402. // consumes:
  403. // - application/json
  404. // produces:
  405. // - application/json
  406. // parameters:
  407. // - name: org
  408. // in: path
  409. // description: name of organization
  410. // type: string
  411. // required: true
  412. // - name: body
  413. // in: body
  414. // schema:
  415. // "$ref": "#/definitions/CreateRepoOption"
  416. // responses:
  417. // "201":
  418. // "$ref": "#/responses/Repository"
  419. // "422":
  420. // "$ref": "#/responses/validationError"
  421. // "403":
  422. // "$ref": "#/responses/forbidden"
  423. // "404":
  424. // "$ref": "#/responses/notFound"
  425. CreateOrgRepo(ctx)
  426. }
  427. // CreateOrgRepo create one repository of the organization
  428. func CreateOrgRepo(ctx *context.APIContext) {
  429. // swagger:operation POST /orgs/{org}/repos organization createOrgRepo
  430. // ---
  431. // summary: Create a repository in an organization
  432. // consumes:
  433. // - application/json
  434. // produces:
  435. // - application/json
  436. // parameters:
  437. // - name: org
  438. // in: path
  439. // description: name of organization
  440. // type: string
  441. // required: true
  442. // - name: body
  443. // in: body
  444. // schema:
  445. // "$ref": "#/definitions/CreateRepoOption"
  446. // responses:
  447. // "201":
  448. // "$ref": "#/responses/Repository"
  449. // "400":
  450. // "$ref": "#/responses/error"
  451. // "404":
  452. // "$ref": "#/responses/notFound"
  453. // "403":
  454. // "$ref": "#/responses/forbidden"
  455. opt := web.GetForm(ctx).(*api.CreateRepoOption)
  456. org, err := organization.GetOrgByName(ctx, ctx.Params(":org"))
  457. if err != nil {
  458. if organization.IsErrOrgNotExist(err) {
  459. ctx.Error(http.StatusUnprocessableEntity, "", err)
  460. } else {
  461. ctx.Error(http.StatusInternalServerError, "GetOrgByName", err)
  462. }
  463. return
  464. }
  465. if !organization.HasOrgOrUserVisible(ctx, org.AsUser(), ctx.Doer) {
  466. ctx.NotFound("HasOrgOrUserVisible", nil)
  467. return
  468. }
  469. if !ctx.Doer.IsAdmin {
  470. canCreate, err := org.CanCreateOrgRepo(ctx.Doer.ID)
  471. if err != nil {
  472. ctx.Error(http.StatusInternalServerError, "CanCreateOrgRepo", err)
  473. return
  474. } else if !canCreate {
  475. ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")
  476. return
  477. }
  478. }
  479. CreateUserRepo(ctx, org.AsUser(), *opt)
  480. }
  481. // Get one repository
  482. func Get(ctx *context.APIContext) {
  483. // swagger:operation GET /repos/{owner}/{repo} repository repoGet
  484. // ---
  485. // summary: Get a repository
  486. // produces:
  487. // - application/json
  488. // parameters:
  489. // - name: owner
  490. // in: path
  491. // description: owner of the repo
  492. // type: string
  493. // required: true
  494. // - name: repo
  495. // in: path
  496. // description: name of the repo
  497. // type: string
  498. // required: true
  499. // responses:
  500. // "200":
  501. // "$ref": "#/responses/Repository"
  502. // "404":
  503. // "$ref": "#/responses/notFound"
  504. if err := ctx.Repo.Repository.LoadAttributes(ctx); err != nil {
  505. ctx.Error(http.StatusInternalServerError, "Repository.LoadAttributes", err)
  506. return
  507. }
  508. ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
  509. }
  510. // GetByID returns a single Repository
  511. func GetByID(ctx *context.APIContext) {
  512. // swagger:operation GET /repositories/{id} repository repoGetByID
  513. // ---
  514. // summary: Get a repository by id
  515. // produces:
  516. // - application/json
  517. // parameters:
  518. // - name: id
  519. // in: path
  520. // description: id of the repo to get
  521. // type: integer
  522. // format: int64
  523. // required: true
  524. // responses:
  525. // "200":
  526. // "$ref": "#/responses/Repository"
  527. // "404":
  528. // "$ref": "#/responses/notFound"
  529. repo, err := repo_model.GetRepositoryByID(ctx, ctx.ParamsInt64(":id"))
  530. if err != nil {
  531. if repo_model.IsErrRepoNotExist(err) {
  532. ctx.NotFound()
  533. } else {
  534. ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
  535. }
  536. return
  537. }
  538. permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
  539. if err != nil {
  540. ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
  541. return
  542. } else if !permission.HasAccess() {
  543. ctx.NotFound()
  544. return
  545. }
  546. ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
  547. }
  548. // Edit edit repository properties
  549. func Edit(ctx *context.APIContext) {
  550. // swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit
  551. // ---
  552. // summary: Edit a repository's properties. Only fields that are set will be changed.
  553. // produces:
  554. // - application/json
  555. // parameters:
  556. // - name: owner
  557. // in: path
  558. // description: owner of the repo to edit
  559. // type: string
  560. // required: true
  561. // - name: repo
  562. // in: path
  563. // description: name of the repo to edit
  564. // type: string
  565. // required: true
  566. // - name: body
  567. // in: body
  568. // description: "Properties of a repo that you can edit"
  569. // schema:
  570. // "$ref": "#/definitions/EditRepoOption"
  571. // responses:
  572. // "200":
  573. // "$ref": "#/responses/Repository"
  574. // "403":
  575. // "$ref": "#/responses/forbidden"
  576. // "404":
  577. // "$ref": "#/responses/notFound"
  578. // "422":
  579. // "$ref": "#/responses/validationError"
  580. opts := *web.GetForm(ctx).(*api.EditRepoOption)
  581. if err := updateBasicProperties(ctx, opts); err != nil {
  582. return
  583. }
  584. if err := updateRepoUnits(ctx, opts); err != nil {
  585. return
  586. }
  587. if opts.Archived != nil {
  588. if err := updateRepoArchivedState(ctx, opts); err != nil {
  589. return
  590. }
  591. }
  592. if opts.MirrorInterval != nil {
  593. if err := updateMirror(ctx, opts); err != nil {
  594. return
  595. }
  596. }
  597. repo, err := repo_model.GetRepositoryByID(ctx, ctx.Repo.Repository.ID)
  598. if err != nil {
  599. ctx.InternalServerError(err)
  600. return
  601. }
  602. ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, ctx.Repo.Permission))
  603. }
  604. // updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility
  605. func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) error {
  606. owner := ctx.Repo.Owner
  607. repo := ctx.Repo.Repository
  608. newRepoName := repo.Name
  609. if opts.Name != nil {
  610. newRepoName = *opts.Name
  611. }
  612. // Check if repository name has been changed and not just a case change
  613. if repo.LowerName != strings.ToLower(newRepoName) {
  614. if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil {
  615. switch {
  616. case repo_model.IsErrRepoAlreadyExist(err):
  617. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err)
  618. case db.IsErrNameReserved(err):
  619. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is reserved [name: %s]", newRepoName), err)
  620. case db.IsErrNamePatternNotAllowed(err):
  621. ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name's pattern is not allowed [name: %s, pattern: %s]", newRepoName, err.(db.ErrNamePatternNotAllowed).Pattern), err)
  622. default:
  623. ctx.Error(http.StatusUnprocessableEntity, "ChangeRepositoryName", err)
  624. }
  625. return err
  626. }
  627. log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
  628. }
  629. // Update the name in the repo object for the response
  630. repo.Name = newRepoName
  631. repo.LowerName = strings.ToLower(newRepoName)
  632. if opts.Description != nil {
  633. repo.Description = *opts.Description
  634. }
  635. if opts.Website != nil {
  636. repo.Website = *opts.Website
  637. }
  638. visibilityChanged := false
  639. if opts.Private != nil {
  640. // Visibility of forked repository is forced sync with base repository.
  641. if repo.IsFork {
  642. if err := repo.GetBaseRepo(ctx); err != nil {
  643. ctx.Error(http.StatusInternalServerError, "Unable to load base repository", err)
  644. return err
  645. }
  646. *opts.Private = repo.BaseRepo.IsPrivate
  647. }
  648. visibilityChanged = repo.IsPrivate != *opts.Private
  649. // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
  650. if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.Doer.IsAdmin {
  651. err := fmt.Errorf("cannot change private repository to public")
  652. ctx.Error(http.StatusUnprocessableEntity, "Force Private enabled", err)
  653. return err
  654. }
  655. repo.IsPrivate = *opts.Private
  656. }
  657. if opts.Template != nil {
  658. repo.IsTemplate = *opts.Template
  659. }
  660. if ctx.Repo.GitRepo == nil && !repo.IsEmpty {
  661. var err error
  662. ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath())
  663. if err != nil {
  664. ctx.Error(http.StatusInternalServerError, "Unable to OpenRepository", err)
  665. return err
  666. }
  667. defer ctx.Repo.GitRepo.Close()
  668. }
  669. // Default branch only updated if changed and exist or the repository is empty
  670. if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) {
  671. if !repo.IsEmpty {
  672. if err := ctx.Repo.GitRepo.SetDefaultBranch(*opts.DefaultBranch); err != nil {
  673. if !git.IsErrUnsupportedVersion(err) {
  674. ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
  675. return err
  676. }
  677. }
  678. }
  679. repo.DefaultBranch = *opts.DefaultBranch
  680. }
  681. if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil {
  682. ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
  683. return err
  684. }
  685. log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
  686. return nil
  687. }
  688. // updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings
  689. func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
  690. owner := ctx.Repo.Owner
  691. repo := ctx.Repo.Repository
  692. var units []repo_model.RepoUnit
  693. var deleteUnitTypes []unit_model.Type
  694. currHasIssues := repo.UnitEnabled(ctx, unit_model.TypeIssues)
  695. newHasIssues := currHasIssues
  696. if opts.HasIssues != nil {
  697. newHasIssues = *opts.HasIssues
  698. }
  699. if currHasIssues || newHasIssues {
  700. if newHasIssues && opts.ExternalTracker != nil && !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
  701. // Check that values are valid
  702. if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
  703. err := fmt.Errorf("External tracker URL not valid")
  704. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL", err)
  705. return err
  706. }
  707. if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) {
  708. err := fmt.Errorf("External tracker URL format not valid")
  709. ctx.Error(http.StatusUnprocessableEntity, "Invalid external tracker URL format", err)
  710. return err
  711. }
  712. units = append(units, repo_model.RepoUnit{
  713. RepoID: repo.ID,
  714. Type: unit_model.TypeExternalTracker,
  715. Config: &repo_model.ExternalTrackerConfig{
  716. ExternalTrackerURL: opts.ExternalTracker.ExternalTrackerURL,
  717. ExternalTrackerFormat: opts.ExternalTracker.ExternalTrackerFormat,
  718. ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle,
  719. ExternalTrackerRegexpPattern: opts.ExternalTracker.ExternalTrackerRegexpPattern,
  720. },
  721. })
  722. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
  723. } else if newHasIssues && opts.ExternalTracker == nil && !unit_model.TypeIssues.UnitGlobalDisabled() {
  724. // Default to built-in tracker
  725. var config *repo_model.IssuesConfig
  726. if opts.InternalTracker != nil {
  727. config = &repo_model.IssuesConfig{
  728. EnableTimetracker: opts.InternalTracker.EnableTimeTracker,
  729. AllowOnlyContributorsToTrackTime: opts.InternalTracker.AllowOnlyContributorsToTrackTime,
  730. EnableDependencies: opts.InternalTracker.EnableIssueDependencies,
  731. }
  732. } else if unit, err := repo.GetUnit(ctx, unit_model.TypeIssues); err != nil {
  733. // Unit type doesn't exist so we make a new config file with default values
  734. config = &repo_model.IssuesConfig{
  735. EnableTimetracker: true,
  736. AllowOnlyContributorsToTrackTime: true,
  737. EnableDependencies: true,
  738. }
  739. } else {
  740. config = unit.IssuesConfig()
  741. }
  742. units = append(units, repo_model.RepoUnit{
  743. RepoID: repo.ID,
  744. Type: unit_model.TypeIssues,
  745. Config: config,
  746. })
  747. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
  748. } else if !newHasIssues {
  749. if !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
  750. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
  751. }
  752. if !unit_model.TypeIssues.UnitGlobalDisabled() {
  753. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
  754. }
  755. }
  756. }
  757. currHasWiki := repo.UnitEnabled(ctx, unit_model.TypeWiki)
  758. newHasWiki := currHasWiki
  759. if opts.HasWiki != nil {
  760. newHasWiki = *opts.HasWiki
  761. }
  762. if currHasWiki || newHasWiki {
  763. if newHasWiki && opts.ExternalWiki != nil && !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
  764. // Check that values are valid
  765. if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
  766. err := fmt.Errorf("External wiki URL not valid")
  767. ctx.Error(http.StatusUnprocessableEntity, "", "Invalid external wiki URL")
  768. return err
  769. }
  770. units = append(units, repo_model.RepoUnit{
  771. RepoID: repo.ID,
  772. Type: unit_model.TypeExternalWiki,
  773. Config: &repo_model.ExternalWikiConfig{
  774. ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL,
  775. },
  776. })
  777. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
  778. } else if newHasWiki && opts.ExternalWiki == nil && !unit_model.TypeWiki.UnitGlobalDisabled() {
  779. config := &repo_model.UnitConfig{}
  780. units = append(units, repo_model.RepoUnit{
  781. RepoID: repo.ID,
  782. Type: unit_model.TypeWiki,
  783. Config: config,
  784. })
  785. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
  786. } else if !newHasWiki {
  787. if !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
  788. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
  789. }
  790. if !unit_model.TypeWiki.UnitGlobalDisabled() {
  791. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
  792. }
  793. }
  794. }
  795. currHasPullRequests := repo.UnitEnabled(ctx, unit_model.TypePullRequests)
  796. newHasPullRequests := currHasPullRequests
  797. if opts.HasPullRequests != nil {
  798. newHasPullRequests = *opts.HasPullRequests
  799. }
  800. if currHasPullRequests || newHasPullRequests {
  801. if newHasPullRequests && !unit_model.TypePullRequests.UnitGlobalDisabled() {
  802. // We do allow setting individual PR settings through the API, so
  803. // we get the config settings and then set them
  804. // if those settings were provided in the opts.
  805. unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests)
  806. var config *repo_model.PullRequestsConfig
  807. if err != nil {
  808. // Unit type doesn't exist so we make a new config file with default values
  809. config = &repo_model.PullRequestsConfig{
  810. IgnoreWhitespaceConflicts: false,
  811. AllowMerge: true,
  812. AllowRebase: true,
  813. AllowRebaseMerge: true,
  814. AllowSquash: true,
  815. AllowManualMerge: true,
  816. AutodetectManualMerge: false,
  817. AllowRebaseUpdate: true,
  818. DefaultDeleteBranchAfterMerge: false,
  819. DefaultMergeStyle: repo_model.MergeStyleMerge,
  820. DefaultAllowMaintainerEdit: false,
  821. }
  822. } else {
  823. config = unit.PullRequestsConfig()
  824. }
  825. if opts.IgnoreWhitespaceConflicts != nil {
  826. config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
  827. }
  828. if opts.AllowMerge != nil {
  829. config.AllowMerge = *opts.AllowMerge
  830. }
  831. if opts.AllowRebase != nil {
  832. config.AllowRebase = *opts.AllowRebase
  833. }
  834. if opts.AllowRebaseMerge != nil {
  835. config.AllowRebaseMerge = *opts.AllowRebaseMerge
  836. }
  837. if opts.AllowSquash != nil {
  838. config.AllowSquash = *opts.AllowSquash
  839. }
  840. if opts.AllowManualMerge != nil {
  841. config.AllowManualMerge = *opts.AllowManualMerge
  842. }
  843. if opts.AutodetectManualMerge != nil {
  844. config.AutodetectManualMerge = *opts.AutodetectManualMerge
  845. }
  846. if opts.AllowRebaseUpdate != nil {
  847. config.AllowRebaseUpdate = *opts.AllowRebaseUpdate
  848. }
  849. if opts.DefaultDeleteBranchAfterMerge != nil {
  850. config.DefaultDeleteBranchAfterMerge = *opts.DefaultDeleteBranchAfterMerge
  851. }
  852. if opts.DefaultMergeStyle != nil {
  853. config.DefaultMergeStyle = repo_model.MergeStyle(*opts.DefaultMergeStyle)
  854. }
  855. if opts.DefaultAllowMaintainerEdit != nil {
  856. config.DefaultAllowMaintainerEdit = *opts.DefaultAllowMaintainerEdit
  857. }
  858. units = append(units, repo_model.RepoUnit{
  859. RepoID: repo.ID,
  860. Type: unit_model.TypePullRequests,
  861. Config: config,
  862. })
  863. } else if !newHasPullRequests && !unit_model.TypePullRequests.UnitGlobalDisabled() {
  864. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
  865. }
  866. }
  867. if opts.HasProjects != nil && !unit_model.TypeProjects.UnitGlobalDisabled() {
  868. if *opts.HasProjects {
  869. units = append(units, repo_model.RepoUnit{
  870. RepoID: repo.ID,
  871. Type: unit_model.TypeProjects,
  872. })
  873. } else {
  874. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
  875. }
  876. }
  877. if opts.HasReleases != nil && !unit_model.TypeReleases.UnitGlobalDisabled() {
  878. if *opts.HasReleases {
  879. units = append(units, repo_model.RepoUnit{
  880. RepoID: repo.ID,
  881. Type: unit_model.TypeReleases,
  882. })
  883. } else {
  884. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases)
  885. }
  886. }
  887. if opts.HasPackages != nil && !unit_model.TypePackages.UnitGlobalDisabled() {
  888. if *opts.HasPackages {
  889. units = append(units, repo_model.RepoUnit{
  890. RepoID: repo.ID,
  891. Type: unit_model.TypePackages,
  892. })
  893. } else {
  894. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
  895. }
  896. }
  897. if opts.HasActions != nil && !unit_model.TypeActions.UnitGlobalDisabled() {
  898. if *opts.HasActions {
  899. units = append(units, repo_model.RepoUnit{
  900. RepoID: repo.ID,
  901. Type: unit_model.TypeActions,
  902. })
  903. } else {
  904. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
  905. }
  906. }
  907. if len(units)+len(deleteUnitTypes) > 0 {
  908. if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
  909. ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
  910. return err
  911. }
  912. }
  913. log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name)
  914. return nil
  915. }
  916. // updateRepoArchivedState updates repo's archive state
  917. func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) error {
  918. repo := ctx.Repo.Repository
  919. // archive / un-archive
  920. if opts.Archived != nil {
  921. if repo.IsMirror {
  922. err := fmt.Errorf("repo is a mirror, cannot archive/un-archive")
  923. ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
  924. return err
  925. }
  926. if *opts.Archived {
  927. if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {
  928. log.Error("Tried to archive a repo: %s", err)
  929. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  930. return err
  931. }
  932. log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  933. } else {
  934. if err := repo_model.SetArchiveRepoState(ctx, repo, *opts.Archived); err != nil {
  935. log.Error("Tried to un-archive a repo: %s", err)
  936. ctx.Error(http.StatusInternalServerError, "ArchiveRepoState", err)
  937. return err
  938. }
  939. log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  940. }
  941. }
  942. return nil
  943. }
  944. // updateMirror updates a repo's mirror Interval and EnablePrune
  945. func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error {
  946. repo := ctx.Repo.Repository
  947. // only update mirror if interval or enable prune are provided
  948. if opts.MirrorInterval == nil && opts.EnablePrune == nil {
  949. return nil
  950. }
  951. // these values only make sense if the repo is a mirror
  952. if !repo.IsMirror {
  953. err := fmt.Errorf("repo is not a mirror, can not change mirror interval")
  954. ctx.Error(http.StatusUnprocessableEntity, err.Error(), err)
  955. return err
  956. }
  957. // get the mirror from the repo
  958. mirror, err := repo_model.GetMirrorByRepoID(ctx, repo.ID)
  959. if err != nil {
  960. log.Error("Failed to get mirror: %s", err)
  961. ctx.Error(http.StatusInternalServerError, "MirrorInterval", err)
  962. return err
  963. }
  964. // update MirrorInterval
  965. if opts.MirrorInterval != nil {
  966. // MirrorInterval should be a duration
  967. interval, err := time.ParseDuration(*opts.MirrorInterval)
  968. if err != nil {
  969. log.Error("Wrong format for MirrorInternal Sent: %s", err)
  970. ctx.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
  971. return err
  972. }
  973. // Ensure the provided duration is not too short
  974. if interval != 0 && interval < setting.Mirror.MinInterval {
  975. err := fmt.Errorf("invalid mirror interval: %s is below minimum interval: %s", interval, setting.Mirror.MinInterval)
  976. ctx.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
  977. return err
  978. }
  979. mirror.Interval = interval
  980. mirror.Repo = repo
  981. mirror.ScheduleNextUpdate()
  982. log.Trace("Repository %s Mirror[%d] Set Interval: %s NextUpdateUnix: %s", repo.FullName(), mirror.ID, interval, mirror.NextUpdateUnix)
  983. }
  984. // update EnablePrune
  985. if opts.EnablePrune != nil {
  986. mirror.EnablePrune = *opts.EnablePrune
  987. log.Trace("Repository %s Mirror[%d] Set EnablePrune: %t", repo.FullName(), mirror.ID, mirror.EnablePrune)
  988. }
  989. // finally update the mirror in the DB
  990. if err := repo_model.UpdateMirror(ctx, mirror); err != nil {
  991. log.Error("Failed to Set Mirror Interval: %s", err)
  992. ctx.Error(http.StatusUnprocessableEntity, "MirrorInterval", err)
  993. return err
  994. }
  995. return nil
  996. }
  997. // Delete one repository
  998. func Delete(ctx *context.APIContext) {
  999. // swagger:operation DELETE /repos/{owner}/{repo} repository repoDelete
  1000. // ---
  1001. // summary: Delete a repository
  1002. // produces:
  1003. // - application/json
  1004. // parameters:
  1005. // - name: owner
  1006. // in: path
  1007. // description: owner of the repo to delete
  1008. // type: string
  1009. // required: true
  1010. // - name: repo
  1011. // in: path
  1012. // description: name of the repo to delete
  1013. // type: string
  1014. // required: true
  1015. // responses:
  1016. // "204":
  1017. // "$ref": "#/responses/empty"
  1018. // "403":
  1019. // "$ref": "#/responses/forbidden"
  1020. // "404":
  1021. // "$ref": "#/responses/notFound"
  1022. owner := ctx.Repo.Owner
  1023. repo := ctx.Repo.Repository
  1024. canDelete, err := repo_module.CanUserDelete(ctx, repo, ctx.Doer)
  1025. if err != nil {
  1026. ctx.Error(http.StatusInternalServerError, "CanUserDelete", err)
  1027. return
  1028. } else if !canDelete {
  1029. ctx.Error(http.StatusForbidden, "", "Given user is not owner of organization.")
  1030. return
  1031. }
  1032. if ctx.Repo.GitRepo != nil {
  1033. ctx.Repo.GitRepo.Close()
  1034. }
  1035. if err := repo_service.DeleteRepository(ctx, ctx.Doer, repo, true); err != nil {
  1036. ctx.Error(http.StatusInternalServerError, "DeleteRepository", err)
  1037. return
  1038. }
  1039. log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
  1040. ctx.Status(http.StatusNoContent)
  1041. }
  1042. // GetIssueTemplates returns the issue templates for a repository
  1043. func GetIssueTemplates(ctx *context.APIContext) {
  1044. // swagger:operation GET /repos/{owner}/{repo}/issue_templates repository repoGetIssueTemplates
  1045. // ---
  1046. // summary: Get available issue templates for a repository
  1047. // produces:
  1048. // - application/json
  1049. // parameters:
  1050. // - name: owner
  1051. // in: path
  1052. // description: owner of the repo
  1053. // type: string
  1054. // required: true
  1055. // - name: repo
  1056. // in: path
  1057. // description: name of the repo
  1058. // type: string
  1059. // required: true
  1060. // responses:
  1061. // "200":
  1062. // "$ref": "#/responses/IssueTemplates"
  1063. // "404":
  1064. // "$ref": "#/responses/notFound"
  1065. ret, err := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
  1066. if err != nil {
  1067. ctx.Error(http.StatusInternalServerError, "GetTemplatesFromDefaultBranch", err)
  1068. return
  1069. }
  1070. ctx.JSON(http.StatusOK, ret)
  1071. }
  1072. // GetIssueConfig returns the issue config for a repo
  1073. func GetIssueConfig(ctx *context.APIContext) {
  1074. // swagger:operation GET /repos/{owner}/{repo}/issue_config repository repoGetIssueConfig
  1075. // ---
  1076. // summary: Returns the issue config for a repo
  1077. // produces:
  1078. // - application/json
  1079. // parameters:
  1080. // - name: owner
  1081. // in: path
  1082. // description: owner of the repo
  1083. // type: string
  1084. // required: true
  1085. // - name: repo
  1086. // in: path
  1087. // description: name of the repo
  1088. // type: string
  1089. // required: true
  1090. // responses:
  1091. // "200":
  1092. // "$ref": "#/responses/RepoIssueConfig"
  1093. // "404":
  1094. // "$ref": "#/responses/notFound"
  1095. issueConfig, _ := issue.GetTemplateConfigFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
  1096. ctx.JSON(http.StatusOK, issueConfig)
  1097. }
  1098. // ValidateIssueConfig returns validation errors for the issue config
  1099. func ValidateIssueConfig(ctx *context.APIContext) {
  1100. // swagger:operation GET /repos/{owner}/{repo}/issue_config/validate repository repoValidateIssueConfig
  1101. // ---
  1102. // summary: Returns the validation information for a issue config
  1103. // produces:
  1104. // - application/json
  1105. // parameters:
  1106. // - name: owner
  1107. // in: path
  1108. // description: owner of the repo
  1109. // type: string
  1110. // required: true
  1111. // - name: repo
  1112. // in: path
  1113. // description: name of the repo
  1114. // type: string
  1115. // required: true
  1116. // responses:
  1117. // "200":
  1118. // "$ref": "#/responses/RepoIssueConfigValidation"
  1119. // "404":
  1120. // "$ref": "#/responses/notFound"
  1121. _, err := issue.GetTemplateConfigFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
  1122. if err == nil {
  1123. ctx.JSON(http.StatusOK, api.IssueConfigValidation{Valid: true, Message: ""})
  1124. } else {
  1125. ctx.JSON(http.StatusOK, api.IssueConfigValidation{Valid: false, Message: err.Error()})
  1126. }
  1127. }
  1128. func ListRepoActivityFeeds(ctx *context.APIContext) {
  1129. // swagger:operation GET /repos/{owner}/{repo}/activities/feeds repository repoListActivityFeeds
  1130. // ---
  1131. // summary: List a repository's activity feeds
  1132. // produces:
  1133. // - application/json
  1134. // parameters:
  1135. // - name: owner
  1136. // in: path
  1137. // description: owner of the repo
  1138. // type: string
  1139. // required: true
  1140. // - name: repo
  1141. // in: path
  1142. // description: name of the repo
  1143. // type: string
  1144. // required: true
  1145. // - name: date
  1146. // in: query
  1147. // description: the date of the activities to be found
  1148. // type: string
  1149. // format: date
  1150. // - name: page
  1151. // in: query
  1152. // description: page number of results to return (1-based)
  1153. // type: integer
  1154. // - name: limit
  1155. // in: query
  1156. // description: page size of results
  1157. // type: integer
  1158. // responses:
  1159. // "200":
  1160. // "$ref": "#/responses/ActivityFeedsList"
  1161. // "404":
  1162. // "$ref": "#/responses/notFound"
  1163. listOptions := utils.GetListOptions(ctx)
  1164. opts := activities_model.GetFeedsOptions{
  1165. RequestedRepo: ctx.Repo.Repository,
  1166. Actor: ctx.Doer,
  1167. IncludePrivate: true,
  1168. Date: ctx.FormString("date"),
  1169. ListOptions: listOptions,
  1170. }
  1171. feeds, count, err := activities_model.GetFeeds(ctx, opts)
  1172. if err != nil {
  1173. ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
  1174. return
  1175. }
  1176. ctx.SetTotalCountHeader(count)
  1177. ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
  1178. }