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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  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. UnprotectedFilePatterns: form.UnprotectedFilePatterns,
  457. BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
  458. }
  459. err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
  460. UserIDs: whitelistUsers,
  461. TeamIDs: whitelistTeams,
  462. MergeUserIDs: mergeWhitelistUsers,
  463. MergeTeamIDs: mergeWhitelistTeams,
  464. ApprovalsUserIDs: approvalsWhitelistUsers,
  465. ApprovalsTeamIDs: approvalsWhitelistTeams,
  466. })
  467. if err != nil {
  468. ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
  469. return
  470. }
  471. if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil {
  472. ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
  473. return
  474. }
  475. // Reload from db to get all whitelists
  476. bp, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, form.BranchName)
  477. if err != nil {
  478. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  479. return
  480. }
  481. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  482. ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
  483. return
  484. }
  485. ctx.JSON(http.StatusCreated, convert.ToBranchProtection(bp))
  486. }
  487. // EditBranchProtection edits a branch protection for a repo
  488. func EditBranchProtection(ctx *context.APIContext) {
  489. // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
  490. // ---
  491. // summary: Edit a branch protections for a repository. Only fields that are set will be changed
  492. // consumes:
  493. // - application/json
  494. // produces:
  495. // - application/json
  496. // parameters:
  497. // - name: owner
  498. // in: path
  499. // description: owner of the repo
  500. // type: string
  501. // required: true
  502. // - name: repo
  503. // in: path
  504. // description: name of the repo
  505. // type: string
  506. // required: true
  507. // - name: name
  508. // in: path
  509. // description: name of protected branch
  510. // type: string
  511. // required: true
  512. // - name: body
  513. // in: body
  514. // schema:
  515. // "$ref": "#/definitions/EditBranchProtectionOption"
  516. // responses:
  517. // "200":
  518. // "$ref": "#/responses/BranchProtection"
  519. // "404":
  520. // "$ref": "#/responses/notFound"
  521. // "422":
  522. // "$ref": "#/responses/validationError"
  523. form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
  524. repo := ctx.Repo.Repository
  525. bpName := ctx.Params(":name")
  526. protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName)
  527. if err != nil {
  528. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  529. return
  530. }
  531. if protectBranch == nil || protectBranch.RepoID != repo.ID {
  532. ctx.NotFound()
  533. return
  534. }
  535. if form.EnablePush != nil {
  536. if !*form.EnablePush {
  537. protectBranch.CanPush = false
  538. protectBranch.EnableWhitelist = false
  539. protectBranch.WhitelistDeployKeys = false
  540. } else {
  541. protectBranch.CanPush = true
  542. if form.EnablePushWhitelist != nil {
  543. if !*form.EnablePushWhitelist {
  544. protectBranch.EnableWhitelist = false
  545. protectBranch.WhitelistDeployKeys = false
  546. } else {
  547. protectBranch.EnableWhitelist = true
  548. if form.PushWhitelistDeployKeys != nil {
  549. protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys
  550. }
  551. }
  552. }
  553. }
  554. }
  555. if form.EnableMergeWhitelist != nil {
  556. protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
  557. }
  558. if form.EnableStatusCheck != nil {
  559. protectBranch.EnableStatusCheck = *form.EnableStatusCheck
  560. }
  561. if protectBranch.EnableStatusCheck {
  562. protectBranch.StatusCheckContexts = form.StatusCheckContexts
  563. }
  564. if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 {
  565. protectBranch.RequiredApprovals = *form.RequiredApprovals
  566. }
  567. if form.EnableApprovalsWhitelist != nil {
  568. protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
  569. }
  570. if form.BlockOnRejectedReviews != nil {
  571. protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
  572. }
  573. if form.BlockOnOfficialReviewRequests != nil {
  574. protectBranch.BlockOnOfficialReviewRequests = *form.BlockOnOfficialReviewRequests
  575. }
  576. if form.DismissStaleApprovals != nil {
  577. protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
  578. }
  579. if form.RequireSignedCommits != nil {
  580. protectBranch.RequireSignedCommits = *form.RequireSignedCommits
  581. }
  582. if form.ProtectedFilePatterns != nil {
  583. protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns
  584. }
  585. if form.UnprotectedFilePatterns != nil {
  586. protectBranch.UnprotectedFilePatterns = *form.UnprotectedFilePatterns
  587. }
  588. if form.BlockOnOutdatedBranch != nil {
  589. protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
  590. }
  591. var whitelistUsers []int64
  592. if form.PushWhitelistUsernames != nil {
  593. whitelistUsers, err = models.GetUserIDsByNames(form.PushWhitelistUsernames, false)
  594. if err != nil {
  595. if models.IsErrUserNotExist(err) {
  596. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  597. return
  598. }
  599. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  600. return
  601. }
  602. } else {
  603. whitelistUsers = protectBranch.WhitelistUserIDs
  604. }
  605. var mergeWhitelistUsers []int64
  606. if form.MergeWhitelistUsernames != nil {
  607. mergeWhitelistUsers, err = models.GetUserIDsByNames(form.MergeWhitelistUsernames, false)
  608. if err != nil {
  609. if models.IsErrUserNotExist(err) {
  610. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  611. return
  612. }
  613. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  614. return
  615. }
  616. } else {
  617. mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
  618. }
  619. var approvalsWhitelistUsers []int64
  620. if form.ApprovalsWhitelistUsernames != nil {
  621. approvalsWhitelistUsers, err = models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false)
  622. if err != nil {
  623. if models.IsErrUserNotExist(err) {
  624. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  625. return
  626. }
  627. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  628. return
  629. }
  630. } else {
  631. approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
  632. }
  633. var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  634. if repo.Owner.IsOrganization() {
  635. if form.PushWhitelistTeams != nil {
  636. whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false)
  637. if err != nil {
  638. if models.IsErrTeamNotExist(err) {
  639. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  640. return
  641. }
  642. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  643. return
  644. }
  645. } else {
  646. whitelistTeams = protectBranch.WhitelistTeamIDs
  647. }
  648. if form.MergeWhitelistTeams != nil {
  649. mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false)
  650. if err != nil {
  651. if models.IsErrTeamNotExist(err) {
  652. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  653. return
  654. }
  655. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  656. return
  657. }
  658. } else {
  659. mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs
  660. }
  661. if form.ApprovalsWhitelistTeams != nil {
  662. approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  663. if err != nil {
  664. if models.IsErrTeamNotExist(err) {
  665. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  666. return
  667. }
  668. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  669. return
  670. }
  671. } else {
  672. approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
  673. }
  674. }
  675. err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
  676. UserIDs: whitelistUsers,
  677. TeamIDs: whitelistTeams,
  678. MergeUserIDs: mergeWhitelistUsers,
  679. MergeTeamIDs: mergeWhitelistTeams,
  680. ApprovalsUserIDs: approvalsWhitelistUsers,
  681. ApprovalsTeamIDs: approvalsWhitelistTeams,
  682. })
  683. if err != nil {
  684. ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
  685. return
  686. }
  687. if err = pull_service.CheckPrsForBaseBranch(ctx.Repo.Repository, protectBranch.BranchName); err != nil {
  688. ctx.Error(http.StatusInternalServerError, "CheckPrsForBaseBranch", err)
  689. return
  690. }
  691. // Reload from db to ensure get all whitelists
  692. bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
  693. if err != nil {
  694. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
  695. return
  696. }
  697. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  698. ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
  699. return
  700. }
  701. ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp))
  702. }
  703. // DeleteBranchProtection deletes a branch protection for a repo
  704. func DeleteBranchProtection(ctx *context.APIContext) {
  705. // swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
  706. // ---
  707. // summary: Delete a specific branch protection for the repository
  708. // produces:
  709. // - application/json
  710. // parameters:
  711. // - name: owner
  712. // in: path
  713. // description: owner of the repo
  714. // type: string
  715. // required: true
  716. // - name: repo
  717. // in: path
  718. // description: name of the repo
  719. // type: string
  720. // required: true
  721. // - name: name
  722. // in: path
  723. // description: name of protected branch
  724. // type: string
  725. // required: true
  726. // responses:
  727. // "204":
  728. // "$ref": "#/responses/empty"
  729. // "404":
  730. // "$ref": "#/responses/notFound"
  731. repo := ctx.Repo.Repository
  732. bpName := ctx.Params(":name")
  733. bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
  734. if err != nil {
  735. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  736. return
  737. }
  738. if bp == nil || bp.RepoID != repo.ID {
  739. ctx.NotFound()
  740. return
  741. }
  742. if err := ctx.Repo.Repository.DeleteProtectedBranch(bp.ID); err != nil {
  743. ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
  744. return
  745. }
  746. ctx.Status(http.StatusNoContent)
  747. }