mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-24 01:24:39 +02:00
feat(repo): allow deleting protected branches (#39243)
This adds a Branch Deletion setting with three modes: disabled by default, anyone with push access, or a restricted user/team/deploy-key allowlist. The policy applies to Git pushes, API/UI deletion, and post-merge cleanup; the default branch and the pull request target branch remain undeletable. Closes https://github.com/go-gitea/gitea/issues/33305 --------- Co-authored-by: Enoque Duarte <12515551+enoquefcd@users.noreply.github.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
21 files changed
+600
-59
No files matched your search
@@ -711,6 +711,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
|
||||
whitelistUsers := loadBranchProtectionUserIDs(ctx, form.PushWhitelistUsernames)
|
||||
forcePushAllowlistUsers := loadBranchProtectionUserIDs(ctx, form.ForcePushAllowlistUsernames)
|
||||
deletionAllowlistUsers := loadBranchProtectionUserIDs(ctx, util.Iif(form.EnablePush && form.EnableDeletion && form.EnableDeletionAllowlist, form.DeletionAllowlistUsernames, nil))
|
||||
mergeWhitelistUsers := loadBranchProtectionUserIDs(ctx, form.MergeWhitelistUsernames)
|
||||
approvalsWhitelistUsers := loadBranchProtectionUserIDs(ctx, form.ApprovalsWhitelistUsernames)
|
||||
bypassAllowlistUsers := loadBranchProtectionUserIDs(ctx, util.Iif(form.EnableBypassAllowlist, form.BypassAllowlistUsernames, nil))
|
||||
@@ -718,7 +719,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams, bypassAllowlistTeams []int64
|
||||
var whitelistTeams, forcePushAllowlistTeams, deletionAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams, bypassAllowlistTeams []int64
|
||||
if repo.Owner.IsOrganization() {
|
||||
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
|
||||
if err != nil {
|
||||
@@ -738,6 +739,17 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if form.EnablePush && form.EnableDeletion && form.EnableDeletionAllowlist {
|
||||
deletionAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.DeletionAllowlistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
@@ -782,6 +794,8 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
|
||||
CanForcePush: form.EnablePush && form.EnableForcePush,
|
||||
EnableForcePushAllowlist: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist,
|
||||
CanDelete: form.EnablePush && form.EnableDeletion,
|
||||
EnableDeletionAllowlist: form.EnablePush && form.EnableDeletion && form.EnableDeletionAllowlist,
|
||||
EnableMergeWhitelist: form.EnableMergeWhitelist,
|
||||
EnableBypassAllowlist: form.EnableBypassAllowlist,
|
||||
EnableStatusCheck: form.EnableStatusCheck,
|
||||
@@ -810,6 +824,8 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
TeamIDs: whitelistTeams,
|
||||
ForcePushUserIDs: forcePushAllowlistUsers,
|
||||
ForcePushTeamIDs: forcePushAllowlistTeams,
|
||||
DeletionUserIDs: deletionAllowlistUsers,
|
||||
DeletionTeamIDs: deletionAllowlistTeams,
|
||||
MergeUserIDs: mergeWhitelistUsers,
|
||||
MergeTeamIDs: mergeWhitelistTeams,
|
||||
ApprovalsUserIDs: approvalsWhitelistUsers,
|
||||
@@ -929,6 +945,15 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if form.EnableDeletion != nil {
|
||||
protectBranch.CanDelete = *form.EnableDeletion
|
||||
}
|
||||
if form.EnableDeletionAllowlist != nil {
|
||||
protectBranch.EnableDeletionAllowlist = *form.EnableDeletionAllowlist
|
||||
}
|
||||
protectBranch.CanDelete = protectBranch.CanPush && protectBranch.CanDelete
|
||||
protectBranch.EnableDeletionAllowlist = protectBranch.CanDelete && protectBranch.EnableDeletionAllowlist
|
||||
|
||||
if form.Priority != nil {
|
||||
protectBranch.Priority = *form.Priority
|
||||
}
|
||||
@@ -999,6 +1024,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
|
||||
whitelistUsers := loadBranchProtectionUserIDs(ctx, form.PushWhitelistUsernames, protectBranch.WhitelistUserIDs)
|
||||
forcePushAllowlistUsers := loadBranchProtectionUserIDs(ctx, form.ForcePushAllowlistUsernames, protectBranch.ForcePushAllowlistUserIDs)
|
||||
deletionAllowlistUsers := loadBranchProtectionUserIDs(ctx, form.DeletionAllowlistUsernames, protectBranch.DeletionAllowlistUserIDs)
|
||||
mergeWhitelistUsers := loadBranchProtectionUserIDs(ctx, form.MergeWhitelistUsernames, protectBranch.MergeWhitelistUserIDs)
|
||||
approvalsWhitelistUsers := loadBranchProtectionUserIDs(ctx, form.ApprovalsWhitelistUsernames, protectBranch.ApprovalsWhitelistUserIDs)
|
||||
bypassAllowlistUsers := loadBranchProtectionUserIDs(ctx, form.BypassAllowlistUsernames, protectBranch.BypassAllowlistUserIDs)
|
||||
@@ -1015,7 +1041,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
whitelistUsers = legacySetDeployKeyAllowlist(whitelistUsers, pushDeployKeys)
|
||||
forcePushAllowlistUsers = legacySetDeployKeyAllowlist(forcePushAllowlistUsers, forcePushDeployKeys)
|
||||
|
||||
var whitelistTeams, forcePushAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams, bypassAllowlistTeams []int64
|
||||
var whitelistTeams, forcePushAllowlistTeams, deletionAllowlistTeams, mergeWhitelistTeams, approvalsWhitelistTeams, bypassAllowlistTeams []int64
|
||||
if repo.Owner.IsOrganization() {
|
||||
if form.PushWhitelistTeams != nil {
|
||||
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
|
||||
@@ -1043,6 +1069,19 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
} else {
|
||||
forcePushAllowlistTeams = protectBranch.ForcePushAllowlistTeamIDs
|
||||
}
|
||||
if form.DeletionAllowlistTeams != nil {
|
||||
deletionAllowlistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.DeletionAllowlistTeams, false)
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
deletionAllowlistTeams = protectBranch.DeletionAllowlistTeamIDs
|
||||
}
|
||||
if form.MergeWhitelistTeams != nil {
|
||||
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
|
||||
if err != nil {
|
||||
@@ -1088,11 +1127,18 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
bypassAllowlistTeams = nil
|
||||
}
|
||||
|
||||
if !protectBranch.EnableDeletionAllowlist {
|
||||
deletionAllowlistUsers = nil
|
||||
deletionAllowlistTeams = nil
|
||||
}
|
||||
|
||||
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
|
||||
UserIDs: whitelistUsers,
|
||||
TeamIDs: whitelistTeams,
|
||||
ForcePushUserIDs: forcePushAllowlistUsers,
|
||||
ForcePushTeamIDs: forcePushAllowlistTeams,
|
||||
DeletionUserIDs: deletionAllowlistUsers,
|
||||
DeletionTeamIDs: deletionAllowlistTeams,
|
||||
MergeUserIDs: mergeWhitelistUsers,
|
||||
MergeTeamIDs: mergeWhitelistTeams,
|
||||
ApprovalsUserIDs: approvalsWhitelistUsers,
|
||||
|
||||
@@ -130,15 +130,21 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
repo := ctx.Repo.Repository
|
||||
gitRepo := ctx.Repo.GitRepo
|
||||
objectFormat := ctx.Repo.GetObjectFormat()
|
||||
isDeletingBranch := newCommitID == objectFormat.EmptyObjectID().String()
|
||||
|
||||
defaultBranch := repo.DefaultBranch
|
||||
if ctx.opts.IsWiki && repo.DefaultWikiBranch != "" {
|
||||
defaultBranch = repo.DefaultWikiBranch
|
||||
}
|
||||
if branchName == defaultBranch && newCommitID == objectFormat.EmptyObjectID().String() {
|
||||
|
||||
if isDeletingBranch && branchName == defaultBranch {
|
||||
ctx.PrivateUserErrorf(http.StatusForbidden, "Branch %s is the default branch and cannot be deleted", branchName)
|
||||
return
|
||||
}
|
||||
if isDeletingBranch && branchName == repo.GetPullRequestTargetBranch(ctx) {
|
||||
ctx.PrivateUserErrorf(http.StatusForbidden, "Branch %s is protected from deletion", branchName)
|
||||
return
|
||||
}
|
||||
|
||||
protectBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)
|
||||
if err != nil {
|
||||
@@ -156,9 +162,11 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
//
|
||||
// First of all we need to enforce absolutely:
|
||||
//
|
||||
// 1. Detect and prevent deletion of the branch
|
||||
if newCommitID == objectFormat.EmptyObjectID().String() {
|
||||
ctx.PrivateUserErrorf(http.StatusForbidden, "Branch %s is protected from deletion", branchName)
|
||||
// 1. Enforce branch deletion permissions
|
||||
if isDeletingBranch {
|
||||
if !protectBranch.CanUserDelete(ctx, ctx.Doer, ctx.Repo.Permission) {
|
||||
ctx.PrivateUserErrorf(http.StatusForbidden, "Branch %s is protected from deletion", branchName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -199,7 +207,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
// Now there are several tests which can be overridden:
|
||||
//
|
||||
// 4. Check protected file patterns - this is overridable from the UI
|
||||
changedProtectedfiles := false
|
||||
changedProtectedFiles := false
|
||||
protectedFilePath := ""
|
||||
|
||||
globs := protectBranch.GetProtectedFilePatterns()
|
||||
@@ -212,7 +220,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
return
|
||||
}
|
||||
|
||||
changedProtectedfiles = true
|
||||
changedProtectedFiles = true
|
||||
protectedFilePath = errFilePathProtected.Path
|
||||
}
|
||||
}
|
||||
@@ -220,9 +228,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
// 5. Check if the doer is allowed to push (and force-push if the incoming push is a force-push)
|
||||
var canPush bool
|
||||
if isForcePush {
|
||||
canPush = !changedProtectedfiles && protectBranch.CanUserForcePush(ctx, ctx.Doer, ctx.Repo.Permission)
|
||||
canPush = !changedProtectedFiles && protectBranch.CanUserForcePush(ctx, ctx.Doer, ctx.Repo.Permission)
|
||||
} else {
|
||||
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, ctx.Doer, ctx.Repo.Permission)
|
||||
canPush = !changedProtectedFiles && protectBranch.CanUserPush(ctx, ctx.Doer, ctx.Repo.Permission)
|
||||
}
|
||||
|
||||
// 6. If we're not allowed to push directly
|
||||
@@ -232,7 +240,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
// 6a. If we're not merging from the UI/API then there are two ways we got here:
|
||||
//
|
||||
// We are changing a protected file, and we're not allowed to do that
|
||||
if changedProtectedfiles {
|
||||
if changedProtectedFiles {
|
||||
ctx.PrivateUserErrorf(http.StatusForbidden, "Branch %s is protected from changing file %s", branchName, protectedFilePath)
|
||||
return
|
||||
}
|
||||
@@ -287,7 +295,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
}
|
||||
|
||||
// Now if we're not an admin - we can't overwrite protected files so fail now
|
||||
if changedProtectedfiles {
|
||||
if changedProtectedFiles {
|
||||
ctx.PrivateUserErrorf(http.StatusForbidden, "Branch %s is protected from changing file %s", branchName, protectedFilePath)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
package private
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
@@ -20,18 +22,27 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPreReceiveActionsProtectedBranch(t *testing.T) {
|
||||
func TestPreReceiveActionsBranch(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
protection git_model.ProtectedBranch
|
||||
forcePush bool
|
||||
allowed bool
|
||||
name string
|
||||
protection *git_model.ProtectedBranchConfig
|
||||
forcePush bool
|
||||
deleteBranch bool
|
||||
targetBranch bool
|
||||
defaultBranch bool
|
||||
allowed bool
|
||||
}{
|
||||
{name: "push", protection: git_model.ProtectedBranch{CanPush: true}, allowed: true},
|
||||
{name: "push allowlist", protection: git_model.ProtectedBranch{CanPush: true, EnableWhitelist: true}},
|
||||
{name: "force push", protection: git_model.ProtectedBranch{CanPush: true, CanForcePush: true}, forcePush: true, allowed: true},
|
||||
{name: "force push allowlist", protection: git_model.ProtectedBranch{CanPush: true, CanForcePush: true, EnableForcePushAllowlist: true}, forcePush: true},
|
||||
{name: "push", protection: &git_model.ProtectedBranchConfig{CanPush: true}, allowed: true},
|
||||
{name: "push allowlist", protection: &git_model.ProtectedBranchConfig{CanPush: true, EnableWhitelist: true}},
|
||||
{name: "force push", protection: &git_model.ProtectedBranchConfig{CanPush: true, CanForcePush: true}, forcePush: true, allowed: true},
|
||||
{name: "force push allowlist", protection: &git_model.ProtectedBranchConfig{CanPush: true, CanForcePush: true, EnableForcePushAllowlist: true}, forcePush: true},
|
||||
{name: "deletion disabled", protection: &git_model.ProtectedBranchConfig{CanPush: true}, deleteBranch: true},
|
||||
{name: "deletion allowed", protection: &git_model.ProtectedBranchConfig{CanPush: true, CanDelete: true}, deleteBranch: true, allowed: true},
|
||||
{name: "pull request target", protection: &git_model.ProtectedBranchConfig{CanPush: true, CanDelete: true}, deleteBranch: true, targetBranch: true},
|
||||
{name: "unprotected deletion", deleteBranch: true, allowed: true},
|
||||
{name: "unprotected pull request target", deleteBranch: true, targetBranch: true},
|
||||
{name: "default branch", protection: &git_model.ProtectedBranchConfig{CanPush: true, CanDelete: true}, deleteBranch: true, defaultBranch: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockCtx, resp := contexttest.MockPrivateContext(t, "/")
|
||||
@@ -45,21 +56,34 @@ func TestPreReceiveActionsProtectedBranch(t *testing.T) {
|
||||
doer := user_model.NewActionsUserWithTaskID(53)
|
||||
loadContextDoerPermission(ctx.PrivateContext, doer.ID, doer.ExtDoerData.EncodeToString())
|
||||
|
||||
protection := tc.protection
|
||||
protection.RepoID = ctx.Repo.Repository.ID
|
||||
protection.RuleName = "probe"
|
||||
require.NoError(t, db.Insert(t.Context(), &protection))
|
||||
defer func() {
|
||||
require.NoError(t, git_model.DeleteProtectedBranch(t.Context(), ctx.Repo.Repository, protection.ID))
|
||||
}()
|
||||
if tc.protection != nil {
|
||||
protection := git_model.ProtectedBranch{ProtectedBranchConfig: *tc.protection}
|
||||
protection.RepoID = ctx.Repo.Repository.ID
|
||||
protection.RuleName = "probe"
|
||||
require.NoError(t, db.Insert(t.Context(), &protection))
|
||||
defer func() {
|
||||
require.NoError(t, git_model.DeleteProtectedBranch(t.Context(), ctx.Repo.Repository, protection.ID))
|
||||
}()
|
||||
}
|
||||
|
||||
oldCommitID, newCommitID := "205ac761f3326a7ebe416e8673760016450b5cec", "1032bbf17fbc0d9c95bb5418dabe8f8c99278700"
|
||||
if tc.forcePush {
|
||||
oldCommitID, newCommitID = newCommitID, oldCommitID
|
||||
}
|
||||
if tc.deleteBranch {
|
||||
newCommitID = ctx.Repo.GetObjectFormat().EmptyObjectID().String()
|
||||
}
|
||||
if tc.targetBranch {
|
||||
ctx.Repo.Repository.Units = append(ctx.Repo.Repository.Units, &repo_model.RepoUnit{Type: unit.TypePullRequests, Config: &repo_model.PullRequestsConfig{DefaultTargetBranch: "probe"}})
|
||||
}
|
||||
if tc.defaultBranch {
|
||||
ctx.Repo.Repository.DefaultBranch = "probe"
|
||||
}
|
||||
preReceiveBranch(ctx, oldCommitID, newCommitID, git.RefNameFromBranch("probe"))
|
||||
if tc.allowed {
|
||||
assert.False(t, ctx.Written(), resp.Body.String())
|
||||
} else if tc.deleteBranch {
|
||||
assert.Equal(t, http.StatusForbidden, resp.Code)
|
||||
} else {
|
||||
assert.Contains(t, resp.Body.String(), "Not allowed to")
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func Branches(ctx *context.Context) {
|
||||
|
||||
kw := ctx.FormString("q")
|
||||
|
||||
defaultBranchOptional, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, optional.None[bool](), kw, page, pageSize)
|
||||
defaultBranchOptional, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Doer, ctx.Repo.Permission, optional.None[bool](), kw, page, pageSize)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadBranches", err)
|
||||
return
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
issue_service "gitea.dev/services/issue"
|
||||
"gitea.dev/services/notifications"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
@@ -900,14 +901,15 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
||||
return
|
||||
}
|
||||
if perm.CanWrite(unit.TypeCode) {
|
||||
// Check if branch is not protected
|
||||
if pull.HeadBranch != pull.HeadRepo.DefaultBranch {
|
||||
if protected, err := git_model.IsBranchProtected(ctx, pull.HeadRepo.ID, pull.HeadBranch); err != nil {
|
||||
log.Error("IsProtectedBranch: %v", err)
|
||||
} else if !protected {
|
||||
canDelete = true
|
||||
ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup"
|
||||
if err := repo_service.CanDeleteBranchWithPermission(ctx, pull.HeadRepo, pull.HeadBranch, ctx.Doer, perm); err != nil {
|
||||
if errors.Is(err, util.ErrPermissionDenied) {
|
||||
log.Trace("CanDeleteBranch: %v", err)
|
||||
} else {
|
||||
log.Error("CanDeleteBranch: %v", err)
|
||||
}
|
||||
} else {
|
||||
canDelete = true
|
||||
ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup"
|
||||
}
|
||||
canWriteToHeadRepo = true
|
||||
}
|
||||
|
||||
@@ -89,11 +89,13 @@ func SettingsProtectedBranch(c *context.Context) {
|
||||
|
||||
c.Data["PushUsers"] = pushUsers
|
||||
c.Data["ForcePushUsers"] = pushUsers
|
||||
c.Data["DeletionUsers"] = pushUsers
|
||||
c.Data["MergeUsers"] = repoUsers
|
||||
c.Data["BypassCheckUsers"] = repoUsers
|
||||
c.Data["ApprovalUsers"] = repoUsers
|
||||
c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",")
|
||||
c.Data["force_push_allowlist_users"] = strings.Join(base.Int64sToStrings(rule.ForcePushAllowlistUserIDs), ",")
|
||||
c.Data["deletion_allowlist_users"] = strings.Join(base.Int64sToStrings(rule.DeletionAllowlistUserIDs), ",")
|
||||
c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",")
|
||||
c.Data["bypass_allowlist_users"] = strings.Join(base.Int64sToStrings(rule.BypassAllowlistUserIDs), ",")
|
||||
c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",")
|
||||
@@ -110,6 +112,7 @@ func SettingsProtectedBranch(c *context.Context) {
|
||||
c.Data["Teams"] = teams
|
||||
c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",")
|
||||
c.Data["force_push_allowlist_teams"] = strings.Join(base.Int64sToStrings(rule.ForcePushAllowlistTeamIDs), ",")
|
||||
c.Data["deletion_allowlist_teams"] = strings.Join(base.Int64sToStrings(rule.DeletionAllowlistTeamIDs), ",")
|
||||
c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",")
|
||||
c.Data["bypass_allowlist_teams"] = strings.Join(base.Int64sToStrings(rule.BypassAllowlistTeamIDs), ",")
|
||||
c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",")
|
||||
@@ -171,7 +174,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
var whitelistUsers, whitelistTeams, forcePushAllowlistUsers, forcePushAllowlistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams, bypassAllowlistUsers, bypassAllowlistTeams []int64
|
||||
var whitelistUsers, whitelistTeams, forcePushAllowlistUsers, forcePushAllowlistTeams, deletionAllowlistUsers, deletionAllowlistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams, bypassAllowlistUsers, bypassAllowlistTeams []int64
|
||||
protectBranch.RuleName = f.RuleName
|
||||
if f.RequiredApprovals < 0 {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
|
||||
@@ -215,6 +218,29 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||
protectBranch.EnableForcePushAllowlist = false
|
||||
}
|
||||
|
||||
enableDeletion := f.EnableDeletion
|
||||
if !protectBranch.CanPush {
|
||||
// Deletion requires push access.
|
||||
enableDeletion = ""
|
||||
}
|
||||
switch enableDeletion {
|
||||
case "all":
|
||||
protectBranch.CanDelete = true
|
||||
protectBranch.EnableDeletionAllowlist = false
|
||||
case "whitelist":
|
||||
protectBranch.CanDelete = true
|
||||
protectBranch.EnableDeletionAllowlist = true
|
||||
if strings.TrimSpace(f.DeletionAllowlistUsers) != "" {
|
||||
deletionAllowlistUsers, _ = base.StringsToInt64s(strings.Split(f.DeletionAllowlistUsers, ","))
|
||||
}
|
||||
if strings.TrimSpace(f.DeletionAllowlistTeams) != "" {
|
||||
deletionAllowlistTeams, _ = base.StringsToInt64s(strings.Split(f.DeletionAllowlistTeams, ","))
|
||||
}
|
||||
default:
|
||||
protectBranch.CanDelete = false
|
||||
protectBranch.EnableDeletionAllowlist = false
|
||||
}
|
||||
|
||||
protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
|
||||
if f.EnableMergeWhitelist {
|
||||
if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
|
||||
@@ -288,6 +314,8 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
||||
TeamIDs: whitelistTeams,
|
||||
ForcePushUserIDs: forcePushAllowlistUsers,
|
||||
ForcePushTeamIDs: forcePushAllowlistTeams,
|
||||
DeletionUserIDs: deletionAllowlistUsers,
|
||||
DeletionTeamIDs: deletionAllowlistTeams,
|
||||
MergeUserIDs: mergeWhitelistUsers,
|
||||
MergeTeamIDs: mergeWhitelistTeams,
|
||||
ApprovalsUserIDs: approvalsWhitelistUsers,
|
||||
|
||||
Reference in new issue
Block a user