refactor: migrate protected branch fields into one json column and support system users (#39735)

1. migrate protected branch fields into one json column
2. use the dropdown list for push/force-push user selection, and Actions
user can be added into the list now.
This commit is contained in:
wxiaoguang authored and GitHub committed 2026-10-13 00:40:21 +00:00
1 parent f270534e83
commit c6e2513570
25 files changed
+659 -334

No files matched your search

+60 -57
View File
@@ -28,50 +28,53 @@ var ErrBranchIsProtected = util.ErrorWrap(util.ErrPermissionDenied, "branch is p
// ProtectedBranch struct
type ProtectedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
Repo *repo_model.Repository `xorm:"-"`
RuleName string `xorm:"'branch_name' UNIQUE(s)"` // a branch name or a glob match to branch name
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
globRule glob.Glob `xorm:"-"`
isPlainName bool `xorm:"-"`
CanPush bool `xorm:"NOT NULL DEFAULT false"`
EnableWhitelist bool
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"`
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
EnableBypassAllowlist bool `xorm:"NOT NULL DEFAULT false"`
BypassAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
BypassAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
CanForcePush bool `xorm:"NOT NULL DEFAULT false"`
EnableForcePushAllowlist bool `xorm:"NOT NULL DEFAULT false"`
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
StatusCheckContexts []string `xorm:"JSON TEXT"`
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"`
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"`
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"`
BlockOnCodeownerReviews bool `xorm:"NOT NULL DEFAULT false"`
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"`
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false"`
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"`
ProtectedFilePatterns string `xorm:"TEXT"`
UnprotectedFilePatterns string `xorm:"TEXT"`
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false"`
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s)"`
Repo *repo_model.Repository `xorm:"-"`
RuleName string `xorm:"'branch_name' UNIQUE(s)"` // a branch name or a glob match to branch name
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
globRule glob.Glob `xorm:"-"`
isPlainName bool `xorm:"-"`
ProtectedBranchConfig `xorm:"'config' JSON LONGTEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}
// ProtectedBranchConfig stores protection settings in a single JSON column.
type ProtectedBranchConfig struct {
CanPush bool `json:"can_push,omitzero"`
EnableWhitelist bool `json:"enable_whitelist,omitzero"`
WhitelistUserIDs []int64 `json:"whitelist_user_ids,omitzero"`
WhitelistTeamIDs []int64 `json:"whitelist_team_ids,omitzero"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitzero"`
MergeWhitelistUserIDs []int64 `json:"merge_whitelist_user_ids,omitzero"`
MergeWhitelistTeamIDs []int64 `json:"merge_whitelist_team_ids,omitzero"`
EnableBypassAllowlist bool `json:"enable_bypass_allowlist,omitzero"`
BypassAllowlistUserIDs []int64 `json:"bypass_allowlist_user_ids,omitzero"`
BypassAllowlistTeamIDs []int64 `json:"bypass_allowlist_team_ids,omitzero"`
CanForcePush bool `json:"can_force_push,omitzero"`
EnableForcePushAllowlist bool `json:"enable_force_push_allowlist,omitzero"`
ForcePushAllowlistUserIDs []int64 `json:"force_push_allowlist_user_ids,omitzero"`
ForcePushAllowlistTeamIDs []int64 `json:"force_push_allowlist_team_ids,omitzero"`
EnableStatusCheck bool `json:"enable_status_check,omitzero"`
StatusCheckContexts []string `json:"status_check_contexts,omitzero"`
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitzero"`
ApprovalsWhitelistUserIDs []int64 `json:"approvals_whitelist_user_ids,omitzero"`
ApprovalsWhitelistTeamIDs []int64 `json:"approvals_whitelist_team_ids,omitzero"`
RequiredApprovals int64 `json:"required_approvals,omitzero"`
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitzero"`
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitzero"`
BlockOnCodeownerReviews bool `json:"block_on_codeowner_reviews,omitzero"`
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitzero"`
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitzero"`
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitzero"`
RequireSignedCommits bool `json:"require_signed_commits,omitzero"`
ProtectedFilePatterns string `json:"protected_file_patterns,omitzero"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitzero"`
BlockAdminMergeOverride bool `json:"block_admin_merge_override,omitzero"`
}
func init() {
db.RegisterModel(new(ProtectedBranch))
}
@@ -525,13 +528,17 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre
whitelist = make([]int64, 0, len(newWhitelist))
for _, userID := range newWhitelist {
if userID == user_model.ActionsUserID || userID == user_model.DeployKeyUserID {
whitelist = append(whitelist, userID)
continue
}
user, err := user_model.GetUserByID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err)
}
perm, err := access_model.GetIndividualUserRepoPermission(ctx, repo, user)
if err != nil {
return nil, fmt.Errorf("GetIndividualUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
return nil, fmt.Errorf("GetIndividualUserRepoPermission [user_id: %d, repo_id: %d]: %w", userID, repo.ID, err)
}
if !perm.CanWrite(unit.TypeCode) {
@@ -589,7 +596,7 @@ func DeleteProtectedBranch(ctx context.Context, repo *repo_model.Repository, id
}
// removeIDsFromProtectedBranch is a helper function to remove IDs from protected branch options
func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID, teamID int64, columnNames []string) error {
func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID, teamID int64) error {
lenUserIDs, lenForcePushIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ForcePushAllowlistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs)
lenTeamIDs, lenForcePushTeamIDs, lenApprovalTeamIDs, lenMergeTeamIDs := len(p.WhitelistTeamIDs), len(p.ForcePushAllowlistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs)
@@ -615,7 +622,7 @@ func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userI
lenForcePushTeamIDs != len(p.ForcePushAllowlistTeamIDs) ||
lenApprovalTeamIDs != len(p.ApprovalsWhitelistTeamIDs) ||
lenMergeTeamIDs != len(p.MergeWhitelistTeamIDs)) {
if _, err := db.GetEngine(ctx).ID(p.ID).Cols(columnNames...).Update(p); err != nil {
if _, err := db.GetEngine(ctx).ID(p.ID).Cols("config").Update(p); err != nil {
return fmt.Errorf("updateProtectedBranches: %v", err)
}
}
@@ -624,22 +631,18 @@ func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userI
// RemoveUserIDFromProtectedBranch removes all user ids from protected branch options
func RemoveUserIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID int64) error {
columnNames := []string{
"whitelist_user_i_ds",
"force_push_allowlist_user_i_ds",
"merge_whitelist_user_i_ds",
"approvals_whitelist_user_i_ds",
}
return removeIDsFromProtectedBranch(ctx, p, userID, 0, columnNames)
return removeIDsFromProtectedBranch(ctx, p, userID, 0)
}
// RemoveTeamIDFromProtectedBranch removes all team ids from protected branch options
func RemoveTeamIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, teamID int64) error {
columnNames := []string{
"whitelist_team_i_ds",
"force_push_allowlist_team_i_ds",
"merge_whitelist_team_i_ds",
"approvals_whitelist_team_i_ds",
}
return removeIDsFromProtectedBranch(ctx, p, 0, teamID, columnNames)
return removeIDsFromProtectedBranch(ctx, p, 0, teamID)
}
func (protectBranch *ProtectedBranch) HasDeployKeyPush() bool {
return slices.Contains(protectBranch.WhitelistUserIDs, user_model.DeployKeyUserID)
}
func (protectBranch *ProtectedBranch) HasDeployKeyForcePush() bool {
return slices.Contains(protectBranch.ForcePushAllowlistUserIDs, user_model.DeployKeyUserID)
}
+26
View File
@@ -12,6 +12,7 @@ import (
user_model "gitea.dev/models/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBranchRuleMatch(t *testing.T) {
@@ -202,3 +203,28 @@ func TestCanBypassBranchProtection(t *testing.T) {
// User does not bypass when not in allowlisted teams.
testBypass(t, false, pb, user, false)
}
func TestProtectedBranchConfig(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
ctx := t.Context()
pb := &ProtectedBranch{
RepoID: 1, RuleName: "config-test",
ProtectedBranchConfig: ProtectedBranchConfig{
CanPush: true, WhitelistUserIDs: []int64{2, 4}, WhitelistTeamIDs: []int64{1, 2}, StatusCheckContexts: []string{},
},
}
require.NoError(t, db.Insert(ctx, pb))
loaded := unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
assert.Equal(t, pb.ProtectedBranchConfig, loaded.ProtectedBranchConfig)
require.NoError(t, RemoveUserIDFromProtectedBranch(ctx, loaded, 2))
require.NoError(t, RemoveTeamIDFromProtectedBranch(ctx, loaded, 1))
loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
assert.Equal(t, []int64{4}, loaded.WhitelistUserIDs)
assert.Equal(t, []int64{2}, loaded.WhitelistTeamIDs)
assert.True(t, loaded.CanPush)
loaded.ProtectedBranchConfig = ProtectedBranchConfig{}
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
require.NoError(t, UpdateProtectBranch(ctx, repo, loaded, WhitelistOptions{}))
loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID})
assert.Equal(t, ProtectedBranchConfig{}, loaded.ProtectedBranchConfig)
}