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

branch.go 22 kB

Improve listing performance by using go-git (#6478) * Use go-git for tree reading and commit info lookup. Signed-off-by: Filip Navara <navara@emclient.com> * Use TreeEntry.IsRegular() instead of ObjectType that was removed. Signed-off-by: Filip Navara <navara@emclient.com> * Use the treePath to optimize commit info search. Signed-off-by: Filip Navara <navara@emclient.com> * Extract the latest commit at treePath along with the other commits. Signed-off-by: Filip Navara <navara@emclient.com> * Fix listing commit info for a directory that was created in one commit and never modified after. Signed-off-by: Filip Navara <navara@emclient.com> * Avoid nearly all external 'git' invocations when doing directory listing (.editorconfig code path is still hit). Signed-off-by: Filip Navara <navara@emclient.com> * Use go-git for reading blobs. Signed-off-by: Filip Navara <navara@emclient.com> * Make SHA1 type alias for plumbing.Hash in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Make Signature type alias for object.Signature in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Fix GetCommitsInfo for repository with only one commit. Signed-off-by: Filip Navara <navara@emclient.com> * Fix PGP signature verification. Signed-off-by: Filip Navara <navara@emclient.com> * Fix issues with walking commit graph across merges. Signed-off-by: Filip Navara <navara@emclient.com> * Fix typo in condition. Signed-off-by: Filip Navara <navara@emclient.com> * Speed up loading branch list by keeping the repository reference (and thus all the loaded packfile indexes). Signed-off-by: Filip Navara <navara@emclient.com> * Fix lising submodules. Signed-off-by: Filip Navara <navara@emclient.com> * Fix build Signed-off-by: Filip Navara <navara@emclient.com> * Add back commit cache because of name-rev Signed-off-by: Filip Navara <navara@emclient.com> * Fix tests Signed-off-by: Filip Navara <navara@emclient.com> * Fix code style * Fix spelling * Address PR feedback Signed-off-by: Filip Navara <navara@emclient.com> * Update vendor module list Signed-off-by: Filip Navara <navara@emclient.com> * Fix getting trees by commit id Signed-off-by: Filip Navara <navara@emclient.com> * Fix remaining unit test failures * Fix GetTreeBySHA * Avoid running `git name-rev` if not necessary Signed-off-by: Filip Navara <navara@emclient.com> * Move Branch code to git module * Clean up GPG signature verification and fix it for tagged commits * Address PR feedback (import formatting, copyright headers) * Make blob lookup by SHA working * Update tests to use public API * Allow getting content from any type of object through the blob interface * Change test to actually expect the object content that is in the GIT repository * Change one more test to actually expect the object content that is in the GIT repository * Add comments
6 years ago
9 years ago
Improve listing performance by using go-git (#6478) * Use go-git for tree reading and commit info lookup. Signed-off-by: Filip Navara <navara@emclient.com> * Use TreeEntry.IsRegular() instead of ObjectType that was removed. Signed-off-by: Filip Navara <navara@emclient.com> * Use the treePath to optimize commit info search. Signed-off-by: Filip Navara <navara@emclient.com> * Extract the latest commit at treePath along with the other commits. Signed-off-by: Filip Navara <navara@emclient.com> * Fix listing commit info for a directory that was created in one commit and never modified after. Signed-off-by: Filip Navara <navara@emclient.com> * Avoid nearly all external 'git' invocations when doing directory listing (.editorconfig code path is still hit). Signed-off-by: Filip Navara <navara@emclient.com> * Use go-git for reading blobs. Signed-off-by: Filip Navara <navara@emclient.com> * Make SHA1 type alias for plumbing.Hash in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Make Signature type alias for object.Signature in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Fix GetCommitsInfo for repository with only one commit. Signed-off-by: Filip Navara <navara@emclient.com> * Fix PGP signature verification. Signed-off-by: Filip Navara <navara@emclient.com> * Fix issues with walking commit graph across merges. Signed-off-by: Filip Navara <navara@emclient.com> * Fix typo in condition. Signed-off-by: Filip Navara <navara@emclient.com> * Speed up loading branch list by keeping the repository reference (and thus all the loaded packfile indexes). Signed-off-by: Filip Navara <navara@emclient.com> * Fix lising submodules. Signed-off-by: Filip Navara <navara@emclient.com> * Fix build Signed-off-by: Filip Navara <navara@emclient.com> * Add back commit cache because of name-rev Signed-off-by: Filip Navara <navara@emclient.com> * Fix tests Signed-off-by: Filip Navara <navara@emclient.com> * Fix code style * Fix spelling * Address PR feedback Signed-off-by: Filip Navara <navara@emclient.com> * Update vendor module list Signed-off-by: Filip Navara <navara@emclient.com> * Fix getting trees by commit id Signed-off-by: Filip Navara <navara@emclient.com> * Fix remaining unit test failures * Fix GetTreeBySHA * Avoid running `git name-rev` if not necessary Signed-off-by: Filip Navara <navara@emclient.com> * Move Branch code to git module * Clean up GPG signature verification and fix it for tagged commits * Address PR feedback (import formatting, copyright headers) * Make blob lookup by SHA working * Update tests to use public API * Allow getting content from any type of object through the blob interface * Change test to actually expect the object content that is in the GIT repository * Change one more test to actually expect the object content that is in the GIT repository * Add comments
6 years ago
9 years ago
9 years ago
Improve listing performance by using go-git (#6478) * Use go-git for tree reading and commit info lookup. Signed-off-by: Filip Navara <navara@emclient.com> * Use TreeEntry.IsRegular() instead of ObjectType that was removed. Signed-off-by: Filip Navara <navara@emclient.com> * Use the treePath to optimize commit info search. Signed-off-by: Filip Navara <navara@emclient.com> * Extract the latest commit at treePath along with the other commits. Signed-off-by: Filip Navara <navara@emclient.com> * Fix listing commit info for a directory that was created in one commit and never modified after. Signed-off-by: Filip Navara <navara@emclient.com> * Avoid nearly all external 'git' invocations when doing directory listing (.editorconfig code path is still hit). Signed-off-by: Filip Navara <navara@emclient.com> * Use go-git for reading blobs. Signed-off-by: Filip Navara <navara@emclient.com> * Make SHA1 type alias for plumbing.Hash in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Make Signature type alias for object.Signature in go-git. Signed-off-by: Filip Navara <navara@emclient.com> * Fix GetCommitsInfo for repository with only one commit. Signed-off-by: Filip Navara <navara@emclient.com> * Fix PGP signature verification. Signed-off-by: Filip Navara <navara@emclient.com> * Fix issues with walking commit graph across merges. Signed-off-by: Filip Navara <navara@emclient.com> * Fix typo in condition. Signed-off-by: Filip Navara <navara@emclient.com> * Speed up loading branch list by keeping the repository reference (and thus all the loaded packfile indexes). Signed-off-by: Filip Navara <navara@emclient.com> * Fix lising submodules. Signed-off-by: Filip Navara <navara@emclient.com> * Fix build Signed-off-by: Filip Navara <navara@emclient.com> * Add back commit cache because of name-rev Signed-off-by: Filip Navara <navara@emclient.com> * Fix tests Signed-off-by: Filip Navara <navara@emclient.com> * Fix code style * Fix spelling * Address PR feedback Signed-off-by: Filip Navara <navara@emclient.com> * Update vendor module list Signed-off-by: Filip Navara <navara@emclient.com> * Fix getting trees by commit id Signed-off-by: Filip Navara <navara@emclient.com> * Fix remaining unit test failures * Fix GetTreeBySHA * Avoid running `git name-rev` if not necessary Signed-off-by: Filip Navara <navara@emclient.com> * Move Branch code to git module * Clean up GPG signature verification and fix it for tagged commits * Address PR feedback (import formatting, copyright headers) * Make blob lookup by SHA working * Update tests to use public API * Allow getting content from any type of object through the blob interface * Change test to actually expect the object content that is in the GIT repository * Change one more test to actually expect the object content that is in the GIT repository * Add comments
6 years ago
9 years ago
9 years ago
9 years ago
9 years ago
3 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  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. "fmt"
  8. "net/http"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/convert"
  12. "code.gitea.io/gitea/modules/git"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/repofiles"
  15. repo_module "code.gitea.io/gitea/modules/repository"
  16. api "code.gitea.io/gitea/modules/structs"
  17. )
  18. // GetBranch get a branch of a repository
  19. func GetBranch(ctx *context.APIContext) {
  20. // swagger:operation GET /repos/{owner}/{repo}/branches/{branch} repository repoGetBranch
  21. // ---
  22. // summary: Retrieve a specific branch from a repository, including its effective branch protection
  23. // produces:
  24. // - application/json
  25. // parameters:
  26. // - name: owner
  27. // in: path
  28. // description: owner of the repo
  29. // type: string
  30. // required: true
  31. // - name: repo
  32. // in: path
  33. // description: name of the repo
  34. // type: string
  35. // required: true
  36. // - name: branch
  37. // in: path
  38. // description: branch to get
  39. // type: string
  40. // required: true
  41. // responses:
  42. // "200":
  43. // "$ref": "#/responses/Branch"
  44. if ctx.Repo.TreePath != "" {
  45. // if TreePath != "", then URL contained extra slashes
  46. // (i.e. "master/subbranch" instead of "master"), so branch does
  47. // not exist
  48. ctx.NotFound()
  49. return
  50. }
  51. branch, err := repo_module.GetBranch(ctx.Repo.Repository, ctx.Repo.BranchName)
  52. if err != nil {
  53. if git.IsErrBranchNotExist(err) {
  54. ctx.NotFound(err)
  55. } else {
  56. ctx.Error(http.StatusInternalServerError, "GetBranch", err)
  57. }
  58. return
  59. }
  60. c, err := branch.GetCommit()
  61. if err != nil {
  62. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  63. return
  64. }
  65. branchProtection, err := ctx.Repo.Repository.GetBranchProtection(ctx.Repo.BranchName)
  66. if err != nil {
  67. ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
  68. return
  69. }
  70. br, err := convert.ToBranch(ctx.Repo.Repository, branch, c, branchProtection, ctx.User, ctx.Repo.IsAdmin())
  71. if err != nil {
  72. ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
  73. return
  74. }
  75. ctx.JSON(http.StatusOK, br)
  76. }
  77. // DeleteBranch get a branch of a repository
  78. func DeleteBranch(ctx *context.APIContext) {
  79. // swagger:operation DELETE /repos/{owner}/{repo}/branches/{branch} repository repoDeleteBranch
  80. // ---
  81. // summary: Delete a specific branch from a repository
  82. // produces:
  83. // - application/json
  84. // parameters:
  85. // - name: owner
  86. // in: path
  87. // description: owner of the repo
  88. // type: string
  89. // required: true
  90. // - name: repo
  91. // in: path
  92. // description: name of the repo
  93. // type: string
  94. // required: true
  95. // - name: branch
  96. // in: path
  97. // description: branch to delete
  98. // type: string
  99. // required: true
  100. // responses:
  101. // "204":
  102. // "$ref": "#/responses/empty"
  103. // "403":
  104. // "$ref": "#/responses/error"
  105. if ctx.Repo.TreePath != "" {
  106. // if TreePath != "", then URL contained extra slashes
  107. // (i.e. "master/subbranch" instead of "master"), so branch does
  108. // not exist
  109. ctx.NotFound()
  110. return
  111. }
  112. if ctx.Repo.Repository.DefaultBranch == ctx.Repo.BranchName {
  113. ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
  114. return
  115. }
  116. isProtected, err := ctx.Repo.Repository.IsProtectedBranch(ctx.Repo.BranchName, ctx.User)
  117. if err != nil {
  118. ctx.InternalServerError(err)
  119. return
  120. }
  121. if isProtected {
  122. ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
  123. return
  124. }
  125. branch, err := repo_module.GetBranch(ctx.Repo.Repository, ctx.Repo.BranchName)
  126. if err != nil {
  127. if git.IsErrBranchNotExist(err) {
  128. ctx.NotFound(err)
  129. } else {
  130. ctx.Error(http.StatusInternalServerError, "GetBranch", err)
  131. }
  132. return
  133. }
  134. c, err := branch.GetCommit()
  135. if err != nil {
  136. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  137. return
  138. }
  139. if err := ctx.Repo.GitRepo.DeleteBranch(ctx.Repo.BranchName, git.DeleteBranchOptions{
  140. Force: true,
  141. }); err != nil {
  142. ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
  143. return
  144. }
  145. // Don't return error below this
  146. if err := repofiles.PushUpdate(
  147. ctx.Repo.Repository,
  148. ctx.Repo.BranchName,
  149. repofiles.PushUpdateOptions{
  150. RefFullName: git.BranchPrefix + ctx.Repo.BranchName,
  151. OldCommitID: c.ID.String(),
  152. NewCommitID: git.EmptySHA,
  153. PusherID: ctx.User.ID,
  154. PusherName: ctx.User.Name,
  155. RepoUserName: ctx.Repo.Owner.Name,
  156. RepoName: ctx.Repo.Repository.Name,
  157. }); err != nil {
  158. log.Error("Update: %v", err)
  159. }
  160. if err := ctx.Repo.Repository.AddDeletedBranch(ctx.Repo.BranchName, c.ID.String(), ctx.User.ID); err != nil {
  161. log.Warn("AddDeletedBranch: %v", err)
  162. }
  163. ctx.Status(http.StatusNoContent)
  164. }
  165. // ListBranches list all the branches of a repository
  166. func ListBranches(ctx *context.APIContext) {
  167. // swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches
  168. // ---
  169. // summary: List a repository's branches
  170. // produces:
  171. // - application/json
  172. // parameters:
  173. // - name: owner
  174. // in: path
  175. // description: owner of the repo
  176. // type: string
  177. // required: true
  178. // - name: repo
  179. // in: path
  180. // description: name of the repo
  181. // type: string
  182. // required: true
  183. // responses:
  184. // "200":
  185. // "$ref": "#/responses/BranchList"
  186. branches, _, err := repo_module.GetBranches(ctx.Repo.Repository,0,0)
  187. if err != nil {
  188. ctx.Error(http.StatusInternalServerError, "GetBranches", err)
  189. return
  190. }
  191. apiBranches := make([]*api.Branch, len(branches))
  192. for i := range branches {
  193. c, err := branches[i].GetCommit()
  194. if err != nil {
  195. ctx.Error(http.StatusInternalServerError, "GetCommit", err)
  196. return
  197. }
  198. branchProtection, err := ctx.Repo.Repository.GetBranchProtection(branches[i].Name)
  199. if err != nil {
  200. ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
  201. return
  202. }
  203. apiBranches[i], err = convert.ToBranch(ctx.Repo.Repository, branches[i], c, branchProtection, ctx.User, ctx.Repo.IsAdmin())
  204. if err != nil {
  205. ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
  206. return
  207. }
  208. }
  209. ctx.JSON(http.StatusOK, &apiBranches)
  210. }
  211. // GetBranchProtection gets a branch protection
  212. func GetBranchProtection(ctx *context.APIContext) {
  213. // swagger:operation GET /repos/{owner}/{repo}/branch_protections/{name} repository repoGetBranchProtection
  214. // ---
  215. // summary: Get a specific branch protection for the repository
  216. // produces:
  217. // - application/json
  218. // parameters:
  219. // - name: owner
  220. // in: path
  221. // description: owner of the repo
  222. // type: string
  223. // required: true
  224. // - name: repo
  225. // in: path
  226. // description: name of the repo
  227. // type: string
  228. // required: true
  229. // - name: name
  230. // in: path
  231. // description: name of protected branch
  232. // type: string
  233. // required: true
  234. // responses:
  235. // "200":
  236. // "$ref": "#/responses/BranchProtection"
  237. // "404":
  238. // "$ref": "#/responses/notFound"
  239. repo := ctx.Repo.Repository
  240. bpName := ctx.Params(":name")
  241. bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
  242. if err != nil {
  243. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  244. return
  245. }
  246. if bp == nil || bp.RepoID != repo.ID {
  247. ctx.NotFound()
  248. return
  249. }
  250. ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp))
  251. }
  252. // ListBranchProtections list branch protections for a repo
  253. func ListBranchProtections(ctx *context.APIContext) {
  254. // swagger:operation GET /repos/{owner}/{repo}/branch_protections repository repoListBranchProtection
  255. // ---
  256. // summary: List branch protections for a repository
  257. // produces:
  258. // - application/json
  259. // parameters:
  260. // - name: owner
  261. // in: path
  262. // description: owner of the repo
  263. // type: string
  264. // required: true
  265. // - name: repo
  266. // in: path
  267. // description: name of the repo
  268. // type: string
  269. // required: true
  270. // responses:
  271. // "200":
  272. // "$ref": "#/responses/BranchProtectionList"
  273. repo := ctx.Repo.Repository
  274. bps, err := repo.GetProtectedBranches()
  275. if err != nil {
  276. ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
  277. return
  278. }
  279. apiBps := make([]*api.BranchProtection, len(bps))
  280. for i := range bps {
  281. apiBps[i] = convert.ToBranchProtection(bps[i])
  282. }
  283. ctx.JSON(http.StatusOK, apiBps)
  284. }
  285. // CreateBranchProtection creates a branch protection for a repo
  286. func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtectionOption) {
  287. // swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
  288. // ---
  289. // summary: Create a branch protections for a repository
  290. // consumes:
  291. // - application/json
  292. // produces:
  293. // - application/json
  294. // parameters:
  295. // - name: owner
  296. // in: path
  297. // description: owner of the repo
  298. // type: string
  299. // required: true
  300. // - name: repo
  301. // in: path
  302. // description: name of the repo
  303. // type: string
  304. // required: true
  305. // - name: body
  306. // in: body
  307. // schema:
  308. // "$ref": "#/definitions/CreateBranchProtectionOption"
  309. // responses:
  310. // "201":
  311. // "$ref": "#/responses/BranchProtection"
  312. // "403":
  313. // "$ref": "#/responses/forbidden"
  314. // "404":
  315. // "$ref": "#/responses/notFound"
  316. // "422":
  317. // "$ref": "#/responses/validationError"
  318. repo := ctx.Repo.Repository
  319. // Currently protection must match an actual branch
  320. if !git.IsBranchExist(ctx.Repo.Repository.RepoPath(), form.BranchName) {
  321. ctx.NotFound()
  322. return
  323. }
  324. protectBranch, err := models.GetProtectedBranchBy(repo.ID, form.BranchName)
  325. if err != nil {
  326. ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
  327. return
  328. } else if protectBranch != nil {
  329. ctx.Error(http.StatusForbidden, "Create branch protection", "Branch protection already exist")
  330. return
  331. }
  332. var requiredApprovals int64
  333. if form.RequiredApprovals > 0 {
  334. requiredApprovals = form.RequiredApprovals
  335. }
  336. whitelistUsers, err := models.GetUserIDsByNames(form.PushWhitelistUsernames, false)
  337. if err != nil {
  338. if models.IsErrUserNotExist(err) {
  339. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  340. return
  341. }
  342. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  343. return
  344. }
  345. mergeWhitelistUsers, err := models.GetUserIDsByNames(form.MergeWhitelistUsernames, false)
  346. if err != nil {
  347. if models.IsErrUserNotExist(err) {
  348. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  349. return
  350. }
  351. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  352. return
  353. }
  354. approvalsWhitelistUsers, err := models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false)
  355. if err != nil {
  356. if models.IsErrUserNotExist(err) {
  357. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  358. return
  359. }
  360. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  361. return
  362. }
  363. var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  364. if repo.Owner.IsOrganization() {
  365. whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false)
  366. if err != nil {
  367. if models.IsErrTeamNotExist(err) {
  368. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  369. return
  370. }
  371. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  372. return
  373. }
  374. mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false)
  375. if err != nil {
  376. if models.IsErrTeamNotExist(err) {
  377. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  378. return
  379. }
  380. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  381. return
  382. }
  383. approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  384. if err != nil {
  385. if models.IsErrTeamNotExist(err) {
  386. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  387. return
  388. }
  389. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  390. return
  391. }
  392. }
  393. protectBranch = &models.ProtectedBranch{
  394. RepoID: ctx.Repo.Repository.ID,
  395. BranchName: form.BranchName,
  396. CanPush: form.EnablePush,
  397. EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
  398. EnableMergeWhitelist: form.EnableMergeWhitelist,
  399. WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
  400. EnableStatusCheck: form.EnableStatusCheck,
  401. StatusCheckContexts: form.StatusCheckContexts,
  402. EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
  403. RequiredApprovals: requiredApprovals,
  404. BlockOnRejectedReviews: form.BlockOnRejectedReviews,
  405. DismissStaleApprovals: form.DismissStaleApprovals,
  406. RequireSignedCommits: form.RequireSignedCommits,
  407. ProtectedFilePatterns: form.ProtectedFilePatterns,
  408. BlockOnOutdatedBranch: form.BlockOnOutdatedBranch,
  409. }
  410. err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
  411. UserIDs: whitelistUsers,
  412. TeamIDs: whitelistTeams,
  413. MergeUserIDs: mergeWhitelistUsers,
  414. MergeTeamIDs: mergeWhitelistTeams,
  415. ApprovalsUserIDs: approvalsWhitelistUsers,
  416. ApprovalsTeamIDs: approvalsWhitelistTeams,
  417. })
  418. if err != nil {
  419. ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
  420. return
  421. }
  422. // Reload from db to get all whitelists
  423. bp, err := models.GetProtectedBranchBy(ctx.Repo.Repository.ID, form.BranchName)
  424. if err != nil {
  425. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  426. return
  427. }
  428. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  429. ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
  430. return
  431. }
  432. ctx.JSON(http.StatusCreated, convert.ToBranchProtection(bp))
  433. }
  434. // EditBranchProtection edits a branch protection for a repo
  435. func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtectionOption) {
  436. // swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
  437. // ---
  438. // summary: Edit a branch protections for a repository. Only fields that are set will be changed
  439. // consumes:
  440. // - application/json
  441. // produces:
  442. // - application/json
  443. // parameters:
  444. // - name: owner
  445. // in: path
  446. // description: owner of the repo
  447. // type: string
  448. // required: true
  449. // - name: repo
  450. // in: path
  451. // description: name of the repo
  452. // type: string
  453. // required: true
  454. // - name: name
  455. // in: path
  456. // description: name of protected branch
  457. // type: string
  458. // required: true
  459. // - name: body
  460. // in: body
  461. // schema:
  462. // "$ref": "#/definitions/EditBranchProtectionOption"
  463. // responses:
  464. // "200":
  465. // "$ref": "#/responses/BranchProtection"
  466. // "404":
  467. // "$ref": "#/responses/notFound"
  468. // "422":
  469. // "$ref": "#/responses/validationError"
  470. repo := ctx.Repo.Repository
  471. bpName := ctx.Params(":name")
  472. protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName)
  473. if err != nil {
  474. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  475. return
  476. }
  477. if protectBranch == nil || protectBranch.RepoID != repo.ID {
  478. ctx.NotFound()
  479. return
  480. }
  481. if form.EnablePush != nil {
  482. if !*form.EnablePush {
  483. protectBranch.CanPush = false
  484. protectBranch.EnableWhitelist = false
  485. protectBranch.WhitelistDeployKeys = false
  486. } else {
  487. protectBranch.CanPush = true
  488. if form.EnablePushWhitelist != nil {
  489. if !*form.EnablePushWhitelist {
  490. protectBranch.EnableWhitelist = false
  491. protectBranch.WhitelistDeployKeys = false
  492. } else {
  493. protectBranch.EnableWhitelist = true
  494. if form.PushWhitelistDeployKeys != nil {
  495. protectBranch.WhitelistDeployKeys = *form.PushWhitelistDeployKeys
  496. }
  497. }
  498. }
  499. }
  500. }
  501. if form.EnableMergeWhitelist != nil {
  502. protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
  503. }
  504. if form.EnableStatusCheck != nil {
  505. protectBranch.EnableStatusCheck = *form.EnableStatusCheck
  506. }
  507. if protectBranch.EnableStatusCheck {
  508. protectBranch.StatusCheckContexts = form.StatusCheckContexts
  509. }
  510. if form.RequiredApprovals != nil && *form.RequiredApprovals >= 0 {
  511. protectBranch.RequiredApprovals = *form.RequiredApprovals
  512. }
  513. if form.EnableApprovalsWhitelist != nil {
  514. protectBranch.EnableApprovalsWhitelist = *form.EnableApprovalsWhitelist
  515. }
  516. if form.BlockOnRejectedReviews != nil {
  517. protectBranch.BlockOnRejectedReviews = *form.BlockOnRejectedReviews
  518. }
  519. if form.DismissStaleApprovals != nil {
  520. protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
  521. }
  522. if form.RequireSignedCommits != nil {
  523. protectBranch.RequireSignedCommits = *form.RequireSignedCommits
  524. }
  525. if form.ProtectedFilePatterns != nil {
  526. protectBranch.ProtectedFilePatterns = *form.ProtectedFilePatterns
  527. }
  528. if form.BlockOnOutdatedBranch != nil {
  529. protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
  530. }
  531. var whitelistUsers []int64
  532. if form.PushWhitelistUsernames != nil {
  533. whitelistUsers, err = models.GetUserIDsByNames(form.PushWhitelistUsernames, false)
  534. if err != nil {
  535. if models.IsErrUserNotExist(err) {
  536. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  537. return
  538. }
  539. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  540. return
  541. }
  542. } else {
  543. whitelistUsers = protectBranch.WhitelistUserIDs
  544. }
  545. var mergeWhitelistUsers []int64
  546. if form.MergeWhitelistUsernames != nil {
  547. mergeWhitelistUsers, err = models.GetUserIDsByNames(form.MergeWhitelistUsernames, false)
  548. if err != nil {
  549. if models.IsErrUserNotExist(err) {
  550. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  551. return
  552. }
  553. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  554. return
  555. }
  556. } else {
  557. mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
  558. }
  559. var approvalsWhitelistUsers []int64
  560. if form.ApprovalsWhitelistUsernames != nil {
  561. approvalsWhitelistUsers, err = models.GetUserIDsByNames(form.ApprovalsWhitelistUsernames, false)
  562. if err != nil {
  563. if models.IsErrUserNotExist(err) {
  564. ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
  565. return
  566. }
  567. ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
  568. return
  569. }
  570. } else {
  571. approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
  572. }
  573. var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
  574. if repo.Owner.IsOrganization() {
  575. if form.PushWhitelistTeams != nil {
  576. whitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.PushWhitelistTeams, false)
  577. if err != nil {
  578. if models.IsErrTeamNotExist(err) {
  579. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  580. return
  581. }
  582. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  583. return
  584. }
  585. } else {
  586. whitelistTeams = protectBranch.WhitelistTeamIDs
  587. }
  588. if form.MergeWhitelistTeams != nil {
  589. mergeWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.MergeWhitelistTeams, false)
  590. if err != nil {
  591. if models.IsErrTeamNotExist(err) {
  592. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  593. return
  594. }
  595. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  596. return
  597. }
  598. } else {
  599. mergeWhitelistTeams = protectBranch.MergeWhitelistTeamIDs
  600. }
  601. if form.ApprovalsWhitelistTeams != nil {
  602. approvalsWhitelistTeams, err = models.GetTeamIDsByNames(repo.OwnerID, form.ApprovalsWhitelistTeams, false)
  603. if err != nil {
  604. if models.IsErrTeamNotExist(err) {
  605. ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
  606. return
  607. }
  608. ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
  609. return
  610. }
  611. } else {
  612. approvalsWhitelistTeams = protectBranch.ApprovalsWhitelistTeamIDs
  613. }
  614. }
  615. err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
  616. UserIDs: whitelistUsers,
  617. TeamIDs: whitelistTeams,
  618. MergeUserIDs: mergeWhitelistUsers,
  619. MergeTeamIDs: mergeWhitelistTeams,
  620. ApprovalsUserIDs: approvalsWhitelistUsers,
  621. ApprovalsTeamIDs: approvalsWhitelistTeams,
  622. })
  623. if err != nil {
  624. ctx.Error(http.StatusInternalServerError, "UpdateProtectBranch", err)
  625. return
  626. }
  627. // Reload from db to ensure get all whitelists
  628. bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
  629. if err != nil {
  630. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
  631. return
  632. }
  633. if bp == nil || bp.RepoID != ctx.Repo.Repository.ID {
  634. ctx.Error(http.StatusInternalServerError, "New branch protection not found", err)
  635. return
  636. }
  637. ctx.JSON(http.StatusOK, convert.ToBranchProtection(bp))
  638. }
  639. // DeleteBranchProtection deletes a branch protection for a repo
  640. func DeleteBranchProtection(ctx *context.APIContext) {
  641. // swagger:operation DELETE /repos/{owner}/{repo}/branch_protections/{name} repository repoDeleteBranchProtection
  642. // ---
  643. // summary: Delete a specific branch protection for the repository
  644. // produces:
  645. // - application/json
  646. // parameters:
  647. // - name: owner
  648. // in: path
  649. // description: owner of the repo
  650. // type: string
  651. // required: true
  652. // - name: repo
  653. // in: path
  654. // description: name of the repo
  655. // type: string
  656. // required: true
  657. // - name: name
  658. // in: path
  659. // description: name of protected branch
  660. // type: string
  661. // required: true
  662. // responses:
  663. // "204":
  664. // "$ref": "#/responses/empty"
  665. // "404":
  666. // "$ref": "#/responses/notFound"
  667. repo := ctx.Repo.Repository
  668. bpName := ctx.Params(":name")
  669. bp, err := models.GetProtectedBranchBy(repo.ID, bpName)
  670. if err != nil {
  671. ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
  672. return
  673. }
  674. if bp == nil || bp.RepoID != repo.ID {
  675. ctx.NotFound()
  676. return
  677. }
  678. if err := ctx.Repo.Repository.DeleteProtectedBranch(bp.ID); err != nil {
  679. ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
  680. return
  681. }
  682. ctx.Status(http.StatusNoContent)
  683. }