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
10 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
10 years ago
10 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
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 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)
  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. }