gitea源码

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "errors"
  7. "net/http"
  8. "code.gitea.io/gitea/models/db"
  9. git_model "code.gitea.io/gitea/models/git"
  10. "code.gitea.io/gitea/models/organization"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. user_model "code.gitea.io/gitea/models/user"
  13. "code.gitea.io/gitea/modules/git"
  14. "code.gitea.io/gitea/modules/gitrepo"
  15. "code.gitea.io/gitea/modules/optional"
  16. repo_module "code.gitea.io/gitea/modules/repository"
  17. api "code.gitea.io/gitea/modules/structs"
  18. "code.gitea.io/gitea/modules/util"
  19. "code.gitea.io/gitea/modules/web"
  20. "code.gitea.io/gitea/routers/api/v1/utils"
  21. "code.gitea.io/gitea/services/context"
  22. "code.gitea.io/gitea/services/convert"
  23. pull_service "code.gitea.io/gitea/services/pull"
  24. release_service "code.gitea.io/gitea/services/release"
  25. repo_service "code.gitea.io/gitea/services/repository"
  26. )
  27. // GetBranch get a branch of a repository
  28. func GetBranch(ctx *context.APIContext) {
  29. // swagger:operation GET /repos/{owner}/{repo}/branches/{branch} repository repoGetBranch
  30. // ---
  31. // summary: Retrieve a specific branch from a repository, including its effective branch protection
  32. // produces:
  33. // - application/json
  34. // parameters:
  35. // - name: owner
  36. // in: path
  37. // description: owner of the repo
  38. // type: string
  39. // required: true
  40. // - name: repo
  41. // in: path
  42. // description: name of the repo
  43. // type: string
  44. // required: true
  45. // - name: branch
  46. // in: path
  47. // description: branch to get
  48. // type: string
  49. // required: true
  50. // responses:
  51. // "200":
  52. // "$ref": "#/responses/Branch"
  53. // "404":
  54. // "$ref": "#/responses/notFound"
  55. branchName := ctx.PathParam("*")
  56. exist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, branchName)
  57. if err != nil {
  58. ctx.APIErrorInternal(err)
  59. return
  60. } else if !exist {
  61. ctx.APIErrorNotFound(err)
  62. return
  63. }
  64. c, err := ctx.Repo.GitRepo.GetBranchCommit(branchName)
  65. if err != nil {
  66. ctx.APIErrorInternal(err)
  67. return
  68. }
  69. branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName)
  70. if err != nil {
  71. ctx.APIErrorInternal(err)
  72. return
  73. }
  74. br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branchName, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
  75. if err != nil {
  76. ctx.APIErrorInternal(err)
  77. return
  78. }
  79. ctx.JSON(http.StatusOK, br)
  80. }
  81. // DeleteBranch get a branch of a repository
  82. func DeleteBranch(ctx *context.APIContext) {
  83. // swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch} repository repoDeleteBranch
  84. // ---
  85. // summary: Delete a specific branch from a repository
  86. // produces:
  87. // - application/json
  88. // parameters:
  89. // - name: owner
  90. // in: path
  91. // description: owner of the repo
  92. // type: string
  93. // required: true
  94. // - name: repo
  95. // in: path
  96. // description: name of the repo
  97. // type: string
  98. // required: true
  99. // - name: branch
  100. // in: path
  101. // description: branch to delete
  102. // type: string
  103. // required: true
  104. // responses:
  105. // "204":
  106. // "$ref": "#/responses/empty"
  107. // "403":
  108. // "$ref": "#/responses/error"
  109. // "404":
  110. // "$ref": "#/responses/notFound"
  111. // "423":
  112. // "$ref": "#/responses/repoArchivedError"
  113. if ctx.Repo.Repository.IsEmpty {
  114. ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
  115. return
  116. }
  117. if ctx.Repo.Repository.IsMirror {
  118. ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
  119. return
  120. }
  121. branchName := ctx.PathParam("*")
  122. // check whether branches of this repository has been synced
  123. totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
  124. RepoID: ctx.Repo.Repository.ID,
  125. IsDeletedBranch: optional.Some(false),
  126. })
  127. if err != nil {
  128. ctx.APIErrorInternal(err)
  129. return
  130. }
  131. if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
  132. _, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
  133. if err != nil {
  134. ctx.APIErrorInternal(err)
  135. return
  136. }
  137. }
  138. if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil {
  139. switch {
  140. case git.IsErrBranchNotExist(err):
  141. ctx.APIErrorNotFound(err)
  142. case errors.Is(err, repo_service.ErrBranchIsDefault):
  143. ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch"))
  144. case errors.Is(err, git_model.ErrBranchIsProtected):
  145. ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
  146. default:
  147. ctx.APIErrorInternal(err)
  148. }
  149. return
  150. }
  151. ctx.Status(http.StatusNoContent)
  152. }
  153. // CreateBranch creates a branch for a user's repository
  154. func CreateBranch(ctx *context.APIContext) {
  155. // swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
  156. // ---
  157. // summary: Create a branch
  158. // consumes:
  159. // - application/json
  160. // produces:
  161. // - application/json
  162. // parameters:
  163. // - name: owner
  164. // in: path
  165. // description: owner of the repo
  166. // type: string
  167. // required: true
  168. // - name: repo
  169. // in: path
  170. // description: name of the repo
  171. // type: string
  172. // required: true
  173. // - name: body
  174. // in: body
  175. // schema:
  176. // "$ref": "#/definitions/CreateBranchRepoOption"
  177. // responses:
  178. // "201":
  179. // "$ref": "#/responses/Branch"
  180. // "403":
  181. // description: The branch is archived or a mirror.
  182. // "404":
  183. // description: The old branch does not exist.
  184. // "409":
  185. // description: The branch with the same name already exists.
  186. // "423":
  187. // "$ref": "#/responses/repoArchivedError"
  188. if ctx.Repo.Repository.IsEmpty {
  189. ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
  190. return
  191. }
  192. if ctx.Repo.Repository.IsMirror {
  193. ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
  194. return
  195. }
  196. opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
  197. var oldCommit *git.Commit
  198. var err error
  199. if len(opt.OldRefName) > 0 {
  200. oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName)
  201. if err != nil {
  202. ctx.APIErrorInternal(err)
  203. return
  204. }
  205. } else if len(opt.OldBranchName) > 0 { //nolint:staticcheck // deprecated field
  206. if gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, opt.OldBranchName) { //nolint:staticcheck // deprecated field
  207. oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint:staticcheck // deprecated field
  208. if err != nil {
  209. ctx.APIErrorInternal(err)
  210. return
  211. }
  212. } else {
  213. ctx.APIError(http.StatusNotFound, "The old branch does not exist")
  214. return
  215. }
  216. } else {
  217. oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
  218. if err != nil {
  219. ctx.APIErrorInternal(err)
  220. return
  221. }
  222. }
  223. err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
  224. if err != nil {
  225. if git_model.IsErrBranchNotExist(err) {
  226. ctx.APIError(http.StatusNotFound, "The old branch does not exist")
  227. } else if release_service.IsErrTagAlreadyExists(err) {
  228. ctx.APIError(http.StatusConflict, "The branch with the same tag already exists.")
  229. } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
  230. ctx.APIError(http.StatusConflict, "The branch already exists.")
  231. } else if git_model.IsErrBranchNameConflict(err) {
  232. ctx.APIError(http.StatusConflict, "The branch with the same name already exists.")
  233. } else {
  234. ctx.APIErrorInternal(err)
  235. }
  236. return
  237. }
  238. commit, err := ctx.Repo.GitRepo.GetBranchCommit(opt.BranchName)
  239. if err != nil {
  240. ctx.APIErrorInternal(err)
  241. return
  242. }
  243. branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, opt.BranchName)
  244. if err != nil {
  245. ctx.APIErrorInternal(err)
  246. return
  247. }
  248. br, err := convert.ToBranch(ctx, ctx.Repo.Repository, opt.BranchName, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
  249. if err != nil {
  250. ctx.APIErrorInternal(err)
  251. return
  252. }
  253. ctx.JSON(http.StatusCreated, br)
  254. }
  255. // ListBranches list all the branches of a repository
  256. func ListBranches(ctx *context.APIContext) {
  257. // swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches
  258. // ---
  259. // summary: List a repository's branches
  260. // produces:
  261. // - application/json
  262. // parameters:
  263. // - name: owner
  264. // in: path
  265. // description: owner of the repo
  266. // type: string
  267. // required: true
  268. // - name: repo
  269. // in: path
  270. // description: name of the repo
  271. // type: string
  272. // required: true
  273. // - name: page
  274. // in: query
  275. // description: page number of results to return (1-based)
  276. // type: integer
  277. // - name: limit
  278. // in: query
  279. // description: page size of results
  280. // type: integer
  281. // responses:
  282. // "200":
  283. // "$ref": "#/responses/BranchList"
  284. var totalNumOfBranches int64
  285. var apiBranches []*api.Branch
  286. listOptions := utils.GetListOptions(ctx)
  287. if !ctx.Repo.Repository.IsEmpty {
  288. if ctx.Repo.GitRepo == nil {
  289. ctx.APIErrorInternal(nil)
  290. return
  291. }
  292. branchOpts := git_model.FindBranchOptions{
  293. ListOptions: listOptions,
  294. RepoID: ctx.Repo.Repository.ID,
  295. IsDeletedBranch: optional.Some(false),
  296. }
  297. var err error
  298. totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
  299. if err != nil {
  300. ctx.APIErrorInternal(err)
  301. return
  302. }
  303. if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
  304. totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
  305. if err != nil {
  306. ctx.APIErrorInternal(err)
  307. return
  308. }
  309. }
  310. rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
  311. if err != nil {
  312. ctx.APIErrorInternal(err)
  313. return
  314. }
  315. branches, err := db.Find[git_model.Branch](ctx, branchOpts)
  316. if err != nil {
  317. ctx.APIErrorInternal(err)
  318. return
  319. }
  320. apiBranches = make([]*api.Branch, 0, len(branches))
  321. for i := range branches {
  322. c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
  323. if err != nil {
  324. // Skip if this branch doesn't exist anymore.
  325. if git.IsErrNotExist(err) {
  326. totalNumOfBranches--
  327. continue
  328. }
  329. ctx.APIErrorInternal(err)
  330. return
  331. }
  332. branchProtection := rules.GetFirstMatched(branches[i].Name)
  333. apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
  334. if err != nil {
  335. ctx.APIErrorInternal(err)
  336. return
  337. }
  338. apiBranches = append(apiBranches, apiBranch)
  339. }
  340. }
  341. ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
  342. ctx.SetTotalCountHeader(totalNumOfBranches)
  343. ctx.JSON(http.StatusOK, apiBranches)
  344. }
  345. // RenameBranch renames a repository's branch.
  346. func RenameBranch(ctx *context.APIContext) {
  347. // swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoRenameBranch
  348. // ---
  349. // summary: Rename a branch
  350. // consumes:
  351. // - application/json
  352. // produces:
  353. // - application/json
  354. // parameters:
  355. // - name: owner
  356. // in: path
  357. // description: owner of the repo
  358. // type: string
  359. // required: true
  360. // - name: repo
  361. // in: path
  362. // description: name of the repo
  363. // type: string
  364. // required: true
  365. // - name: branch
  366. // in: path
  367. // description: name of the branch
  368. // type: string
  369. // required: true
  370. // - name: body
  371. // in: body
  372. // schema:
  373. // "$ref": "#/definitions/RenameBranchRepoOption"
  374. // responses:
  375. // "204":
  376. // "$ref": "#/responses/empty"
  377. // "403":
  378. // "$ref": "#/responses/forbidden"
  379. // "404":
  380. // "$ref": "#/responses/notFound"
  381. // "422":
  382. // "$ref": "#/responses/validationError"
  383. opt := web.GetForm(ctx).(*api.RenameBranchRepoOption)
  384. oldName := ctx.PathParam("*")
  385. repo := ctx.Repo.Repository
  386. if repo.IsEmpty {
  387. ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
  388. return
  389. }
  390. if repo.IsMirror {
  391. ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
  392. return
  393. }
  394. msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, oldName, opt.Name)
  395. if err != nil {
  396. switch {
  397. case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
  398. ctx.APIError(http.StatusForbidden, "User must be a repo or site admin to rename default or protected branches.")
  399. case errors.Is(err, git_model.ErrBranchIsProtected):
  400. ctx.APIError(http.StatusForbidden, "Branch is protected by glob-based protection rules.")
  401. default:
  402. ctx.APIErrorInternal(err)
  403. }
  404. return
  405. }
  406. if msg == "target_exist" {
  407. ctx.APIError(http.StatusUnprocessableEntity, "Cannot rename a branch using the same name or rename to a branch that already exists.")
  408. return
  409. }
  410. if msg == "from_not_exist" {
  411. ctx.APIError(http.StatusNotFound, "Branch doesn't exist.")
  412. return
  413. }
  414. ctx.Status(http.StatusNoContent)
  415. }
  416. // GetBranchProtection gets a branch protection
  417. func GetBranchProtection(ctx *context.APIContext) {
  418. // swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
  419. // ---
  420. // summary: Get a specific branch protection for the repository
  421. // produces:
  422. // - application/json
  423. // parameters:
  424. // - name: owner
  425. // in: path
  426. // description: owner of the repo
  427. // type: string
  428. // required: true
  429. // - name: repo
  430. // in: path
  431. // description: name of the repo
  432. // type: string
  433. // required: true
  434. // - name: name
  435. // in: path
  436. // description: name of protected branch
  437. // type: string
  438. // required: true
  439. // responses:
  440. // "200":
  441. // "$ref": "#/responses/BranchProtection"
  442. // "404":
  443. // "$ref": "#/responses/notFound"
  444. repo := ctx.Repo.Repository
  445. bpName := ctx.PathParam("name")
  446. bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
  447. if err != nil {
  448. ctx.APIErrorInternal(err)
  449. return
  450. }
  451. if bp == nil || bp.RepoID != repo.ID {
  452. ctx.APIErrorNotFound()
  453. return
  454. }
  455. ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
  456. }
  457. // ListBranchProtections list branch protections for a repo
  458. func ListBranchProtections(ctx *context.APIContext) {
  459. // swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection
  460. // ---
  461. // summary: List branch protections for a repository
  462. // produces:
  463. // - application/json
  464. // parameters:
  465. // - name: owner
  466. // in: path
  467. // description: owner of the repo
  468. // type: string
  469. // required: true
  470. // - name: repo
  471. // in: path
  472. // description: name of the repo
  473. // type: string
  474. // required: true
  475. // responses:
  476. // "200":
  477. // "$ref": "#/responses/BranchProtectionList"
  478. repo := ctx.Repo.Repository
  479. bps, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
  480. if err != nil {
  481. ctx.APIErrorInternal(err)
  482. return
  483. }
  484. apiBps := make([]*api.BranchProtection, len(bps))
  485. for i := range bps {
  486. apiBps[i] = convert.ToBranchProtection(ctx, bps[i], repo)
  487. }
  488. ctx.JSON(http.StatusOK, apiBps)
  489. }
  490. // CreateBranchProtection creates a branch protection for a repo
  491. func CreateBranchProtection(ctx *context.APIContext) {
  492. // swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
  493. // ---
  494. // summary: Create a branch protections for a repository
  495. // consumes:
  496. // - application/json
  497. // produces:
  498. // - application/json
  499. // parameters:
  500. // - name: owner
  501. // in: path
  502. // description: owner of the repo
  503. // type: string
  504. // required: true
  505. // - name: repo
  506. // in: path
  507. // description: name of the repo
  508. // type: string
  509. // required: true
  510. // - name: body
  511. // in: body
  512. // schema:
  513. // "$ref": "#/definitions/CreateBranchProtectionOption"
  514. // responses:
  515. // "201":
  516. // "$ref": "#/responses/BranchProtection"
  517. // "403":
  518. // "$ref": "#/responses/forbidden"
  519. // "404":
  520. // "$ref": "#/responses/notFound"
  521. // "422":
  522. // "$ref": "#/responses/validationError"
  523. // "423":
  524. // "$ref": "#/responses/repoArchivedError"
  525. form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
  526. repo := ctx.Repo.Repository
  527. ruleName := form.RuleName
  528. if ruleName == "" {
  529. ruleName = form.BranchName //nolint:staticcheck // deprecated field
  530. }
  531. if len(ruleName) == 0 {
  532. ctx.APIError(http.StatusBadRequest, "both rule_name and branch_name are empty")
  533. return
  534. }
  535. protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, ruleName)
  536. if err != nil {
  537. ctx.APIErrorInternal(err)
  538. return
  539. } else if protectBranch != nil {
  540. ctx.APIError(http.StatusForbidden, "Branch protection already exist")
  541. return
  542. }
  543. var requiredApprovals int64
  544. if form.RequiredApprovals > 0 {
  545. requiredApprovals = form.RequiredApprovals
  546. }
  547. whitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
  548. if err != nil {
  549. if user_model.IsErrUserNotExist(err) {
  550. ctx.APIError(http.StatusUnprocessableEntity, err)
  551. return
  552. }
  553. ctx.APIErrorInternal(err)
  554. return
  555. }
  556. forcePushAllowlistUsers, err := user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
  557. if err != nil {
  558. if user_model.IsErrUserNotExist(err) {
  559. ctx.APIError(http.StatusUnprocessableEntity, err)
  560. return
  561. }
  562. ctx.APIErrorInternal(err)
  563. return
  564. }
  565. mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
  566. if err != nil {
  567. if user_model.IsErrUserNotExist(err) {
  568. ctx.APIError(http.StatusUnprocessableEntity, err)
  569. return
  570. }
  571. ctx.APIErrorInternal(err)
  572. return
  573. }
  574. approvalsWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
  575. if err != nil {
  576. if user_model.IsErrUserNotExist(err) {
  577. ctx.APIError(http.StatusUnprocessableEntity, err)
  578. return
  579. }
  580. ctx.APIErrorInternal(err)
  581. return
  582. }
  583. var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  584. if repo.Owner.IsOrganization() {
  585. whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
  586. if err != nil {
  587. if organization.IsErrTeamNotExist(err) {
  588. ctx.APIError(http.StatusUnprocessableEntity, err)
  589. return
  590. }
  591. ctx.APIErrorInternal(err)
  592. return
  593. }
  594. forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false)
  595. if err != nil {
  596. if organization.IsErrTeamNotExist(err) {
  597. ctx.APIError(http.StatusUnprocessableEntity, err)
  598. return
  599. }
  600. ctx.APIErrorInternal(err)
  601. return
  602. }
  603. mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
  604. if err != nil {
  605. if organization.IsErrTeamNotExist(err) {
  606. ctx.APIError(http.StatusUnprocessableEntity, err)
  607. return
  608. }
  609. ctx.APIErrorInternal(err)
  610. return
  611. }
  612. approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  613. if err != nil {
  614. if organization.IsErrTeamNotExist(err) {
  615. ctx.APIError(http.StatusUnprocessableEntity, err)
  616. return
  617. }
  618. ctx.APIErrorInternal(err)
  619. return
  620. }
  621. }
  622. protectBranch = &git_model.ProtectedBranch{
  623. RepoID: ctx.Repo.Repository.ID,
  624. RuleName: ruleName,
  625. Priority: form.Priority,
  626. CanPush: form.EnablePush,
  627. EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
  628. WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
  629. CanForcePush: form.EnablePush && form.EnableForcePush,
  630. EnableForcePushAllowlist: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist,
  631. ForcePushAllowlistDeployKeys: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist && form.ForcePushAllowlistDeployKeys,
  632. EnableMergeWhitelist: form.EnableMergeWhitelist,
  633. EnableStatusCheck: form.EnableStatusCheck,
  634. StatusCheckContexts: form.StatusCheckContexts,
  635. EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
  636. RequiredApprovals: requiredApprovals,
  637. BlockOnRejectedReviews: form.BlockOnRejectedReviews,
  638. BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
  639. DismissStaleApprovals: form.DismissStaleApprovals,
  640. IgnoreStaleApprovals: form.IgnoreStaleApprovals,
  641. RequireSignedCommits: form.RequireSignedCommits,
  642. ProtectedFilePatterns: form.ProtectedFilePatterns,
  643. UnprotectedFilePatterns: form.UnprotectedFilePatterns,
  644. BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
  645. BlockAdminMergeOverride: form.BlockAdminMergeOverride,
  646. }
  647. if err := pull_service.CreateOrUpdateProtectedBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
  648. UserIDs: whitelistUsers,
  649. TeamIDs: whitelistTeams,
  650. ForcePushUserIDs: forcePushAllowlistUsers,
  651. ForcePushTeamIDs: forcePushAllowlistTeams,
  652. MergeUserIDs: mergeWhitelistUsers,
  653. MergeTeamIDs: mergeWhitelistTeams,
  654. ApprovalsUserIDs: approvalsWhitelistUsers,
  655. ApprovalsTeamIDs: approvalsWhitelistTeams,
  656. }); err != nil {
  657. ctx.APIErrorInternal(err)
  658. return
  659. }
  660. // Reload from db to get all whitelists
  661. bp, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, ruleName)
  662. if err != nil {
  663. ctx.APIErrorInternal(err)
  664. return
  665. }
  666. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  667. ctx.APIErrorInternal(err)
  668. return
  669. }
  670. ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp, repo))
  671. }
  672. // EditBranchProtection edits a branch protection for a repo
  673. func EditBranchProtection(ctx *context.APIContext) {
  674. // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
  675. // ---
  676. // summary: Edit a branch protections for a repository. Only fields that are set will be changed
  677. // consumes:
  678. // - application/json
  679. // produces:
  680. // - application/json
  681. // parameters:
  682. // - name: owner
  683. // in: path
  684. // description: owner of the repo
  685. // type: string
  686. // required: true
  687. // - name: repo
  688. // in: path
  689. // description: name of the repo
  690. // type: string
  691. // required: true
  692. // - name: name
  693. // in: path
  694. // description: name of protected branch
  695. // type: string
  696. // required: true
  697. // - name: body
  698. // in: body
  699. // schema:
  700. // "$ref": "#/definitions/EditBranchProtectionOption"
  701. // responses:
  702. // "200":
  703. // "$ref": "#/responses/BranchProtection"
  704. // "404":
  705. // "$ref": "#/responses/notFound"
  706. // "422":
  707. // "$ref": "#/responses/validationError"
  708. // "423":
  709. // "$ref": "#/responses/repoArchivedError"
  710. form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
  711. repo := ctx.Repo.Repository
  712. bpName := ctx.PathParam("name")
  713. protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
  714. if err != nil {
  715. ctx.APIErrorInternal(err)
  716. return
  717. }
  718. if protectBranch == nil || protectBranch.RepoID != repo.ID {
  719. ctx.APIErrorNotFound()
  720. return
  721. }
  722. if form.EnablePush != nil {
  723. if !*form.EnablePush {
  724. protectBranch.CanPush = false
  725. protectBranch.EnableWhitelist = false
  726. protectBranch.WhitelistDeployKeys = false
  727. } else {
  728. protectBranch.CanPush = true
  729. if form.EnablePushWhitelist != nil {
  730. if !*form.EnablePushWhitelist {
  731. protectBranch.EnableWhitelist = false
  732. protectBranch.WhitelistDeployKeys = false
  733. } else {
  734. protectBranch.EnableWhitelist = true
  735. if form.PushWhitelistDeployKeys != nil {
  736. protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys
  737. }
  738. }
  739. }
  740. }
  741. }
  742. if form.EnableForcePush != nil {
  743. if !*form.EnableForcePush {
  744. protectBranch.CanForcePush = false
  745. protectBranch.EnableForcePushAllowlist = false
  746. protectBranch.ForcePushAllowlistDeployKeys = false
  747. } else {
  748. protectBranch.CanForcePush = true
  749. if form.EnableForcePushAllowlist != nil {
  750. if !*form.EnableForcePushAllowlist {
  751. protectBranch.EnableForcePushAllowlist = false
  752. protectBranch.ForcePushAllowlistDeployKeys = false
  753. } else {
  754. protectBranch.EnableForcePushAllowlist = true
  755. if form.ForcePushAllowlistDeployKeys != nil {
  756. protectBranch.ForcePushAllowlistDeployKeys = *form.ForcePushAllowlistDeployKeys
  757. }
  758. }
  759. }
  760. }
  761. }
  762. if form.Priority != nil {
  763. protectBranch.Priority = *form.Priority
  764. }
  765. if form.EnableMergeWhitelist != nil {
  766. protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
  767. }
  768. if form.EnableStatusCheck != nil {
  769. protectBranch.EnableStatusCheck = *form.EnableStatusCheck
  770. }
  771. if form.StatusCheckContexts != nil {
  772. protectBranch.StatusCheckContexts = form.StatusCheckContexts
  773. }
  774. if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 {
  775. protectBranch.RequiredApprovals = *form.RequiredApprovals
  776. }
  777. if form.EnableApprovalsWhitelist != nil {
  778. protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
  779. }
  780. if form.BlockOnRejectedReviews != nil {
  781. protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
  782. }
  783. if form.BlockOnOfficialReviewRequests != nil {
  784. protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
  785. }
  786. if form.DismissStaleApprovals != nil {
  787. protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
  788. }
  789. if form.IgnoreStaleApprovals != nil {
  790. protectBranch.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
  791. }
  792. if form.RequireSignedCommits != nil {
  793. protectBranch.RequireSignedCommits = *form.RequireSignedCommits
  794. }
  795. if form.ProtectedFilePatterns != nil {
  796. protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns
  797. }
  798. if form.UnprotectedFilePatterns != nil {
  799. protectBranch.UnprotectedFilePatterns = *form.UnprotectedFilePatterns
  800. }
  801. if form.BlockOnOutdatedBranch != nil {
  802. protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
  803. }
  804. if form.BlockAdminMergeOverride != nil {
  805. protectBranch.BlockAdminMergeOverride = *form.BlockAdminMergeOverride
  806. }
  807. var whitelistUsers, forcePushAllowlistUsers, mergeWhitelistUsers, approvalsWhitelistUsers []int64
  808. if form.PushWhitelistUsernames != nil {
  809. whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
  810. if err != nil {
  811. if user_model.IsErrUserNotExist(err) {
  812. ctx.APIError(http.StatusUnprocessableEntity, err)
  813. return
  814. }
  815. ctx.APIErrorInternal(err)
  816. return
  817. }
  818. } else {
  819. whitelistUsers = protectBranch.WhitelistUserIDs
  820. }
  821. if form.ForcePushAllowlistDeployKeys != nil {
  822. forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
  823. if err != nil {
  824. if user_model.IsErrUserNotExist(err) {
  825. ctx.APIError(http.StatusUnprocessableEntity, err)
  826. return
  827. }
  828. ctx.APIErrorInternal(err)
  829. return
  830. }
  831. } else {
  832. forcePushAllowlistUsers = protectBranch.ForcePushAllowlistUserIDs
  833. }
  834. if form.MergeWhitelistUsernames != nil {
  835. mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
  836. if err != nil {
  837. if user_model.IsErrUserNotExist(err) {
  838. ctx.APIError(http.StatusUnprocessableEntity, err)
  839. return
  840. }
  841. ctx.APIErrorInternal(err)
  842. return
  843. }
  844. } else {
  845. mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
  846. }
  847. if form.ApprovalsWhitelistUsernames != nil {
  848. approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
  849. if err != nil {
  850. if user_model.IsErrUserNotExist(err) {
  851. ctx.APIError(http.StatusUnprocessableEntity, err)
  852. return
  853. }
  854. ctx.APIErrorInternal(err)
  855. return
  856. }
  857. } else {
  858. approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
  859. }
  860. var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  861. if repo.Owner.IsOrganization() {
  862. if form.PushWhitelistTeams != nil {
  863. whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
  864. if err != nil {
  865. if organization.IsErrTeamNotExist(err) {
  866. ctx.APIError(http.StatusUnprocessableEntity, err)
  867. return
  868. }
  869. ctx.APIErrorInternal(err)
  870. return
  871. }
  872. } else {
  873. whitelistTeams = protectBranch.WhitelistTeamIDs
  874. }
  875. if form.ForcePushAllowlistTeams != nil {
  876. forcePushAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushAllowlistTeams, false)
  877. if err != nil {
  878. if organization.IsErrTeamNotExist(err) {
  879. ctx.APIError(http.StatusUnprocessableEntity, err)
  880. return
  881. }
  882. ctx.APIErrorInternal(err)
  883. return
  884. }
  885. } else {
  886. forcePushAllowlistTeams = protectBranch.ForcePushAllowlistTeamIDs
  887. }
  888. if form.MergeWhitelistTeams != nil {
  889. mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
  890. if err != nil {
  891. if organization.IsErrTeamNotExist(err) {
  892. ctx.APIError(http.StatusUnprocessableEntity, err)
  893. return
  894. }
  895. ctx.APIErrorInternal(err)
  896. return
  897. }
  898. } else {
  899. mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs
  900. }
  901. if form.ApprovalsWhitelistTeams != nil {
  902. approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  903. if err != nil {
  904. if organization.IsErrTeamNotExist(err) {
  905. ctx.APIError(http.StatusUnprocessableEntity, err)
  906. return
  907. }
  908. ctx.APIErrorInternal(err)
  909. return
  910. }
  911. } else {
  912. approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
  913. }
  914. }
  915. err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
  916. UserIDs: whitelistUsers,
  917. TeamIDs: whitelistTeams,
  918. ForcePushUserIDs: forcePushAllowlistUsers,
  919. ForcePushTeamIDs: forcePushAllowlistTeams,
  920. MergeUserIDs: mergeWhitelistUsers,
  921. MergeTeamIDs: mergeWhitelistTeams,
  922. ApprovalsUserIDs: approvalsWhitelistUsers,
  923. ApprovalsTeamIDs: approvalsWhitelistTeams,
  924. })
  925. if err != nil {
  926. ctx.APIErrorInternal(err)
  927. return
  928. }
  929. isPlainRule := !git_model.IsRuleNameSpecial(bpName)
  930. var isBranchExist bool
  931. if isPlainRule {
  932. isBranchExist = gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, bpName)
  933. }
  934. if isBranchExist {
  935. if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, bpName); err != nil {
  936. ctx.APIErrorInternal(err)
  937. return
  938. }
  939. } else {
  940. if !isPlainRule {
  941. if ctx.Repo.GitRepo == nil {
  942. ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
  943. if err != nil {
  944. ctx.APIErrorInternal(err)
  945. return
  946. }
  947. }
  948. // FIXME: since we only need to recheck files protected rules, we could improve this
  949. matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
  950. if err != nil {
  951. ctx.APIErrorInternal(err)
  952. return
  953. }
  954. for _, branchName := range matchedBranches {
  955. if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
  956. ctx.APIErrorInternal(err)
  957. return
  958. }
  959. }
  960. }
  961. }
  962. // Reload from db to ensure get all whitelists
  963. bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
  964. if err != nil {
  965. ctx.APIErrorInternal(err)
  966. return
  967. }
  968. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  969. ctx.APIErrorInternal(err)
  970. return
  971. }
  972. ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp, repo))
  973. }
  974. // DeleteBranchProtection deletes a branch protection for a repo
  975. func DeleteBranchProtection(ctx *context.APIContext) {
  976. // swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
  977. // ---
  978. // summary: Delete a specific branch protection for the repository
  979. // produces:
  980. // - application/json
  981. // parameters:
  982. // - name: owner
  983. // in: path
  984. // description: owner of the repo
  985. // type: string
  986. // required: true
  987. // - name: repo
  988. // in: path
  989. // description: name of the repo
  990. // type: string
  991. // required: true
  992. // - name: name
  993. // in: path
  994. // description: name of protected branch
  995. // type: string
  996. // required: true
  997. // responses:
  998. // "204":
  999. // "$ref": "#/responses/empty"
  1000. // "404":
  1001. // "$ref": "#/responses/notFound"
  1002. repo := ctx.Repo.Repository
  1003. bpName := ctx.PathParam("name")
  1004. bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
  1005. if err != nil {
  1006. ctx.APIErrorInternal(err)
  1007. return
  1008. }
  1009. if bp == nil || bp.RepoID != repo.ID {
  1010. ctx.APIErrorNotFound()
  1011. return
  1012. }
  1013. if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, bp.ID); err != nil {
  1014. ctx.APIErrorInternal(err)
  1015. return
  1016. }
  1017. ctx.Status(http.StatusNoContent)
  1018. }
  1019. // UpdateBranchProtectionPriories updates the priorities of branch protections for a repo
  1020. func UpdateBranchProtectionPriories(ctx *context.APIContext) {
  1021. // swagger:operation POST /repos/{owner}/{repo}/branch_protections/priority repository repoUpdateBranchProtectionPriories
  1022. // ---
  1023. // summary: Update the priorities of branch protections for a repository.
  1024. // consumes:
  1025. // - application/json
  1026. // produces:
  1027. // - application/json
  1028. // parameters:
  1029. // - name: owner
  1030. // in: path
  1031. // description: owner of the repo
  1032. // type: string
  1033. // required: true
  1034. // - name: repo
  1035. // in: path
  1036. // description: name of the repo
  1037. // type: string
  1038. // required: true
  1039. // - name: body
  1040. // in: body
  1041. // schema:
  1042. // "$ref": "#/definitions/UpdateBranchProtectionPriories"
  1043. // responses:
  1044. // "204":
  1045. // "$ref": "#/responses/empty"
  1046. // "404":
  1047. // "$ref": "#/responses/notFound"
  1048. // "422":
  1049. // "$ref": "#/responses/validationError"
  1050. // "423":
  1051. // "$ref": "#/responses/repoArchivedError"
  1052. form := web.GetForm(ctx).(*api.UpdateBranchProtectionPriories)
  1053. repo := ctx.Repo.Repository
  1054. if err := git_model.UpdateProtectBranchPriorities(ctx, repo, form.IDs); err != nil {
  1055. ctx.APIErrorInternal(err)
  1056. return
  1057. }
  1058. ctx.Status(http.StatusNoContent)
  1059. }
  1060. func MergeUpstream(ctx *context.APIContext) {
  1061. // swagger:operation POST /repos/{owner}/{repo}/merge-upstream repository repoMergeUpstream
  1062. // ---
  1063. // summary: Merge a branch from upstream
  1064. // produces:
  1065. // - application/json
  1066. // parameters:
  1067. // - name: owner
  1068. // in: path
  1069. // description: owner of the repo
  1070. // type: string
  1071. // required: true
  1072. // - name: repo
  1073. // in: path
  1074. // description: name of the repo
  1075. // type: string
  1076. // required: true
  1077. // - name: body
  1078. // in: body
  1079. // schema:
  1080. // "$ref": "#/definitions/MergeUpstreamRequest"
  1081. // responses:
  1082. // "200":
  1083. // "$ref": "#/responses/MergeUpstreamResponse"
  1084. // "400":
  1085. // "$ref": "#/responses/error"
  1086. // "404":
  1087. // "$ref": "#/responses/notFound"
  1088. form := web.GetForm(ctx).(*api.MergeUpstreamRequest)
  1089. mergeStyle, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, form.Branch, form.FfOnly)
  1090. if err != nil {
  1091. if errors.Is(err, util.ErrInvalidArgument) {
  1092. ctx.APIError(http.StatusBadRequest, err)
  1093. return
  1094. } else if errors.Is(err, util.ErrNotExist) {
  1095. ctx.APIError(http.StatusNotFound, err)
  1096. return
  1097. }
  1098. ctx.APIErrorInternal(err)
  1099. return
  1100. }
  1101. ctx.JSON(http.StatusOK, &api.MergeUpstreamResponse{MergeStyle: mergeStyle})
  1102. }