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.

branch.go 31KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  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. "fmt"
  8. "net/http"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/models/db"
  11. git_model "code.gitea.io/gitea/models/git"
  12. "code.gitea.io/gitea/models/organization"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/git"
  15. "code.gitea.io/gitea/modules/gitrepo"
  16. "code.gitea.io/gitea/modules/optional"
  17. repo_module "code.gitea.io/gitea/modules/repository"
  18. api "code.gitea.io/gitea/modules/structs"
  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. repo_service "code.gitea.io/gitea/services/repository"
  25. )
  26. // GetBranch get a branch of a repository
  27. func GetBranch(ctx *context.APIContext) {
  28. // swagger:operation GET /repos/{owner}/{repo}/branches/{branch} repository repoGetBranch
  29. // ---
  30. // summary: Retrieve a specific branch from a repository, including its effective branch protection
  31. // produces:
  32. // - application/json
  33. // parameters:
  34. // - name: owner
  35. // in: path
  36. // description: owner of the repo
  37. // type: string
  38. // required: true
  39. // - name: repo
  40. // in: path
  41. // description: name of the repo
  42. // type: string
  43. // required: true
  44. // - name: branch
  45. // in: path
  46. // description: branch to get
  47. // type: string
  48. // required: true
  49. // responses:
  50. // "200":
  51. // "$ref": "#/responses/Branch"
  52. // "404":
  53. // "$ref": "#/responses/notFound"
  54. branchName := ctx.Params("*")
  55. branch, err := ctx.Repo.GitRepo.GetBranch(branchName)
  56. if err != nil {
  57. if git.IsErrBranchNotExist(err) {
  58. ctx.NotFound(err)
  59. } else {
  60. ctx.Error(http.StatusInternalServerError, "GetBranch", err)
  61. }
  62. return
  63. }
  64. c, err := branch.GetCommit()
  65. if err != nil {
  66. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  67. return
  68. }
  69. branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branchName)
  70. if err != nil {
  71. ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
  72. return
  73. }
  74. br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
  75. if err != nil {
  76. ctx.Error(http.StatusInternalServerError, "convert.ToBranch", 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.Error(http.StatusNotFound, "", "Git Repository is empty.")
  115. return
  116. }
  117. if ctx.Repo.Repository.IsMirror {
  118. ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
  119. return
  120. }
  121. branchName := ctx.Params("*")
  122. if ctx.Repo.Repository.IsEmpty {
  123. ctx.Error(http.StatusForbidden, "", "Git Repository is empty.")
  124. return
  125. }
  126. // check whether branches of this repository has been synced
  127. totalNumOfBranches, err := db.Count[git_model.Branch](ctx, git_model.FindBranchOptions{
  128. RepoID: ctx.Repo.Repository.ID,
  129. IsDeletedBranch: optional.Some(false),
  130. })
  131. if err != nil {
  132. ctx.Error(http.StatusInternalServerError, "CountBranches", err)
  133. return
  134. }
  135. if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
  136. _, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
  137. if err != nil {
  138. ctx.ServerError("SyncRepoBranches", err)
  139. return
  140. }
  141. }
  142. if ctx.Repo.Repository.IsMirror {
  143. ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository"))
  144. return
  145. }
  146. if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
  147. switch {
  148. case git.IsErrBranchNotExist(err):
  149. ctx.NotFound(err)
  150. case errors.Is(err, repo_service.ErrBranchIsDefault):
  151. ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
  152. case errors.Is(err, git_model.ErrBranchIsProtected):
  153. ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
  154. default:
  155. ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
  156. }
  157. return
  158. }
  159. ctx.Status(http.StatusNoContent)
  160. }
  161. // CreateBranch creates a branch for a user's repository
  162. func CreateBranch(ctx *context.APIContext) {
  163. // swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
  164. // ---
  165. // summary: Create a branch
  166. // consumes:
  167. // - application/json
  168. // produces:
  169. // - application/json
  170. // parameters:
  171. // - name: owner
  172. // in: path
  173. // description: owner of the repo
  174. // type: string
  175. // required: true
  176. // - name: repo
  177. // in: path
  178. // description: name of the repo
  179. // type: string
  180. // required: true
  181. // - name: body
  182. // in: body
  183. // schema:
  184. // "$ref": "#/definitions/CreateBranchRepoOption"
  185. // responses:
  186. // "201":
  187. // "$ref": "#/responses/Branch"
  188. // "403":
  189. // description: The branch is archived or a mirror.
  190. // "404":
  191. // description: The old branch does not exist.
  192. // "409":
  193. // description: The branch with the same name already exists.
  194. // "423":
  195. // "$ref": "#/responses/repoArchivedError"
  196. if ctx.Repo.Repository.IsEmpty {
  197. ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
  198. return
  199. }
  200. if ctx.Repo.Repository.IsMirror {
  201. ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.")
  202. return
  203. }
  204. opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
  205. var oldCommit *git.Commit
  206. var err error
  207. if len(opt.OldRefName) > 0 {
  208. oldCommit, err = ctx.Repo.GitRepo.GetCommit(opt.OldRefName)
  209. if err != nil {
  210. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  211. return
  212. }
  213. } else if len(opt.OldBranchName) > 0 { //nolint
  214. if ctx.Repo.GitRepo.IsBranchExist(opt.OldBranchName) { //nolint
  215. oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint
  216. if err != nil {
  217. ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
  218. return
  219. }
  220. } else {
  221. ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
  222. return
  223. }
  224. } else {
  225. oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
  226. if err != nil {
  227. ctx.Error(http.StatusInternalServerError, "GetBranchCommit", err)
  228. return
  229. }
  230. }
  231. err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
  232. if err != nil {
  233. if git_model.IsErrBranchNotExist(err) {
  234. ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
  235. } else if models.IsErrTagAlreadyExists(err) {
  236. ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
  237. } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
  238. ctx.Error(http.StatusConflict, "", "The branch already exists.")
  239. } else if git_model.IsErrBranchNameConflict(err) {
  240. ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
  241. } else {
  242. ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
  243. }
  244. return
  245. }
  246. branch, err := ctx.Repo.GitRepo.GetBranch(opt.BranchName)
  247. if err != nil {
  248. ctx.Error(http.StatusInternalServerError, "GetBranch", err)
  249. return
  250. }
  251. commit, err := branch.GetCommit()
  252. if err != nil {
  253. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  254. return
  255. }
  256. branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branch.Name)
  257. if err != nil {
  258. ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
  259. return
  260. }
  261. br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
  262. if err != nil {
  263. ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
  264. return
  265. }
  266. ctx.JSON(http.StatusCreated, br)
  267. }
  268. // ListBranches list all the branches of a repository
  269. func ListBranches(ctx *context.APIContext) {
  270. // swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches
  271. // ---
  272. // summary: List a repository's branches
  273. // produces:
  274. // - application/json
  275. // parameters:
  276. // - name: owner
  277. // in: path
  278. // description: owner of the repo
  279. // type: string
  280. // required: true
  281. // - name: repo
  282. // in: path
  283. // description: name of the repo
  284. // type: string
  285. // required: true
  286. // - name: page
  287. // in: query
  288. // description: page number of results to return (1-based)
  289. // type: integer
  290. // - name: limit
  291. // in: query
  292. // description: page size of results
  293. // type: integer
  294. // responses:
  295. // "200":
  296. // "$ref": "#/responses/BranchList"
  297. var totalNumOfBranches int64
  298. var apiBranches []*api.Branch
  299. listOptions := utils.GetListOptions(ctx)
  300. if !ctx.Repo.Repository.IsEmpty {
  301. if ctx.Repo.GitRepo == nil {
  302. ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil)
  303. return
  304. }
  305. branchOpts := git_model.FindBranchOptions{
  306. ListOptions: listOptions,
  307. RepoID: ctx.Repo.Repository.ID,
  308. IsDeletedBranch: optional.Some(false),
  309. }
  310. var err error
  311. totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
  312. if err != nil {
  313. ctx.Error(http.StatusInternalServerError, "CountBranches", err)
  314. return
  315. }
  316. if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
  317. totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
  318. if err != nil {
  319. ctx.ServerError("SyncRepoBranches", err)
  320. return
  321. }
  322. }
  323. rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
  324. if err != nil {
  325. ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
  326. return
  327. }
  328. branches, err := db.Find[git_model.Branch](ctx, branchOpts)
  329. if err != nil {
  330. ctx.Error(http.StatusInternalServerError, "GetBranches", err)
  331. return
  332. }
  333. apiBranches = make([]*api.Branch, 0, len(branches))
  334. for i := range branches {
  335. c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
  336. if err != nil {
  337. // Skip if this branch doesn't exist anymore.
  338. if git.IsErrNotExist(err) {
  339. totalNumOfBranches--
  340. continue
  341. }
  342. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  343. return
  344. }
  345. branchProtection := rules.GetFirstMatched(branches[i].Name)
  346. apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
  347. if err != nil {
  348. ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
  349. return
  350. }
  351. apiBranches = append(apiBranches, apiBranch)
  352. }
  353. }
  354. ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
  355. ctx.SetTotalCountHeader(totalNumOfBranches)
  356. ctx.JSON(http.StatusOK, apiBranches)
  357. }
  358. // GetBranchProtection gets a branch protection
  359. func GetBranchProtection(ctx *context.APIContext) {
  360. // swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
  361. // ---
  362. // summary: Get a specific branch protection for the repository
  363. // produces:
  364. // - application/json
  365. // parameters:
  366. // - name: owner
  367. // in: path
  368. // description: owner of the repo
  369. // type: string
  370. // required: true
  371. // - name: repo
  372. // in: path
  373. // description: name of the repo
  374. // type: string
  375. // required: true
  376. // - name: name
  377. // in: path
  378. // description: name of protected branch
  379. // type: string
  380. // required: true
  381. // responses:
  382. // "200":
  383. // "$ref": "#/responses/BranchProtection"
  384. // "404":
  385. // "$ref": "#/responses/notFound"
  386. repo := ctx.Repo.Repository
  387. bpName := ctx.Params(":name")
  388. bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
  389. if err != nil {
  390. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  391. return
  392. }
  393. if bp == nil || bp.RepoID != repo.ID {
  394. ctx.NotFound()
  395. return
  396. }
  397. ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp))
  398. }
  399. // ListBranchProtections list branch protections for a repo
  400. func ListBranchProtections(ctx *context.APIContext) {
  401. // swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection
  402. // ---
  403. // summary: List branch protections for a repository
  404. // produces:
  405. // - application/json
  406. // parameters:
  407. // - name: owner
  408. // in: path
  409. // description: owner of the repo
  410. // type: string
  411. // required: true
  412. // - name: repo
  413. // in: path
  414. // description: name of the repo
  415. // type: string
  416. // required: true
  417. // responses:
  418. // "200":
  419. // "$ref": "#/responses/BranchProtectionList"
  420. repo := ctx.Repo.Repository
  421. bps, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
  422. if err != nil {
  423. ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
  424. return
  425. }
  426. apiBps := make([]*api.BranchProtection, len(bps))
  427. for i := range bps {
  428. apiBps[i] = convert.ToBranchProtection(ctx, bps[i])
  429. }
  430. ctx.JSON(http.StatusOK, apiBps)
  431. }
  432. // CreateBranchProtection creates a branch protection for a repo
  433. func CreateBranchProtection(ctx *context.APIContext) {
  434. // swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
  435. // ---
  436. // summary: Create a branch protections for a repository
  437. // consumes:
  438. // - application/json
  439. // produces:
  440. // - application/json
  441. // parameters:
  442. // - name: owner
  443. // in: path
  444. // description: owner of the repo
  445. // type: string
  446. // required: true
  447. // - name: repo
  448. // in: path
  449. // description: name of the repo
  450. // type: string
  451. // required: true
  452. // - name: body
  453. // in: body
  454. // schema:
  455. // "$ref": "#/definitions/CreateBranchProtectionOption"
  456. // responses:
  457. // "201":
  458. // "$ref": "#/responses/BranchProtection"
  459. // "403":
  460. // "$ref": "#/responses/forbidden"
  461. // "404":
  462. // "$ref": "#/responses/notFound"
  463. // "422":
  464. // "$ref": "#/responses/validationError"
  465. // "423":
  466. // "$ref": "#/responses/repoArchivedError"
  467. form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
  468. repo := ctx.Repo.Repository
  469. ruleName := form.RuleName
  470. if ruleName == "" {
  471. ruleName = form.BranchName //nolint
  472. }
  473. if len(ruleName) == 0 {
  474. ctx.Error(http.StatusBadRequest, "both rule_name and branch_name are empty", "both rule_name and branch_name are empty")
  475. return
  476. }
  477. isPlainRule := !git_model.IsRuleNameSpecial(ruleName)
  478. var isBranchExist bool
  479. if isPlainRule {
  480. isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), ruleName)
  481. }
  482. protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, ruleName)
  483. if err != nil {
  484. ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
  485. return
  486. } else if protectBranch != nil {
  487. ctx.Error(http.StatusForbidden, "Create branch protection", "Branch protection already exist")
  488. return
  489. }
  490. var requiredApprovals int64
  491. if form.RequiredApprovals > 0 {
  492. requiredApprovals = form.RequiredApprovals
  493. }
  494. whitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
  495. if err != nil {
  496. if user_model.IsErrUserNotExist(err) {
  497. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  498. return
  499. }
  500. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  501. return
  502. }
  503. mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
  504. if err != nil {
  505. if user_model.IsErrUserNotExist(err) {
  506. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  507. return
  508. }
  509. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  510. return
  511. }
  512. approvalsWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
  513. if err != nil {
  514. if user_model.IsErrUserNotExist(err) {
  515. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  516. return
  517. }
  518. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  519. return
  520. }
  521. var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  522. if repo.Owner.IsOrganization() {
  523. whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
  524. if err != nil {
  525. if organization.IsErrTeamNotExist(err) {
  526. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  527. return
  528. }
  529. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  530. return
  531. }
  532. mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
  533. if err != nil {
  534. if organization.IsErrTeamNotExist(err) {
  535. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  536. return
  537. }
  538. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  539. return
  540. }
  541. approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  542. if err != nil {
  543. if organization.IsErrTeamNotExist(err) {
  544. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  545. return
  546. }
  547. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  548. return
  549. }
  550. }
  551. protectBranch = &git_model.ProtectedBranch{
  552. RepoID: ctx.Repo.Repository.ID,
  553. RuleName: ruleName,
  554. CanPush: form.EnablePush,
  555. EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
  556. EnableMergeWhitelist: form.EnableMergeWhitelist,
  557. WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
  558. EnableStatusCheck: form.EnableStatusCheck,
  559. StatusCheckContexts: form.StatusCheckContexts,
  560. EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
  561. RequiredApprovals: requiredApprovals,
  562. BlockOnRejectedReviews: form.BlockOnRejectedReviews,
  563. BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
  564. DismissStaleApprovals: form.DismissStaleApprovals,
  565. IgnoreStaleApprovals: form.IgnoreStaleApprovals,
  566. RequireSignedCommits: form.RequireSignedCommits,
  567. ProtectedFilePatterns: form.ProtectedFilePatterns,
  568. UnprotectedFilePatterns: form.UnprotectedFilePatterns,
  569. BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
  570. }
  571. err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
  572. UserIDs: whitelistUsers,
  573. TeamIDs: whitelistTeams,
  574. MergeUserIDs: mergeWhitelistUsers,
  575. MergeTeamIDs: mergeWhitelistTeams,
  576. ApprovalsUserIDs: approvalsWhitelistUsers,
  577. ApprovalsTeamIDs: approvalsWhitelistTeams,
  578. })
  579. if err != nil {
  580. ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
  581. return
  582. }
  583. if isBranchExist {
  584. if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, ruleName); err != nil {
  585. ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
  586. return
  587. }
  588. } else {
  589. if !isPlainRule {
  590. if ctx.Repo.GitRepo == nil {
  591. ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
  592. if err != nil {
  593. ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
  594. return
  595. }
  596. defer func() {
  597. ctx.Repo.GitRepo.Close()
  598. ctx.Repo.GitRepo = nil
  599. }()
  600. }
  601. // FIXME: since we only need to recheck files protected rules, we could improve this
  602. matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
  603. if err != nil {
  604. ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
  605. return
  606. }
  607. for _, branchName := range matchedBranches {
  608. if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
  609. ctx.Error(http.StatusInternalServerError, "CheckPRsForBaseBranch", err)
  610. return
  611. }
  612. }
  613. }
  614. }
  615. // Reload from db to get all whitelists
  616. bp, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, ruleName)
  617. if err != nil {
  618. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  619. return
  620. }
  621. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  622. ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
  623. return
  624. }
  625. ctx.JSON(http.StatusCreated, convert.ToBranchProtection(ctx, bp))
  626. }
  627. // EditBranchProtection edits a branch protection for a repo
  628. func EditBranchProtection(ctx *context.APIContext) {
  629. // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
  630. // ---
  631. // summary: Edit a branch protections for a repository. Only fields that are set will be changed
  632. // consumes:
  633. // - application/json
  634. // produces:
  635. // - application/json
  636. // parameters:
  637. // - name: owner
  638. // in: path
  639. // description: owner of the repo
  640. // type: string
  641. // required: true
  642. // - name: repo
  643. // in: path
  644. // description: name of the repo
  645. // type: string
  646. // required: true
  647. // - name: name
  648. // in: path
  649. // description: name of protected branch
  650. // type: string
  651. // required: true
  652. // - name: body
  653. // in: body
  654. // schema:
  655. // "$ref": "#/definitions/EditBranchProtectionOption"
  656. // responses:
  657. // "200":
  658. // "$ref": "#/responses/BranchProtection"
  659. // "404":
  660. // "$ref": "#/responses/notFound"
  661. // "422":
  662. // "$ref": "#/responses/validationError"
  663. // "423":
  664. // "$ref": "#/responses/repoArchivedError"
  665. form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
  666. repo := ctx.Repo.Repository
  667. bpName := ctx.Params(":name")
  668. protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
  669. if err != nil {
  670. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  671. return
  672. }
  673. if protectBranch == nil || protectBranch.RepoID != repo.ID {
  674. ctx.NotFound()
  675. return
  676. }
  677. if form.EnablePush != nil {
  678. if !*form.EnablePush {
  679. protectBranch.CanPush = false
  680. protectBranch.EnableWhitelist = false
  681. protectBranch.WhitelistDeployKeys = false
  682. } else {
  683. protectBranch.CanPush = true
  684. if form.EnablePushWhitelist != nil {
  685. if !*form.EnablePushWhitelist {
  686. protectBranch.EnableWhitelist = false
  687. protectBranch.WhitelistDeployKeys = false
  688. } else {
  689. protectBranch.EnableWhitelist = true
  690. if form.PushWhitelistDeployKeys != nil {
  691. protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys
  692. }
  693. }
  694. }
  695. }
  696. }
  697. if form.EnableMergeWhitelist != nil {
  698. protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
  699. }
  700. if form.EnableStatusCheck != nil {
  701. protectBranch.EnableStatusCheck = *form.EnableStatusCheck
  702. }
  703. if form.StatusCheckContexts != nil {
  704. protectBranch.StatusCheckContexts = form.StatusCheckContexts
  705. }
  706. if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 {
  707. protectBranch.RequiredApprovals = *form.RequiredApprovals
  708. }
  709. if form.EnableApprovalsWhitelist != nil {
  710. protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
  711. }
  712. if form.BlockOnRejectedReviews != nil {
  713. protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
  714. }
  715. if form.BlockOnOfficialReviewRequests != nil {
  716. protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
  717. }
  718. if form.DismissStaleApprovals != nil {
  719. protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
  720. }
  721. if form.IgnoreStaleApprovals != nil {
  722. protectBranch.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
  723. }
  724. if form.RequireSignedCommits != nil {
  725. protectBranch.RequireSignedCommits = *form.RequireSignedCommits
  726. }
  727. if form.ProtectedFilePatterns != nil {
  728. protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns
  729. }
  730. if form.UnprotectedFilePatterns != nil {
  731. protectBranch.UnprotectedFilePatterns = *form.UnprotectedFilePatterns
  732. }
  733. if form.BlockOnOutdatedBranch != nil {
  734. protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
  735. }
  736. var whitelistUsers []int64
  737. if form.PushWhitelistUsernames != nil {
  738. whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
  739. if err != nil {
  740. if user_model.IsErrUserNotExist(err) {
  741. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  742. return
  743. }
  744. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  745. return
  746. }
  747. } else {
  748. whitelistUsers = protectBranch.WhitelistUserIDs
  749. }
  750. var mergeWhitelistUsers []int64
  751. if form.MergeWhitelistUsernames != nil {
  752. mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
  753. if err != nil {
  754. if user_model.IsErrUserNotExist(err) {
  755. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  756. return
  757. }
  758. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  759. return
  760. }
  761. } else {
  762. mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
  763. }
  764. var approvalsWhitelistUsers []int64
  765. if form.ApprovalsWhitelistUsernames != nil {
  766. approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
  767. if err != nil {
  768. if user_model.IsErrUserNotExist(err) {
  769. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  770. return
  771. }
  772. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  773. return
  774. }
  775. } else {
  776. approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
  777. }
  778. var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  779. if repo.Owner.IsOrganization() {
  780. if form.PushWhitelistTeams != nil {
  781. whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
  782. if err != nil {
  783. if organization.IsErrTeamNotExist(err) {
  784. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  785. return
  786. }
  787. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  788. return
  789. }
  790. } else {
  791. whitelistTeams = protectBranch.WhitelistTeamIDs
  792. }
  793. if form.MergeWhitelistTeams != nil {
  794. mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
  795. if err != nil {
  796. if organization.IsErrTeamNotExist(err) {
  797. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  798. return
  799. }
  800. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  801. return
  802. }
  803. } else {
  804. mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs
  805. }
  806. if form.ApprovalsWhitelistTeams != nil {
  807. approvalsWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  808. if err != nil {
  809. if organization.IsErrTeamNotExist(err) {
  810. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  811. return
  812. }
  813. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  814. return
  815. }
  816. } else {
  817. approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
  818. }
  819. }
  820. err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
  821. UserIDs: whitelistUsers,
  822. TeamIDs: whitelistTeams,
  823. MergeUserIDs: mergeWhitelistUsers,
  824. MergeTeamIDs: mergeWhitelistTeams,
  825. ApprovalsUserIDs: approvalsWhitelistUsers,
  826. ApprovalsTeamIDs: approvalsWhitelistTeams,
  827. })
  828. if err != nil {
  829. ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
  830. return
  831. }
  832. isPlainRule := !git_model.IsRuleNameSpecial(bpName)
  833. var isBranchExist bool
  834. if isPlainRule {
  835. isBranchExist = git.IsBranchExist(ctx.Req.Context(), ctx.Repo.Repository.RepoPath(), bpName)
  836. }
  837. if isBranchExist {
  838. if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, bpName); err != nil {
  839. ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
  840. return
  841. }
  842. } else {
  843. if !isPlainRule {
  844. if ctx.Repo.GitRepo == nil {
  845. ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
  846. if err != nil {
  847. ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
  848. return
  849. }
  850. defer func() {
  851. ctx.Repo.GitRepo.Close()
  852. ctx.Repo.GitRepo = nil
  853. }()
  854. }
  855. // FIXME: since we only need to recheck files protected rules, we could improve this
  856. matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
  857. if err != nil {
  858. ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
  859. return
  860. }
  861. for _, branchName := range matchedBranches {
  862. if err = pull_service.CheckPRsForBaseBranch(ctx, ctx.Repo.Repository, branchName); err != nil {
  863. ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
  864. return
  865. }
  866. }
  867. }
  868. }
  869. // Reload from db to ensure get all whitelists
  870. bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
  871. if err != nil {
  872. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
  873. return
  874. }
  875. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  876. ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
  877. return
  878. }
  879. ctx.JSON(http.StatusOK, convert.ToBranchProtection(ctx, bp))
  880. }
  881. // DeleteBranchProtection deletes a branch protection for a repo
  882. func DeleteBranchProtection(ctx *context.APIContext) {
  883. // swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
  884. // ---
  885. // summary: Delete a specific branch protection for the repository
  886. // produces:
  887. // - application/json
  888. // parameters:
  889. // - name: owner
  890. // in: path
  891. // description: owner of the repo
  892. // type: string
  893. // required: true
  894. // - name: repo
  895. // in: path
  896. // description: name of the repo
  897. // type: string
  898. // required: true
  899. // - name: name
  900. // in: path
  901. // description: name of protected branch
  902. // type: string
  903. // required: true
  904. // responses:
  905. // "204":
  906. // "$ref": "#/responses/empty"
  907. // "404":
  908. // "$ref": "#/responses/notFound"
  909. repo := ctx.Repo.Repository
  910. bpName := ctx.Params(":name")
  911. bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
  912. if err != nil {
  913. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  914. return
  915. }
  916. if bp == nil || bp.RepoID != repo.ID {
  917. ctx.NotFound()
  918. return
  919. }
  920. if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, bp.ID); err != nil {
  921. ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
  922. return
  923. }
  924. ctx.Status(http.StatusNoContent)
  925. }