gitea源码

protected_branch.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package setting
  4. import (
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. "time"
  12. git_model "code.gitea.io/gitea/models/git"
  13. "code.gitea.io/gitea/models/organization"
  14. "code.gitea.io/gitea/models/perm"
  15. access_model "code.gitea.io/gitea/models/perm/access"
  16. repo_model "code.gitea.io/gitea/models/repo"
  17. "code.gitea.io/gitea/models/unit"
  18. "code.gitea.io/gitea/modules/base"
  19. "code.gitea.io/gitea/modules/glob"
  20. "code.gitea.io/gitea/modules/templates"
  21. "code.gitea.io/gitea/modules/web"
  22. "code.gitea.io/gitea/routers/web/repo"
  23. "code.gitea.io/gitea/services/context"
  24. "code.gitea.io/gitea/services/forms"
  25. pull_service "code.gitea.io/gitea/services/pull"
  26. "code.gitea.io/gitea/services/repository"
  27. )
  28. const (
  29. tplProtectedBranch templates.TplName = "repo/settings/protected_branch"
  30. )
  31. // ProtectedBranchRules render the page to protect the repository
  32. func ProtectedBranchRules(ctx *context.Context) {
  33. ctx.Data["Title"] = ctx.Tr("repo.settings.branches")
  34. ctx.Data["PageIsSettingsBranches"] = true
  35. rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
  36. if err != nil {
  37. ctx.ServerError("GetProtectedBranches", err)
  38. return
  39. }
  40. ctx.Data["ProtectedBranches"] = rules
  41. repo.PrepareBranchList(ctx)
  42. if ctx.Written() {
  43. return
  44. }
  45. ctx.HTML(http.StatusOK, tplBranches)
  46. }
  47. // SettingsProtectedBranch renders the protected branch setting page
  48. func SettingsProtectedBranch(c *context.Context) {
  49. ruleName := c.FormString("rule_name")
  50. var rule *git_model.ProtectedBranch
  51. if ruleName != "" {
  52. var err error
  53. rule, err = git_model.GetProtectedBranchRuleByName(c, c.Repo.Repository.ID, ruleName)
  54. if err != nil {
  55. c.ServerError("GetProtectBranchOfRepoByName", err)
  56. return
  57. }
  58. }
  59. if rule == nil {
  60. // No options found, create defaults.
  61. rule = &git_model.ProtectedBranch{}
  62. }
  63. c.Data["PageIsSettingsBranches"] = true
  64. c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName
  65. users, err := access_model.GetRepoReaders(c, c.Repo.Repository)
  66. if err != nil {
  67. c.ServerError("Repo.Repository.GetReaders", err)
  68. return
  69. }
  70. c.Data["Users"] = users
  71. c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",")
  72. c.Data["force_push_allowlist_users"] = strings.Join(base.Int64sToStrings(rule.ForcePushAllowlistUserIDs), ",")
  73. c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",")
  74. c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",")
  75. c.Data["status_check_contexts"] = strings.Join(rule.StatusCheckContexts, "\n")
  76. contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c, c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
  77. c.Data["recent_status_checks"] = contexts
  78. if c.Repo.Owner.IsOrganization() {
  79. teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(c, c.Repo.Owner.ID, c.Repo.Repository.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
  80. if err != nil {
  81. c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
  82. return
  83. }
  84. c.Data["Teams"] = teams
  85. c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",")
  86. c.Data["force_push_allowlist_teams"] = strings.Join(base.Int64sToStrings(rule.ForcePushAllowlistTeamIDs), ",")
  87. c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",")
  88. c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",")
  89. }
  90. c.Data["Rule"] = rule
  91. c.HTML(http.StatusOK, tplProtectedBranch)
  92. }
  93. // SettingsProtectedBranchPost updates the protected branch settings
  94. func SettingsProtectedBranchPost(ctx *context.Context) {
  95. f := web.GetForm(ctx).(*forms.ProtectBranchForm)
  96. var protectBranch *git_model.ProtectedBranch
  97. if f.RuleName == "" {
  98. ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_rule_name"))
  99. ctx.Redirect(ctx.Repo.RepoLink + "/settings/branches/edit")
  100. return
  101. }
  102. var err error
  103. if f.RuleID > 0 {
  104. // If the RuleID isn't 0, it must be an edit operation. So we get rule by id.
  105. protectBranch, err = git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, f.RuleID)
  106. if err != nil {
  107. ctx.ServerError("GetProtectBranchOfRepoByID", err)
  108. return
  109. }
  110. if protectBranch != nil && protectBranch.RuleName != f.RuleName {
  111. // RuleName changed. We need to check if there is a rule with the same name.
  112. // If a rule with the same name exists, an error should be returned.
  113. sameNameProtectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName)
  114. if err != nil {
  115. ctx.ServerError("GetProtectBranchOfRepoByName", err)
  116. return
  117. }
  118. if sameNameProtectBranch != nil {
  119. ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_duplicate_rule_name"))
  120. ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName))
  121. return
  122. }
  123. }
  124. } else {
  125. // FIXME: If a new ProtectBranch has a duplicate RuleName, an error should be returned.
  126. // Currently, if a new ProtectBranch with a duplicate RuleName is created, the existing ProtectBranch will be updated.
  127. // But we cannot modify this logic now because many unit tests rely on it.
  128. protectBranch, err = git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName)
  129. if err != nil {
  130. ctx.ServerError("GetProtectBranchOfRepoByName", err)
  131. return
  132. }
  133. }
  134. if protectBranch == nil {
  135. // No options found, create defaults.
  136. protectBranch = &git_model.ProtectedBranch{
  137. RepoID: ctx.Repo.Repository.ID,
  138. RuleName: f.RuleName,
  139. }
  140. }
  141. var whitelistUsers, whitelistTeams, forcePushAllowlistUsers, forcePushAllowlistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
  142. protectBranch.RuleName = f.RuleName
  143. if f.RequiredApprovals < 0 {
  144. ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
  145. ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, f.RuleName))
  146. return
  147. }
  148. switch f.EnablePush {
  149. case "all":
  150. protectBranch.CanPush = true
  151. protectBranch.EnableWhitelist = false
  152. protectBranch.WhitelistDeployKeys = false
  153. case "whitelist":
  154. protectBranch.CanPush = true
  155. protectBranch.EnableWhitelist = true
  156. protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys
  157. if strings.TrimSpace(f.WhitelistUsers) != "" {
  158. whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ","))
  159. }
  160. if strings.TrimSpace(f.WhitelistTeams) != "" {
  161. whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ","))
  162. }
  163. default:
  164. protectBranch.CanPush = false
  165. protectBranch.EnableWhitelist = false
  166. protectBranch.WhitelistDeployKeys = false
  167. }
  168. switch f.EnableForcePush {
  169. case "all":
  170. protectBranch.CanForcePush = true
  171. protectBranch.EnableForcePushAllowlist = false
  172. protectBranch.ForcePushAllowlistDeployKeys = false
  173. case "whitelist":
  174. protectBranch.CanForcePush = true
  175. protectBranch.EnableForcePushAllowlist = true
  176. protectBranch.ForcePushAllowlistDeployKeys = f.ForcePushAllowlistDeployKeys
  177. if strings.TrimSpace(f.ForcePushAllowlistUsers) != "" {
  178. forcePushAllowlistUsers, _ = base.StringsToInt64s(strings.Split(f.ForcePushAllowlistUsers, ","))
  179. }
  180. if strings.TrimSpace(f.ForcePushAllowlistTeams) != "" {
  181. forcePushAllowlistTeams, _ = base.StringsToInt64s(strings.Split(f.ForcePushAllowlistTeams, ","))
  182. }
  183. default:
  184. protectBranch.CanForcePush = false
  185. protectBranch.EnableForcePushAllowlist = false
  186. protectBranch.ForcePushAllowlistDeployKeys = false
  187. }
  188. protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
  189. if f.EnableMergeWhitelist {
  190. if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
  191. mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ","))
  192. }
  193. if strings.TrimSpace(f.MergeWhitelistTeams) != "" {
  194. mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ","))
  195. }
  196. }
  197. protectBranch.EnableStatusCheck = f.EnableStatusCheck
  198. if f.EnableStatusCheck {
  199. patterns := strings.Split(strings.ReplaceAll(f.StatusCheckContexts, "\r", "\n"), "\n")
  200. validPatterns := make([]string, 0, len(patterns))
  201. for _, pattern := range patterns {
  202. trimmed := strings.TrimSpace(pattern)
  203. if trimmed == "" {
  204. continue
  205. }
  206. if _, err := glob.Compile(trimmed); err != nil {
  207. ctx.Flash.Error(ctx.Tr("repo.settings.protect_invalid_status_check_pattern", pattern))
  208. ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, url.QueryEscape(protectBranch.RuleName)))
  209. return
  210. }
  211. validPatterns = append(validPatterns, trimmed)
  212. }
  213. if len(validPatterns) == 0 {
  214. // if status check is enabled, patterns slice is not allowed to be empty
  215. ctx.Flash.Error(ctx.Tr("repo.settings.protect_no_valid_status_check_patterns"))
  216. ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, url.QueryEscape(protectBranch.RuleName)))
  217. return
  218. }
  219. protectBranch.StatusCheckContexts = validPatterns
  220. } else {
  221. protectBranch.StatusCheckContexts = nil
  222. }
  223. protectBranch.RequiredApprovals = f.RequiredApprovals
  224. protectBranch.EnableApprovalsWhitelist = f.EnableApprovalsWhitelist
  225. if f.EnableApprovalsWhitelist {
  226. if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
  227. approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ","))
  228. }
  229. if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" {
  230. approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ","))
  231. }
  232. }
  233. protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
  234. protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
  235. protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
  236. protectBranch.IgnoreStaleApprovals = f.IgnoreStaleApprovals
  237. protectBranch.RequireSignedCommits = f.RequireSignedCommits
  238. protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
  239. protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
  240. protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
  241. protectBranch.BlockAdminMergeOverride = f.BlockAdminMergeOverride
  242. if err = pull_service.CreateOrUpdateProtectedBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
  243. UserIDs: whitelistUsers,
  244. TeamIDs: whitelistTeams,
  245. ForcePushUserIDs: forcePushAllowlistUsers,
  246. ForcePushTeamIDs: forcePushAllowlistTeams,
  247. MergeUserIDs: mergeWhitelistUsers,
  248. MergeTeamIDs: mergeWhitelistTeams,
  249. ApprovalsUserIDs: approvalsWhitelistUsers,
  250. ApprovalsTeamIDs: approvalsWhitelistTeams,
  251. }); err != nil {
  252. ctx.ServerError("CreateOrUpdateProtectedBranch", err)
  253. return
  254. }
  255. ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", protectBranch.RuleName))
  256. ctx.Redirect(fmt.Sprintf("%s/settings/branches?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName))
  257. }
  258. // DeleteProtectedBranchRulePost delete protected branch rule by id
  259. func DeleteProtectedBranchRulePost(ctx *context.Context) {
  260. ruleID := ctx.PathParamInt64("id")
  261. if ruleID <= 0 {
  262. ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10)))
  263. ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
  264. return
  265. }
  266. rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID)
  267. if err != nil {
  268. ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10)))
  269. ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
  270. return
  271. }
  272. if rule == nil {
  273. ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10)))
  274. ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
  275. return
  276. }
  277. if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, ruleID); err != nil {
  278. ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName))
  279. ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
  280. return
  281. }
  282. ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName))
  283. ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
  284. }
  285. func UpdateBranchProtectionPriories(ctx *context.Context) {
  286. form := web.GetForm(ctx).(*forms.ProtectBranchPriorityForm)
  287. repo := ctx.Repo.Repository
  288. if err := git_model.UpdateProtectBranchPriorities(ctx, repo, form.IDs); err != nil {
  289. ctx.ServerError("UpdateProtectBranchPriorities", err)
  290. return
  291. }
  292. }
  293. // RenameBranchPost responses for rename a branch
  294. func RenameBranchPost(ctx *context.Context) {
  295. form := web.GetForm(ctx).(*forms.RenameBranchForm)
  296. if !ctx.Repo.CanCreateBranch() {
  297. ctx.NotFound(nil)
  298. return
  299. }
  300. if ctx.HasError() {
  301. ctx.Flash.Error(ctx.GetErrMsg())
  302. ctx.Redirect(ctx.Repo.RepoLink + "/branches")
  303. return
  304. }
  305. msg, err := repository.RenameBranch(ctx, ctx.Repo.Repository, ctx.Doer, ctx.Repo.GitRepo, form.From, form.To)
  306. if err != nil {
  307. switch {
  308. case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
  309. ctx.Flash.Error(ctx.Tr("repo.branch.rename_default_or_protected_branch_error"))
  310. ctx.Redirect(ctx.Repo.RepoLink + "/branches")
  311. case git_model.IsErrBranchAlreadyExists(err):
  312. ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.To))
  313. ctx.Redirect(ctx.Repo.RepoLink + "/branches")
  314. case errors.Is(err, git_model.ErrBranchIsProtected):
  315. ctx.Flash.Error(ctx.Tr("repo.branch.rename_protected_branch_failed"))
  316. ctx.Redirect(ctx.Repo.RepoLink + "/branches")
  317. default:
  318. ctx.ServerError("RenameBranch", err)
  319. }
  320. return
  321. }
  322. if msg == "target_exist" {
  323. ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To))
  324. ctx.Redirect(ctx.Repo.RepoLink + "/branches")
  325. return
  326. }
  327. if msg == "from_not_exist" {
  328. ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From))
  329. ctx.Redirect(ctx.Repo.RepoLink + "/branches")
  330. return
  331. }
  332. ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To))
  333. ctx.Redirect(ctx.Repo.RepoLink + "/branches")
  334. }