Files

377 lines
14 KiB
Go
Raw Permalink Normal View History

// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2023-07-02 08:59:32 +08:00
package setting
import (
"errors"
"fmt"
"net/http"
2023-05-17 16:11:13 +08:00
"net/url"
2025-04-01 12:14:01 +02:00
"strconv"
"strings"
"time"
2022-06-12 23:51:54 +08:00
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/organization"
2021-11-28 19:58:28 +08:00
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
2025-06-24 21:24:09 +08:00
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
2025-09-14 02:01:00 +08:00
"code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/templates"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
pull_service "code.gitea.io/gitea/services/pull"
"code.gitea.io/gitea/services/repository"
)
2023-01-16 16:00:22 +08:00
const (
tplProtectedBranch templates.TplName = "repo/settings/protected_branch"
2023-01-16 16:00:22 +08:00
)
// ProtectedBranchRules render the page to protect the repository
func ProtectedBranchRules(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.branches")
ctx.Data["PageIsSettingsBranches"] = true
2023-01-16 16:00:22 +08:00
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil {
2018-01-10 22:34:17 +01:00
ctx.ServerError("GetProtectedBranches", err)
return
}
2023-01-16 16:00:22 +08:00
ctx.Data["ProtectedBranches"] = rules
repo.PrepareBranchList(ctx)
if ctx.Written() {
return
}
ctx.HTML(http.StatusOK, tplBranches)
}
// SettingsProtectedBranch renders the protected branch setting page
func SettingsProtectedBranch(c *context.Context) {
2023-01-16 16:00:22 +08:00
ruleName := c.FormString("rule_name")
var rule *git_model.ProtectedBranch
if ruleName != "" {
var err error
rule, err = git_model.GetProtectedBranchRuleByName(c, c.Repo.Repository.ID, ruleName)
if err != nil {
2018-01-10 22:34:17 +01:00
c.ServerError("GetProtectBranchOfRepoByName", err)
return
}
}
2023-01-16 16:00:22 +08:00
if rule == nil {
// No options found, create defaults.
2023-01-16 16:00:22 +08:00
rule = &git_model.ProtectedBranch{}
}
2023-01-16 16:00:22 +08:00
c.Data["PageIsSettingsBranches"] = true
c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName
2025-11-09 11:13:31 +08:00
users, err := access_model.GetUsersWithUnitAccess(c, c.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
2025-11-09 11:13:31 +08:00
c.ServerError("GetUsersWithUnitAccess", err)
return
}
c.Data["Users"] = users
2023-01-16 16:00:22 +08:00
c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",")
c.Data["force_push_allowlist_users"] = strings.Join(base.Int64sToStrings(rule.ForcePushAllowlistUserIDs), ",")
2023-01-16 16:00:22 +08:00
c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",")
c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",")
2023-05-17 16:11:13 +08:00
c.Data["status_check_contexts"] = strings.Join(rule.StatusCheckContexts, "\n")
contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c, c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
2023-05-17 16:11:13 +08:00
c.Data["recent_status_checks"] = contexts
if c.Repo.Owner.IsOrganization() {
2025-06-24 21:24:09 +08:00
teams, err := organization.GetTeamsWithAccessToAnyRepoUnit(c, c.Repo.Owner.ID, c.Repo.Repository.ID, perm.AccessModeRead, unit.TypeCode, unit.TypePullRequests)
if err != nil {
2018-01-10 22:34:17 +01:00
c.ServerError("Repo.Owner.TeamsWithAccessToRepo", err)
return
}
c.Data["Teams"] = teams
2023-01-16 16:00:22 +08:00
c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",")
c.Data["force_push_allowlist_teams"] = strings.Join(base.Int64sToStrings(rule.ForcePushAllowlistTeamIDs), ",")
2023-01-16 16:00:22 +08:00
c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",")
c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",")
}
2023-01-16 16:00:22 +08:00
c.Data["Rule"] = rule
c.HTML(http.StatusOK, tplProtectedBranch)
}
// SettingsProtectedBranchPost updates the protected branch settings
2021-01-26 23:36:53 +08:00
func SettingsProtectedBranchPost(ctx *context.Context) {
f := web.GetForm(ctx).(*forms.ProtectBranchForm)
2023-01-16 16:00:22 +08:00
var protectBranch *git_model.ProtectedBranch
if f.RuleName == "" {
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_rule_name"))
2025-04-01 12:14:01 +02:00
ctx.Redirect(ctx.Repo.RepoLink + "/settings/branches/edit")
return
}
2023-01-16 16:00:22 +08:00
var err error
if f.RuleID > 0 {
// If the RuleID isn't 0, it must be an edit operation. So we get rule by id.
protectBranch, err = git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, f.RuleID)
if err != nil {
ctx.ServerError("GetProtectBranchOfRepoByID", err)
return
}
if protectBranch != nil && protectBranch.RuleName != f.RuleName {
// RuleName changed. We need to check if there is a rule with the same name.
// If a rule with the same name exists, an error should be returned.
sameNameProtectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName)
if err != nil {
ctx.ServerError("GetProtectBranchOfRepoByName", err)
return
}
if sameNameProtectBranch != nil {
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_duplicate_rule_name"))
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName))
return
}
}
} else {
// FIXME: If a new ProtectBranch has a duplicate RuleName, an error should be returned.
// Currently, if a new ProtectBranch with a duplicate RuleName is created, the existing ProtectBranch will be updated.
// But we cannot modify this logic now because many unit tests rely on it.
protectBranch, err = git_model.GetProtectedBranchRuleByName(ctx, ctx.Repo.Repository.ID, f.RuleName)
if err != nil {
ctx.ServerError("GetProtectBranchOfRepoByName", err)
return
}
2023-01-16 16:00:22 +08:00
}
if protectBranch == nil {
// No options found, create defaults.
protectBranch = &git_model.ProtectedBranch{
RepoID: ctx.Repo.Repository.ID,
RuleName: f.RuleName,
}
}
var whitelistUsers, whitelistTeams, forcePushAllowlistUsers, forcePushAllowlistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
2023-01-16 16:00:22 +08:00
protectBranch.RuleName = f.RuleName
if f.RequiredApprovals < 0 {
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, f.RuleName))
return
}
switch f.EnablePush {
case "all":
protectBranch.CanPush = true
protectBranch.EnableWhitelist = false
protectBranch.WhitelistDeployKeys = false
case "whitelist":
protectBranch.CanPush = true
protectBranch.EnableWhitelist = true
protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys
if strings.TrimSpace(f.WhitelistUsers) != "" {
whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ","))
}
2023-01-16 16:00:22 +08:00
if strings.TrimSpace(f.WhitelistTeams) != "" {
whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ","))
2018-12-11 12:28:37 +01:00
}
2023-01-16 16:00:22 +08:00
default:
protectBranch.CanPush = false
protectBranch.EnableWhitelist = false
protectBranch.WhitelistDeployKeys = false
}
switch f.EnableForcePush {
case "all":
protectBranch.CanForcePush = true
protectBranch.EnableForcePushAllowlist = false
protectBranch.ForcePushAllowlistDeployKeys = false
case "whitelist":
protectBranch.CanForcePush = true
protectBranch.EnableForcePushAllowlist = true
protectBranch.ForcePushAllowlistDeployKeys = f.ForcePushAllowlistDeployKeys
if strings.TrimSpace(f.ForcePushAllowlistUsers) != "" {
forcePushAllowlistUsers, _ = base.StringsToInt64s(strings.Split(f.ForcePushAllowlistUsers, ","))
}
if strings.TrimSpace(f.ForcePushAllowlistTeams) != "" {
forcePushAllowlistTeams, _ = base.StringsToInt64s(strings.Split(f.ForcePushAllowlistTeams, ","))
}
default:
protectBranch.CanForcePush = false
protectBranch.EnableForcePushAllowlist = false
protectBranch.ForcePushAllowlistDeployKeys = false
}
2023-01-16 16:00:22 +08:00
protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
if f.EnableMergeWhitelist {
if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ","))
}
2023-01-16 16:00:22 +08:00
if strings.TrimSpace(f.MergeWhitelistTeams) != "" {
mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ","))
}
2023-01-16 16:00:22 +08:00
}
2023-01-16 16:00:22 +08:00
protectBranch.EnableStatusCheck = f.EnableStatusCheck
if f.EnableStatusCheck {
2023-05-17 16:11:13 +08:00
patterns := strings.Split(strings.ReplaceAll(f.StatusCheckContexts, "\r", "\n"), "\n")
validPatterns := make([]string, 0, len(patterns))
for _, pattern := range patterns {
trimmed := strings.TrimSpace(pattern)
if trimmed == "" {
continue
}
if _, err := glob.Compile(trimmed); err != nil {
ctx.Flash.Error(ctx.Tr("repo.settings.protect_invalid_status_check_pattern", pattern))
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, url.QueryEscape(protectBranch.RuleName)))
return
}
validPatterns = append(validPatterns, trimmed)
}
if len(validPatterns) == 0 {
// if status check is enabled, patterns slice is not allowed to be empty
ctx.Flash.Error(ctx.Tr("repo.settings.protect_no_valid_status_check_patterns"))
ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit?rule_name=%s", ctx.Repo.RepoLink, url.QueryEscape(protectBranch.RuleName)))
return
}
protectBranch.StatusCheckContexts = validPatterns
2023-01-16 16:00:22 +08:00
} else {
protectBranch.StatusCheckContexts = nil
}
2023-01-16 16:00:22 +08:00
protectBranch.RequiredApprovals = f.RequiredApprovals
protectBranch.EnableApprovalsWhitelist = f.EnableApprovalsWhitelist
if f.EnableApprovalsWhitelist {
if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" {
approvalsWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistUsers, ","))
2018-12-11 12:28:37 +01:00
}
2023-01-16 16:00:22 +08:00
if strings.TrimSpace(f.ApprovalsWhitelistTeams) != "" {
approvalsWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ApprovalsWhitelistTeams, ","))
}
2023-01-16 16:00:22 +08:00
}
protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
protectBranch.IgnoreStaleApprovals = f.IgnoreStaleApprovals
2023-01-16 16:00:22 +08:00
protectBranch.RequireSignedCommits = f.RequireSignedCommits
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
protectBranch.BlockAdminMergeOverride = f.BlockAdminMergeOverride
2023-01-16 16:00:22 +08:00
if err = pull_service.CreateOrUpdateProtectedBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
2023-01-16 16:00:22 +08:00
UserIDs: whitelistUsers,
TeamIDs: whitelistTeams,
ForcePushUserIDs: forcePushAllowlistUsers,
ForcePushTeamIDs: forcePushAllowlistTeams,
2023-01-16 16:00:22 +08:00
MergeUserIDs: mergeWhitelistUsers,
MergeTeamIDs: mergeWhitelistTeams,
ApprovalsUserIDs: approvalsWhitelistUsers,
ApprovalsTeamIDs: approvalsWhitelistTeams,
}); err != nil {
ctx.ServerError("CreateOrUpdateProtectedBranch", err)
2023-01-16 16:00:22 +08:00
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", protectBranch.RuleName))
ctx.Redirect(fmt.Sprintf("%s/settings/branches?rule_name=%s", ctx.Repo.RepoLink, protectBranch.RuleName))
}
// DeleteProtectedBranchRulePost delete protected branch rule by id
func DeleteProtectedBranchRulePost(ctx *context.Context) {
2024-06-19 06:32:45 +08:00
ruleID := ctx.PathParamInt64("id")
2023-01-16 16:00:22 +08:00
if ruleID <= 0 {
2025-04-01 12:14:01 +02:00
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10)))
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
2023-01-16 16:00:22 +08:00
return
}
rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID)
if err != nil {
2025-04-01 12:14:01 +02:00
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10)))
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
2023-01-16 16:00:22 +08:00
return
}
if rule == nil {
2025-04-01 12:14:01 +02:00
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10)))
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
2023-01-16 16:00:22 +08:00
return
}
if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, ruleID); err != nil {
2023-01-16 16:00:22 +08:00
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName))
2025-04-01 12:14:01 +02:00
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
2023-01-16 16:00:22 +08:00
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName))
2025-04-01 12:14:01 +02:00
ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches")
}
2024-11-27 05:41:06 +01:00
func UpdateBranchProtectionPriories(ctx *context.Context) {
var form struct {
IDs []int64 `json:"ids"`
}
if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
ctx.JSONError("invalid argument")
return
}
if err := git_model.UpdateProtectBranchPriorities(ctx, ctx.Repo.Repository, form.IDs); err != nil {
2024-11-27 05:41:06 +01:00
ctx.ServerError("UpdateProtectBranchPriorities", err)
return
}
}
// RenameBranchPost responses for rename a branch
func RenameBranchPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.RenameBranchForm)
if !ctx.Repo.CanCreateBranch() {
2025-02-17 14:13:17 +08:00
ctx.NotFound(nil)
return
}
if ctx.HasError() {
ctx.Flash.Error(ctx.GetErrMsg())
2025-04-01 12:14:01 +02:00
ctx.Redirect(ctx.Repo.RepoLink + "/branches")
return
}
msg, err := repository.RenameBranch(ctx, ctx.Repo.Repository, ctx.Doer, form.From, form.To)
if err != nil {
switch {
case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
ctx.Flash.Error(ctx.Tr("repo.branch.rename_default_or_protected_branch_error"))
2025-04-01 12:14:01 +02:00
ctx.Redirect(ctx.Repo.RepoLink + "/branches")
case git_model.IsErrBranchAlreadyExists(err):
ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.To))
2025-04-01 12:14:01 +02:00
ctx.Redirect(ctx.Repo.RepoLink + "/branches")
case errors.Is(err, git_model.ErrBranchIsProtected):
ctx.Flash.Error(ctx.Tr("repo.branch.rename_protected_branch_failed"))
2025-04-01 12:14:01 +02:00
ctx.Redirect(ctx.Repo.RepoLink + "/branches")
default:
ctx.ServerError("RenameBranch", err)
}
return
}
if msg == "target_exist" {
ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To))
2025-04-01 12:14:01 +02:00
ctx.Redirect(ctx.Repo.RepoLink + "/branches")
return
}
if msg == "from_not_exist" {
ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From))
2025-04-01 12:14:01 +02:00
ctx.Redirect(ctx.Repo.RepoLink + "/branches")
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To))
2025-04-01 12:14:01 +02:00
ctx.Redirect(ctx.Repo.RepoLink + "/branches")
}