mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-23 23:14:32 +02:00
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:
1 parent
f270534e83
commit
c6e2513570
25 files changed
+659
-334
No files matched your search
@@ -518,3 +518,20 @@ func ModifyColumn(ctx context.Context, x EngineMigration, tableName string, col
|
||||
func Iterate[Bean any](ctx context.Context, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
|
||||
return db.Iterate(ctx, cond, f)
|
||||
}
|
||||
|
||||
func EngineTransaction(x EngineMigration, f func(Session) error) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
err := f(sess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func IterateTableByColumn[Bean any](ctx context.Context, x EngineMigration, sess Session, colName string, cond builder.Cond, f func(ctx context.Context, bean *Bean) error) error {
|
||||
return db.IterateTableByColumn(ctx, x, sess, colName, cond, f)
|
||||
}
|
||||
@@ -436,6 +436,7 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(357, "Normalize legacy team authorize values", v29.NormalizeLegacyTeamAuthorize),
|
||||
newMigration(358, "Track previous remember-me token hashes", v29.AddPreviousAuthTokenHash),
|
||||
newMigration(359, "Add stage and number columns to action_task_step", v29.AddStageAndNumberToActionTaskStep),
|
||||
newMigration(360, "Move branch protection settings to JSON", v29.MoveBranchProtectionConfigToJSON),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v29
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type protectedBranchConfigV358 struct {
|
||||
CanPush bool `xorm:"NOT NULL DEFAULT false" json:"can_push,omitzero"`
|
||||
EnableWhitelist bool `json:"enable_whitelist,omitzero"`
|
||||
WhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"whitelist_user_ids,omitzero"`
|
||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"whitelist_team_ids,omitzero"`
|
||||
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false" json:"enable_merge_whitelist,omitzero"`
|
||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false" json:"-"`
|
||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"merge_whitelist_user_ids,omitzero"`
|
||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"merge_whitelist_team_ids,omitzero"`
|
||||
EnableBypassAllowlist bool `xorm:"NOT NULL DEFAULT false" json:"enable_bypass_allowlist,omitzero"`
|
||||
BypassAllowlistUserIDs []int64 `xorm:"JSON TEXT" json:"bypass_allowlist_user_ids,omitzero"`
|
||||
BypassAllowlistTeamIDs []int64 `xorm:"JSON TEXT" json:"bypass_allowlist_team_ids,omitzero"`
|
||||
CanForcePush bool `xorm:"NOT NULL DEFAULT false" json:"can_force_push,omitzero"`
|
||||
EnableForcePushAllowlist bool `xorm:"NOT NULL DEFAULT false" json:"enable_force_push_allowlist,omitzero"`
|
||||
ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT" json:"force_push_allowlist_user_ids,omitzero"`
|
||||
ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT" json:"force_push_allowlist_team_ids,omitzero"`
|
||||
ForcePushAllowlistDeployKeys bool `xorm:"NOT NULL DEFAULT false" json:"-"`
|
||||
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false" json:"enable_status_check,omitzero"`
|
||||
StatusCheckContexts []string `xorm:"JSON TEXT" json:"status_check_contexts,omitzero"`
|
||||
EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false" json:"enable_approvals_whitelist,omitzero"`
|
||||
ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT" json:"approvals_whitelist_user_ids,omitzero"`
|
||||
ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT" json:"approvals_whitelist_team_ids,omitzero"`
|
||||
RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0" json:"required_approvals,omitzero"`
|
||||
BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false" json:"block_on_rejected_reviews,omitzero"`
|
||||
BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false" json:"block_on_official_review_requests,omitzero"`
|
||||
BlockOnCodeownerReviews bool `xorm:"NOT NULL DEFAULT false" json:"block_on_codeowner_reviews,omitzero"`
|
||||
BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false" json:"block_on_outdated_branch,omitzero"`
|
||||
DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false" json:"dismiss_stale_approvals,omitzero"`
|
||||
IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false" json:"ignore_stale_approvals,omitzero"`
|
||||
RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false" json:"require_signed_commits,omitzero"`
|
||||
ProtectedFilePatterns string `xorm:"TEXT" json:"protected_file_patterns,omitzero"`
|
||||
UnprotectedFilePatterns string `xorm:"TEXT" json:"unprotected_file_patterns,omitzero"`
|
||||
BlockAdminMergeOverride bool `xorm:"NOT NULL DEFAULT false" json:"block_admin_merge_override,omitzero"`
|
||||
}
|
||||
|
||||
type protectedBranchV358 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
BranchName string `xorm:"UNIQUE(s)"`
|
||||
Priority int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
Config string `xorm:"LONGTEXT"`
|
||||
CreatedUnix timeutil.TimeStamp
|
||||
UpdatedUnix timeutil.TimeStamp
|
||||
}
|
||||
|
||||
func (protectedBranchV358) TableName() string { return "protected_branch" }
|
||||
|
||||
type intermediateProtectedBranch struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
NewConfig string `xorm:"LONGTEXT config"`
|
||||
OldConfig protectedBranchConfigV358 `xorm:"extends"`
|
||||
}
|
||||
|
||||
func (intermediateProtectedBranch) TableName() string { return "protected_branch" }
|
||||
|
||||
func MoveBranchProtectionConfigToJSON(ctx context.Context, x base.EngineMigration) error {
|
||||
if _, err := x.SyncWithOptions(xorm.SyncOptions{IgnoreDropIndices: true}, new(protectedBranchV358)); err != nil {
|
||||
return err
|
||||
}
|
||||
exists, err := x.Dialect().IsColumnExist(x.DB(), ctx, "protected_branch", "can_push")
|
||||
if err != nil || !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
err = base.EngineTransaction(x, func(sess base.Session) error {
|
||||
err := base.IterateTableByColumn(ctx, x, sess, "id", nil, func(ctx context.Context, pb *intermediateProtectedBranch) error {
|
||||
if pb.NewConfig != "" {
|
||||
return nil // skip the already migrated fields
|
||||
}
|
||||
const deployKeyUserID int64 = -3 // frozen user.DeployKeyUserID
|
||||
if pb.OldConfig.WhitelistDeployKeys {
|
||||
pb.OldConfig.WhitelistUserIDs = append(pb.OldConfig.WhitelistUserIDs, deployKeyUserID)
|
||||
}
|
||||
if pb.OldConfig.ForcePushAllowlistDeployKeys {
|
||||
pb.OldConfig.ForcePushAllowlistUserIDs = append(pb.OldConfig.ForcePushAllowlistUserIDs, deployKeyUserID)
|
||||
}
|
||||
configJson, err := json.Marshal(pb.OldConfig)
|
||||
util.MustNoError(err)
|
||||
_, err = sess.ID(pb.ID).Cols("config").Update(&protectedBranchV358{Config: string(configJson)})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacyTableInfo, err := x.TableInfo(new(protectedBranchConfigV358))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return base.DropTableColumns(sess, "protected_branch", legacyTableInfo.ColumnsSeq()...)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// SQLite rebuilds the table when dropping columns, so restore its unique index.
|
||||
return x.Sync(new(protectedBranchV358))
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v29
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type protectedBranchBeforeV358 struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
BranchName string `xorm:"UNIQUE(s)"`
|
||||
Priority int64
|
||||
CreatedUnix timeutil.TimeStamp
|
||||
UpdatedUnix timeutil.TimeStamp
|
||||
Config protectedBranchConfigV358 `xorm:"extends"`
|
||||
}
|
||||
|
||||
func (protectedBranchBeforeV358) TableName() string { return "protected_branch" }
|
||||
|
||||
func TestMoveBranchProtectionConfigToJSON(t *testing.T) {
|
||||
x, deferrable := migrationtest.PrepareTestEnv(t, 0, new(protectedBranchBeforeV358))
|
||||
defer deferrable()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
|
||||
const configJSON = `{
|
||||
"can_push":true,
|
||||
"enable_whitelist":true,
|
||||
"whitelist_user_ids":[2,4,-3],
|
||||
"whitelist_team_ids":[1,2],
|
||||
"enable_merge_whitelist":true,
|
||||
"merge_whitelist_user_ids":[3,5],
|
||||
"merge_whitelist_team_ids":[3,4],
|
||||
"enable_bypass_allowlist":true,
|
||||
"bypass_allowlist_user_ids":[6,7],
|
||||
"bypass_allowlist_team_ids":[5,6],
|
||||
"can_force_push":true,
|
||||
"enable_force_push_allowlist":true,
|
||||
"force_push_allowlist_user_ids":[8,9,-3],
|
||||
"force_push_allowlist_team_ids":[7,8],
|
||||
"enable_status_check":true,
|
||||
"status_check_contexts":["ci/*","lint"],
|
||||
"enable_approvals_whitelist":true,
|
||||
"approvals_whitelist_user_ids":[10,11],
|
||||
"approvals_whitelist_team_ids":[9,10],
|
||||
"required_approvals":2,
|
||||
"block_on_rejected_reviews":true,
|
||||
"block_on_official_review_requests":true,
|
||||
"block_on_codeowner_reviews":true,
|
||||
"block_on_outdated_branch":true,
|
||||
"dismiss_stale_approvals":true,
|
||||
"ignore_stale_approvals":true,
|
||||
"require_signed_commits":true,
|
||||
"protected_file_patterns":"*.go;*.md",
|
||||
"unprotected_file_patterns":"docs/**",
|
||||
"block_admin_merge_override":true
|
||||
}`
|
||||
var config protectedBranchConfigV358
|
||||
require.NoError(t, json.Unmarshal([]byte(configJSON), &config))
|
||||
config.WhitelistUserIDs = []int64{2, 4}
|
||||
config.ForcePushAllowlistUserIDs = []int64{8, 9}
|
||||
config.WhitelistDeployKeys = true
|
||||
config.ForcePushAllowlistDeployKeys = true
|
||||
for i := range 101 {
|
||||
branch := protectedBranchBeforeV358{RepoID: 1, BranchName: fmt.Sprintf("release-%d/*", i), Priority: int64(i), CreatedUnix: 123, UpdatedUnix: 456}
|
||||
if i%2 == 0 {
|
||||
branch.Config = config
|
||||
} else if i == 1 {
|
||||
branch.Config.StatusCheckContexts = []string{}
|
||||
}
|
||||
_, err := x.Insert(&branch)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, MoveBranchProtectionConfigToJSON(t.Context(), x))
|
||||
// A retry must preserve the migrated configuration and its indexes.
|
||||
require.NoError(t, MoveBranchProtectionConfigToJSON(t.Context(), x))
|
||||
|
||||
var branches []protectedBranchV358
|
||||
require.NoError(t, x.Asc("id").Find(&branches))
|
||||
require.Len(t, branches, 101)
|
||||
for i, branch := range branches {
|
||||
assert.EqualValues(t, i+1, branch.ID)
|
||||
assert.EqualValues(t, 1, branch.RepoID)
|
||||
assert.Equal(t, fmt.Sprintf("release-%d/*", i), branch.BranchName)
|
||||
assert.EqualValues(t, i, branch.Priority)
|
||||
assert.EqualValues(t, 123, branch.CreatedUnix)
|
||||
assert.EqualValues(t, 456, branch.UpdatedUnix)
|
||||
if i%2 == 0 {
|
||||
assert.JSONEq(t, configJSON, branch.Config)
|
||||
} else if i == 1 {
|
||||
assert.JSONEq(t, `{"status_check_contexts":[]}`, branch.Config)
|
||||
} else {
|
||||
assert.JSONEq(t, "{}", branch.Config)
|
||||
}
|
||||
}
|
||||
tables, err := x.DBMetas()
|
||||
require.NoError(t, err)
|
||||
for _, table := range tables {
|
||||
if table.Name == "protected_branch" {
|
||||
assert.ElementsMatch(t, []string{"id", "repo_id", "branch_name", "priority", "config", "created_unix", "updated_unix"}, table.ColumnsSeq())
|
||||
}
|
||||
}
|
||||
_, err = x.Insert(&protectedBranchV358{RepoID: 1, BranchName: branches[0].BranchName})
|
||||
require.Error(t, err, "the repository/rule unique constraint must survive the migration")
|
||||
}
|
||||
Reference in new issue
Block a user