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 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "errors"
  8. "fmt"
  9. "net/http"
  10. "code.gitea.io/gitea/models"
  11. "code.gitea.io/gitea/modules/context"
  12. "code.gitea.io/gitea/modules/convert"
  13. "code.gitea.io/gitea/modules/git"
  14. repo_module "code.gitea.io/gitea/modules/repository"
  15. api "code.gitea.io/gitea/modules/structs"
  16. "code.gitea.io/gitea/modules/web"
  17. "code.gitea.io/gitea/routers/api/v1/utils"
  18. pull_service "code.gitea.io/gitea/services/pull"
  19. repo_service "code.gitea.io/gitea/services/repository"
  20. )
  21. // GetBranch get a branch of a repository
  22. func GetBranch(ctx *context.APIContext) {
  23. // swagger:operation GET /repos/{owner}/{repo}/branches/{branch} repository repoGetBranch
  24. // ---
  25. // summary: Retrieve a specific branch from a repository, including its effective branch protection
  26. // produces:
  27. // - application/json
  28. // parameters:
  29. // - name: owner
  30. // in: path
  31. // description: owner of the repo
  32. // type: string
  33. // required: true
  34. // - name: repo
  35. // in: path
  36. // description: name of the repo
  37. // type: string
  38. // required: true
  39. // - name: branch
  40. // in: path
  41. // description: branch to get
  42. // type: string
  43. // required: true
  44. // responses:
  45. // "200":
  46. // "$ref": "#/responses/Branch"
  47. // "404":
  48. // "$ref": "#/responses/notFound"
  49. branchName := ctx.Params("*")
  50. branch, err := repo_module.GetBranch(ctx.Repo.Repository, branchName)
  51. if err != nil {
  52. if git.IsErrBranchNotExist(err) {
  53. ctx.NotFound(err)
  54. } else {
  55. ctx.Error(http.StatusInternalServerError, "GetBranch", err)
  56. }
  57. return
  58. }
  59. c, err := branch.GetCommit()
  60. if err != nil {
  61. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  62. return
  63. }
  64. branchProtection, err := ctx.Repo.Repository.GetBranchProtection(branchName)
  65. if err != nil {
  66. ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
  67. return
  68. }
  69. br, err := convert.ToBranch(ctx.Repo.Repository, branch, c, branchProtection, ctx.User, ctx.Repo.IsAdmin())
  70. if err != nil {
  71. ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
  72. return
  73. }
  74. ctx.JSON(http.StatusOK, br)
  75. }
  76. // DeleteBranch get a branch of a repository
  77. func DeleteBranch(ctx *context.APIContext) {
  78. // swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch} repository repoDeleteBranch
  79. // ---
  80. // summary: Delete a specific branch from a repository
  81. // produces:
  82. // - application/json
  83. // parameters:
  84. // - name: owner
  85. // in: path
  86. // description: owner of the repo
  87. // type: string
  88. // required: true
  89. // - name: repo
  90. // in: path
  91. // description: name of the repo
  92. // type: string
  93. // required: true
  94. // - name: branch
  95. // in: path
  96. // description: branch to delete
  97. // type: string
  98. // required: true
  99. // responses:
  100. // "204":
  101. // "$ref": "#/responses/empty"
  102. // "403":
  103. // "$ref": "#/responses/error"
  104. // "404":
  105. // "$ref": "#/responses/notFound"
  106. branchName := ctx.Params("*")
  107. if err := repo_service.DeleteBranch(ctx.User, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
  108. switch {
  109. case git.IsErrBranchNotExist(err):
  110. ctx.NotFound(err)
  111. case errors.Is(err, repo_service.ErrBranchIsDefault):
  112. ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
  113. case errors.Is(err, repo_service.ErrBranchIsProtected):
  114. ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
  115. default:
  116. ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
  117. }
  118. return
  119. }
  120. ctx.Status(http.StatusNoContent)
  121. }
  122. // CreateBranch creates a branch for a user's repository
  123. func CreateBranch(ctx *context.APIContext) {
  124. // swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
  125. // ---
  126. // summary: Create a branch
  127. // consumes:
  128. // - application/json
  129. // produces:
  130. // - application/json
  131. // parameters:
  132. // - name: owner
  133. // in: path
  134. // description: owner of the repo
  135. // type: string
  136. // required: true
  137. // - name: repo
  138. // in: path
  139. // description: name of the repo
  140. // type: string
  141. // required: true
  142. // - name: body
  143. // in: body
  144. // schema:
  145. // "$ref": "#/definitions/CreateBranchRepoOption"
  146. // responses:
  147. // "201":
  148. // "$ref": "#/responses/Branch"
  149. // "404":
  150. // description: The old branch does not exist.
  151. // "409":
  152. // description: The branch with the same name already exists.
  153. opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
  154. if ctx.Repo.Repository.IsEmpty {
  155. ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
  156. return
  157. }
  158. if len(opt.OldBranchName) == 0 {
  159. opt.OldBranchName = ctx.Repo.Repository.DefaultBranch
  160. }
  161. err := repo_module.CreateNewBranch(ctx.User, ctx.Repo.Repository, opt.OldBranchName, opt.BranchName)
  162. if err != nil {
  163. if models.IsErrBranchDoesNotExist(err) {
  164. ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
  165. }
  166. if models.IsErrTagAlreadyExists(err) {
  167. ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
  168. } else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
  169. ctx.Error(http.StatusConflict, "", "The branch already exists.")
  170. } else if models.IsErrBranchNameConflict(err) {
  171. ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
  172. } else {
  173. ctx.Error(http.StatusInternalServerError, "CreateRepoBranch", err)
  174. }
  175. return
  176. }
  177. branch, err := repo_module.GetBranch(ctx.Repo.Repository, opt.BranchName)
  178. if err != nil {
  179. ctx.Error(http.StatusInternalServerError, "GetBranch", err)
  180. return
  181. }
  182. commit, err := branch.GetCommit()
  183. if err != nil {
  184. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  185. return
  186. }
  187. branchProtection, err := ctx.Repo.Repository.GetBranchProtection(branch.Name)
  188. if err != nil {
  189. ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
  190. return
  191. }
  192. br, err := convert.ToBranch(ctx.Repo.Repository, branch, commit, branchProtection, ctx.User, ctx.Repo.IsAdmin())
  193. if err != nil {
  194. ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
  195. return
  196. }
  197. ctx.JSON(http.StatusCreated, br)
  198. }
  199. // ListBranches list all the branches of a repository
  200. func ListBranches(ctx *context.APIContext) {
  201. // swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches
  202. // ---
  203. // summary: List a repository's branches
  204. // produces:
  205. // - application/json
  206. // parameters:
  207. // - name: owner
  208. // in: path
  209. // description: owner of the repo
  210. // type: string
  211. // required: true
  212. // - name: repo
  213. // in: path
  214. // description: name of the repo
  215. // type: string
  216. // required: true
  217. // - name: page
  218. // in: query
  219. // description: page number of results to return (1-based)
  220. // type: integer
  221. // - name: limit
  222. // in: query
  223. // description: page size of results
  224. // type: integer
  225. // responses:
  226. // "200":
  227. // "$ref": "#/responses/BranchList"
  228. listOptions := utils.GetListOptions(ctx)
  229. skip, _ := listOptions.GetStartEnd()
  230. branches, totalNumOfBranches, err := repo_module.GetBranches(ctx.Repo.Repository, skip, listOptions.PageSize)
  231. if err != nil {
  232. ctx.Error(http.StatusInternalServerError, "GetBranches", err)
  233. return
  234. }
  235. apiBranches := make([]*api.Branch, len(branches))
  236. for i := range branches {
  237. c, err := branches[i].GetCommit()
  238. if err != nil {
  239. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  240. return
  241. }
  242. branchProtection, err := ctx.Repo.Repository.GetBranchProtection(branches[i].Name)
  243. if err != nil {
  244. ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
  245. return
  246. }
  247. apiBranches[i], err = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.User, ctx.Repo.IsAdmin())
  248. if err != nil {
  249. ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
  250. return
  251. }
  252. }
  253. ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
  254. ctx.SetTotalCountHeader(int64(totalNumOfBranches))
  255. ctx.JSON(http.StatusOK, &apiBranches)
  256. }
  257. // GetBranchProtection gets a branch protection
  258. func GetBranchProtection(ctx *context.APIContext) {
  259. // swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
  260. // ---
  261. // summary: Get a specific branch protection for the repository
  262. // produces:
  263. // - application/json
  264. // parameters:
  265. // - name: owner
  266. // in: path
  267. // description: owner of the repo
  268. // type: string
  269. // required: true
  270. // - name: repo
  271. // in: path
  272. // description: name of the repo
  273. // type: string
  274. // required: true
  275. // - name: name
  276. // in: path
  277. // description: name of protected branch
  278. // type: string
  279. // required: true
  280. // responses:
  281. // "200":
  282. // "$ref": "#/responses/BranchProtection"
  283. // "404":
  284. // "$ref": "#/responses/notFound"
  285. repo := ctx.Repo.Repository
  286. bpName := ctx.Params(":name")
  287. bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
  288. if err != nil {
  289. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  290. return
  291. }
  292. if bp == nil || bp.RepoID != repo.ID {
  293. ctx.NotFound()
  294. return
  295. }
  296. ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp))
  297. }
  298. // ListBranchProtections list branch protections for a repo
  299. func ListBranchProtections(ctx *context.APIContext) {
  300. // swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection
  301. // ---
  302. // summary: List branch protections for a repository
  303. // produces:
  304. // - application/json
  305. // parameters:
  306. // - name: owner
  307. // in: path
  308. // description: owner of the repo
  309. // type: string
  310. // required: true
  311. // - name: repo
  312. // in: path
  313. // description: name of the repo
  314. // type: string
  315. // required: true
  316. // responses:
  317. // "200":
  318. // "$ref": "#/responses/BranchProtectionList"
  319. repo := ctx.Repo.Repository
  320. bps, err := repo.GetProtectedBranches()
  321. if err != nil {
  322. ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
  323. return
  324. }
  325. apiBps := make([]*api.BranchProtection, len(bps))
  326. for i := range bps {
  327. apiBps[i] = convert.ToBranchProtection(bps[i])
  328. }
  329. ctx.JSON(http.StatusOK, apiBps)
  330. }
  331. // CreateBranchProtection creates a branch protection for a repo
  332. func CreateBranchProtection(ctx *context.APIContext) {
  333. // swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
  334. // ---
  335. // summary: Create a branch protections for a repository
  336. // consumes:
  337. // - application/json
  338. // produces:
  339. // - application/json
  340. // parameters:
  341. // - name: owner
  342. // in: path
  343. // description: owner of the repo
  344. // type: string
  345. // required: true
  346. // - name: repo
  347. // in: path
  348. // description: name of the repo
  349. // type: string
  350. // required: true
  351. // - name: body
  352. // in: body
  353. // schema:
  354. // "$ref": "#/definitions/CreateBranchProtectionOption"
  355. // responses:
  356. // "201":
  357. // "$ref": "#/responses/BranchProtection"
  358. // "403":
  359. // "$ref": "#/responses/forbidden"
  360. // "404":
  361. // "$ref": "#/responses/notFound"
  362. // "422":
  363. // "$ref": "#/responses/validationError"
  364. form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
  365. repo := ctx.Repo.Repository
  366. // Currently protection must match an actual branch
  367. if !git.IsBranchExist(ctx.Repo.Repository.RepoPath(), form.BranchName) {
  368. ctx.NotFound()
  369. return
  370. }
  371. protectBranch, err := models.GetProtectedBranchBy(repo.ID, form.BranchName)
  372. if err != nil {
  373. ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
  374. return
  375. } else if protectBranch != nil {
  376. ctx.Error(http.StatusForbidden, "Create branch protection", "Branch protection already exist")
  377. return
  378. }
  379. var requiredApprovals int64
  380. if form.RequiredApprovals > 0 {
  381. requiredApprovals = form.RequiredApprovals
  382. }
  383. whitelistUsers, err := models.GetUserIDsByNames(form.PushWhitelistUsernames, false)
  384. if err != nil {
  385. if models.IsErrUserNotExist(err) {
  386. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  387. return
  388. }
  389. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  390. return
  391. }
  392. mergeWhitelistUsers, err := models.GetUserIDsByNames(form.MergeWhitelistUsernames, false)
  393. if err != nil {
  394. if models.IsErrUserNotExist(err) {
  395. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  396. return
  397. }
  398. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  399. return
  400. }
  401. approvalsWhitelistUsers, err := models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false)
  402. if err != nil {
  403. if models.IsErrUserNotExist(err) {
  404. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  405. return
  406. }
  407. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  408. return
  409. }
  410. var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  411. if repo.Owner.IsOrganization() {
  412. whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false)
  413. if err != nil {
  414. if models.IsErrTeamNotExist(err) {
  415. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  416. return
  417. }
  418. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  419. return
  420. }
  421. mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false)
  422. if err != nil {
  423. if models.IsErrTeamNotExist(err) {
  424. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  425. return
  426. }
  427. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  428. return
  429. }
  430. approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  431. if err != nil {
  432. if models.IsErrTeamNotExist(err) {
  433. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  434. return
  435. }
  436. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  437. return
  438. }
  439. }
  440. protectBranch = &models.ProtectedBranch{
  441. RepoID: ctx.Repo.Repository.ID,
  442. BranchName: form.BranchName,
  443. CanPush: form.EnablePush,
  444. EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
  445. EnableMergeWhitelist: form.EnableMergeWhitelist,
  446. WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
  447. EnableStatusCheck: form.EnableStatusCheck,
  448. StatusCheckContexts: form.StatusCheckContexts,
  449. EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
  450. RequiredApprovals: requiredApprovals,
  451. BlockOnRejectedReviews: form.BlockOnRejectedReviews,
  452. BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
  453. DismissStaleApprovals: form.DismissStaleApprovals,
  454. RequireSignedCommits: form.RequireSignedCommits,
  455. ProtectedFilePatterns: form.ProtectedFilePatterns,
  456. BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
  457. }
  458. err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
  459. UserIDs: whitelistUsers,
  460. TeamIDs: whitelistTeams,
  461. MergeUserIDs: mergeWhitelistUsers,
  462. MergeTeamIDs: mergeWhitelistTeams,
  463. ApprovalsUserIDs: approvalsWhitelistUsers,
  464. ApprovalsTeamIDs: approvalsWhitelistTeams,
  465. })
  466. if err != nil {
  467. ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
  468. return
  469. }
  470. if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil {
  471. ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
  472. return
  473. }
  474. // Reload from db to get all whitelists
  475. bp, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, form.BranchName)
  476. if err != nil {
  477. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  478. return
  479. }
  480. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  481. ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
  482. return
  483. }
  484. ctx.JSON(http.StatusCreated, convert.ToBranchProtection(bp))
  485. }
  486. // EditBranchProtection edits a branch protection for a repo
  487. func EditBranchProtection(ctx *context.APIContext) {
  488. // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
  489. // ---
  490. // summary: Edit a branch protections for a repository. Only fields that are set will be changed
  491. // consumes:
  492. // - application/json
  493. // produces:
  494. // - application/json
  495. // parameters:
  496. // - name: owner
  497. // in: path
  498. // description: owner of the repo
  499. // type: string
  500. // required: true
  501. // - name: repo
  502. // in: path
  503. // description: name of the repo
  504. // type: string
  505. // required: true
  506. // - name: name
  507. // in: path
  508. // description: name of protected branch
  509. // type: string
  510. // required: true
  511. // - name: body
  512. // in: body
  513. // schema:
  514. // "$ref": "#/definitions/EditBranchProtectionOption"
  515. // responses:
  516. // "200":
  517. // "$ref": "#/responses/BranchProtection"
  518. // "404":
  519. // "$ref": "#/responses/notFound"
  520. // "422":
  521. // "$ref": "#/responses/validationError"
  522. form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
  523. repo := ctx.Repo.Repository
  524. bpName := ctx.Params(":name")
  525. protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName)
  526. if err != nil {
  527. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  528. return
  529. }
  530. if protectBranch == nil || protectBranch.RepoID != repo.ID {
  531. ctx.NotFound()
  532. return
  533. }
  534. if form.EnablePush != nil {
  535. if !*form.EnablePush {
  536. protectBranch.CanPush = false
  537. protectBranch.EnableWhitelist = false
  538. protectBranch.WhitelistDeployKeys = false
  539. } else {
  540. protectBranch.CanPush = true
  541. if form.EnablePushWhitelist != nil {
  542. if !*form.EnablePushWhitelist {
  543. protectBranch.EnableWhitelist = false
  544. protectBranch.WhitelistDeployKeys = false
  545. } else {
  546. protectBranch.EnableWhitelist = true
  547. if form.PushWhitelistDeployKeys != nil {
  548. protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys
  549. }
  550. }
  551. }
  552. }
  553. }
  554. if form.EnableMergeWhitelist != nil {
  555. protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
  556. }
  557. if form.EnableStatusCheck != nil {
  558. protectBranch.EnableStatusCheck = *form.EnableStatusCheck
  559. }
  560. if protectBranch.EnableStatusCheck {
  561. protectBranch.StatusCheckContexts = form.StatusCheckContexts
  562. }
  563. if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 {
  564. protectBranch.RequiredApprovals = *form.RequiredApprovals
  565. }
  566. if form.EnableApprovalsWhitelist != nil {
  567. protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
  568. }
  569. if form.BlockOnRejectedReviews != nil {
  570. protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
  571. }
  572. if form.BlockOnOfficialReviewRequests != nil {
  573. protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
  574. }
  575. if form.DismissStaleApprovals != nil {
  576. protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
  577. }
  578. if form.RequireSignedCommits != nil {
  579. protectBranch.RequireSignedCommits = *form.RequireSignedCommits
  580. }
  581. if form.ProtectedFilePatterns != nil {
  582. protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns
  583. }
  584. if form.BlockOnOutdatedBranch != nil {
  585. protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
  586. }
  587. var whitelistUsers []int64
  588. if form.PushWhitelistUsernames != nil {
  589. whitelistUsers, err = models.GetUserIDsByNames(form.PushWhitelistUsernames, false)
  590. if err != nil {
  591. if models.IsErrUserNotExist(err) {
  592. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  593. return
  594. }
  595. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  596. return
  597. }
  598. } else {
  599. whitelistUsers = protectBranch.WhitelistUserIDs
  600. }
  601. var mergeWhitelistUsers []int64
  602. if form.MergeWhitelistUsernames != nil {
  603. mergeWhitelistUsers, err = models.GetUserIDsByNames(form.MergeWhitelistUsernames, false)
  604. if err != nil {
  605. if models.IsErrUserNotExist(err) {
  606. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  607. return
  608. }
  609. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  610. return
  611. }
  612. } else {
  613. mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
  614. }
  615. var approvalsWhitelistUsers []int64
  616. if form.ApprovalsWhitelistUsernames != nil {
  617. approvalsWhitelistUsers, err = models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false)
  618. if err != nil {
  619. if models.IsErrUserNotExist(err) {
  620. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  621. return
  622. }
  623. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  624. return
  625. }
  626. } else {
  627. approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
  628. }
  629. var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  630. if repo.Owner.IsOrganization() {
  631. if form.PushWhitelistTeams != nil {
  632. whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false)
  633. if err != nil {
  634. if models.IsErrTeamNotExist(err) {
  635. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  636. return
  637. }
  638. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  639. return
  640. }
  641. } else {
  642. whitelistTeams = protectBranch.WhitelistTeamIDs
  643. }
  644. if form.MergeWhitelistTeams != nil {
  645. mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false)
  646. if err != nil {
  647. if models.IsErrTeamNotExist(err) {
  648. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  649. return
  650. }
  651. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  652. return
  653. }
  654. } else {
  655. mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs
  656. }
  657. if form.ApprovalsWhitelistTeams != nil {
  658. approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  659. if err != nil {
  660. if models.IsErrTeamNotExist(err) {
  661. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  662. return
  663. }
  664. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  665. return
  666. }
  667. } else {
  668. approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
  669. }
  670. }
  671. err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
  672. UserIDs: whitelistUsers,
  673. TeamIDs: whitelistTeams,
  674. MergeUserIDs: mergeWhitelistUsers,
  675. MergeTeamIDs: mergeWhitelistTeams,
  676. ApprovalsUserIDs: approvalsWhitelistUsers,
  677. ApprovalsTeamIDs: approvalsWhitelistTeams,
  678. })
  679. if err != nil {
  680. ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
  681. return
  682. }
  683. if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil {
  684. ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
  685. return
  686. }
  687. // Reload from db to ensure get all whitelists
  688. bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
  689. if err != nil {
  690. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
  691. return
  692. }
  693. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  694. ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
  695. return
  696. }
  697. ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp))
  698. }
  699. // DeleteBranchProtection deletes a branch protection for a repo
  700. func DeleteBranchProtection(ctx *context.APIContext) {
  701. // swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
  702. // ---
  703. // summary: Delete a specific branch protection for the repository
  704. // produces:
  705. // - application/json
  706. // parameters:
  707. // - name: owner
  708. // in: path
  709. // description: owner of the repo
  710. // type: string
  711. // required: true
  712. // - name: repo
  713. // in: path
  714. // description: name of the repo
  715. // type: string
  716. // required: true
  717. // - name: name
  718. // in: path
  719. // description: name of protected branch
  720. // type: string
  721. // required: true
  722. // responses:
  723. // "204":
  724. // "$ref": "#/responses/empty"
  725. // "404":
  726. // "$ref": "#/responses/notFound"
  727. repo := ctx.Repo.Repository
  728. bpName := ctx.Params(":name")
  729. bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
  730. if err != nil {
  731. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  732. return
  733. }
  734. if bp == nil || bp.RepoID != repo.ID {
  735. ctx.NotFound()
  736. return
  737. }
  738. if err := ctx.Repo.Repository.DeleteProtectedBranch(bp.ID); err != nil {
  739. ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
  740. return
  741. }
  742. ctx.Status(http.StatusNoContent)
  743. }