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.

setting_protected_branch.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package repo
  4. import (
  5. "fmt"
  6. "net/http"
  7. "strings"
  8. "time"
  9. git_model "code.gitea.io/gitea/models/git"
  10. "code.gitea.io/gitea/models/organization"
  11. "code.gitea.io/gitea/models/perm"
  12. access_model "code.gitea.io/gitea/models/perm/access"
  13. repo_model "code.gitea.io/gitea/models/repo"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/git"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/web"
  20. "code.gitea.io/gitea/services/forms"
  21. pull_service "code.gitea.io/gitea/services/pull"
  22. "code.gitea.io/gitea/services/repository"
  23. )
  24. const (
  25. tplProtectedBranch base.TplName = "repo/settings/protected_branch"
  26. )
  27. // ProtectedBranchRules render the page to protect the repository
  28. func ProtectedBranchRules(ctx *context.Context) {
  29. ctx.Data["Title"] = ctx.Tr("repo.settings.branches")
  30. ctx.Data["PageIsSettingsBranches"] = true
  31. rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
  32. if err != nil {
  33. ctx.ServerError("GetProtectedBranches", err)
  34. return
  35. }
  36. ctx.Data["ProtectedBranches"] = rules
  37. ctx.HTML(http.StatusOK, tplBranches)
  38. }
  39. // SetDefaultBranchPost set default branch
  40. func SetDefaultBranchPost(ctx *context.Context) {
  41. ctx.Data["Title"] = ctx.Tr("repo.settings.branches.update_default_branch")
  42. ctx.Data["PageIsSettingsBranches"] = true
  43. repo := ctx.Repo.Repository
  44. switch ctx.FormString("action") {
  45. case "default_branch":
  46. if ctx.HasError() {
  47. ctx.HTML(http.StatusOK, tplBranches)
  48. return
  49. }
  50. branch := ctx.FormString("branch")
  51. if !ctx.Repo.GitRepo.IsBranchExist(branch) {
  52. ctx.Status(http.StatusNotFound)
  53. return
  54. } else if repo.DefaultBranch != branch {
  55. repo.DefaultBranch = branch
  56. if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
  57. if !git.IsErrUnsupportedVersion(err) {
  58. ctx.ServerError("SetDefaultBranch", err)
  59. return
  60. }
  61. }
  62. if err := repo_model.UpdateDefaultBranch(repo); err != nil {
  63. ctx.ServerError("SetDefaultBranch", err)
  64. return
  65. }
  66. }
  67. log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  68. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  69. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
  70. default:
  71. ctx.NotFound("", nil)
  72. }
  73. }
  74. // SettingsProtectedBranch renders the protected branch setting page
  75. func SettingsProtectedBranch(c *context.Context) {
  76. ruleName := c.FormString("rule_name")
  77. var rule *git_model.ProtectedBranch
  78. if ruleName != "" {
  79. var err error
  80. rule, err = git_model.GetProtectedBranchRuleByName(c, c.Repo.Repository.ID, ruleName)
  81. if err != nil {
  82. c.ServerError("GetProtectBranchOfRepoByName", err)
  83. return
  84. }
  85. }
  86. if rule == nil {
  87. // No options found, create defaults.
  88. rule = &git_model.ProtectedBranch{}
  89. }
  90. c.Data["PageIsSettingsBranches"] = true
  91. c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + rule.RuleName
  92. users, err := access_model.GetRepoReaders(c.Repo.Repository)
  93. if err != nil {
  94. c.ServerError("Repo.Repository.GetReaders", err)
  95. return
  96. }
  97. c.Data["Users"] = users
  98. c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",")
  99. c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",")
  100. c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",")
  101. contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c, c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
  102. for _, ctx := range rule.StatusCheckContexts {
  103. var found bool
  104. for i := range contexts {
  105. if contexts[i] == ctx {
  106. found = true
  107. break
  108. }
  109. }
  110. if !found {
  111. contexts = append(contexts, ctx)
  112. }
  113. }
  114. c.Data["branch_status_check_contexts"] = contexts
  115. c.Data["is_context_required"] = func(context string) bool {
  116. for _, c := range rule.StatusCheckContexts {
  117. if c == context {
  118. return true
  119. }
  120. }
  121. return false
  122. }
  123. if c.Repo.Owner.IsOrganization() {
  124. teams, err := organization.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c.Repo.Repository.ID, perm.AccessModeRead)
  125. if err != nil {
  126. c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
  127. return
  128. }
  129. c.Data["Teams"] = teams
  130. c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",")
  131. c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",")
  132. c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",")
  133. }
  134. c.Data["Rule"] = rule
  135. c.HTML(http.StatusOK, tplProtectedBranch)
  136. }
  137. // SettingsProtectedBranchPost updates the protected branch settings
  138. func SettingsProtectedBranchPost(ctx *context.Context) {
  139. f := web.GetForm(ctx).(*forms.ProtectBranchForm)
  140. var protectBranch *git_model.ProtectedBranch
  141. if f.RuleName == "" {
  142. ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_rule_name"))
  143. ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit", ctx.Repo.RepoLink))
  144. return
  145. }
  146. var err error
  147. protectBranch, err = git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName)
  148. if err != nil {
  149. ctx.ServerError("GetProtectBranchOfRepoByName", err)
  150. return
  151. }
  152. if protectBranch == nil {
  153. // No options found, create defaults.
  154. protectBranch = &git_model.ProtectedBranch{
  155. RepoID: ctx.Repo.Repository.ID,
  156. RuleName: f.RuleName,
  157. }
  158. }
  159. var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
  160. protectBranch.RuleName = f.RuleName
  161. if f.RequiredApprovals < 0 {
  162. ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
  163. ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, f.RuleName))
  164. return
  165. }
  166. switch f.EnablePush {
  167. case "all":
  168. protectBranch.CanPush = true
  169. protectBranch.EnableWhitelist = false
  170. protectBranch.WhitelistDeployKeys = false
  171. case "whitelist":
  172. protectBranch.CanPush = true
  173. protectBranch.EnableWhitelist = true
  174. protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys
  175. if strings.TrimSpace(f.WhitelistUsers) != "" {
  176. whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ","))
  177. }
  178. if strings.TrimSpace(f.WhitelistTeams) != "" {
  179. whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ","))
  180. }
  181. default:
  182. protectBranch.CanPush = false
  183. protectBranch.EnableWhitelist = false
  184. protectBranch.WhitelistDeployKeys = false
  185. }
  186. protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
  187. if f.EnableMergeWhitelist {
  188. if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
  189. mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ","))
  190. }
  191. if strings.TrimSpace(f.MergeWhitelistTeams) != "" {
  192. mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ","))
  193. }
  194. }
  195. protectBranch.EnableStatusCheck = f.EnableStatusCheck
  196. if f.EnableStatusCheck {
  197. protectBranch.StatusCheckContexts = f.StatusCheckContexts
  198. } else {
  199. protectBranch.StatusCheckContexts = nil
  200. }
  201. protectBranch.RequiredApprovals = f.RequiredApprovals
  202. protectBranch.EnableApprovalsWhitelist = f.EnableApprovalsWhitelist
  203. if f.EnableApprovalsWhitelist {
  204. if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
  205. approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ","))
  206. }
  207. if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" {
  208. approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ","))
  209. }
  210. }
  211. protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
  212. protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
  213. protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
  214. protectBranch.RequireSignedCommits = f.RequireSignedCommits
  215. protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
  216. protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
  217. protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
  218. err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
  219. UserIDs: whitelistUsers,
  220. TeamIDs: whitelistTeams,
  221. MergeUserIDs: mergeWhitelistUsers,
  222. MergeTeamIDs: mergeWhitelistTeams,
  223. ApprovalsUserIDs: approvalsWhitelistUsers,
  224. ApprovalsTeamIDs: approvalsWhitelistTeams,
  225. })
  226. if err != nil {
  227. ctx.ServerError("UpdateProtectBranch", err)
  228. return
  229. }
  230. // FIXME: since we only need to recheck files protected rules, we could improve this
  231. matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
  232. if err != nil {
  233. ctx.ServerError("FindAllMatchedBranches", err)
  234. return
  235. }
  236. for _, branchName := range matchedBranches {
  237. if err = pull_service.CheckPRsForBaseBranch(ctx.Repo.Repository, branchName); err != nil {
  238. ctx.ServerError("CheckPRsForBaseBranch", err)
  239. return
  240. }
  241. }
  242. ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", protectBranch.RuleName))
  243. ctx.Redirect(fmt.Sprintf("%s/settings/branches?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName))
  244. }
  245. // DeleteProtectedBranchRulePost delete protected branch rule by id
  246. func DeleteProtectedBranchRulePost(ctx *context.Context) {
  247. ruleID := ctx.ParamsInt64("id")
  248. if ruleID <= 0 {
  249. ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
  250. ctx.JSON(http.StatusOK, map[string]interface{}{
  251. "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
  252. })
  253. return
  254. }
  255. rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID)
  256. if err != nil {
  257. ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
  258. ctx.JSON(http.StatusOK, map[string]interface{}{
  259. "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
  260. })
  261. return
  262. }
  263. if rule == nil {
  264. ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
  265. ctx.JSON(http.StatusOK, map[string]interface{}{
  266. "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
  267. })
  268. return
  269. }
  270. if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, ruleID); err != nil {
  271. ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName))
  272. ctx.JSON(http.StatusOK, map[string]interface{}{
  273. "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
  274. })
  275. return
  276. }
  277. ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName))
  278. ctx.JSON(http.StatusOK, map[string]interface{}{
  279. "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
  280. })
  281. }
  282. // RenameBranchPost responses for rename a branch
  283. func RenameBranchPost(ctx *context.Context) {
  284. form := web.GetForm(ctx).(*forms.RenameBranchForm)
  285. if !ctx.Repo.CanCreateBranch() {
  286. ctx.NotFound("RenameBranch", nil)
  287. return
  288. }
  289. if ctx.HasError() {
  290. ctx.Flash.Error(ctx.GetErrMsg())
  291. ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
  292. return
  293. }
  294. msg, err := repository.RenameBranch(ctx.Repo.Repository, ctx.Doer, ctx.Repo.GitRepo, form.From, form.To)
  295. if err != nil {
  296. ctx.ServerError("RenameBranch", err)
  297. return
  298. }
  299. if msg == "target_exist" {
  300. ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To))
  301. ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
  302. return
  303. }
  304. if msg == "from_not_exist" {
  305. ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From))
  306. ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
  307. return
  308. }
  309. ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To))
  310. ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
  311. }