mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-29 00:59:39 +09:00
feat: Add default PR branch update style setting (#37410)
Adds repository-level settings for pull request branch updates so admins can choose the default update method and disable merge or rebase updates. <img width="1025" height="158" src="https://github.com/user-attachments/assets/d030973b-0ddd-4035-b04f-145c445084d7" /> --------- Co-authored-by: OpenAI Codex (GPT-5) <codex@openai.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
co-authored by
OpenAI Codex
silverwind
Claude
wxiaoguang
parent
16189a68c4
commit
34fd3c9f06
@@ -29,6 +29,16 @@ const (
|
||||
MergeStyleRebaseUpdate MergeStyle = "rebase-update-only"
|
||||
)
|
||||
|
||||
// UpdateStyle is a pull request branch update style
|
||||
type UpdateStyle string
|
||||
|
||||
const (
|
||||
// UpdateStyleMerge merges the base branch into the pull request branch
|
||||
UpdateStyleMerge UpdateStyle = "merge"
|
||||
// UpdateStyleRebase rebases the pull request branch onto the base branch
|
||||
UpdateStyleRebase UpdateStyle = "rebase"
|
||||
)
|
||||
|
||||
// UpdateDefaultBranch updates the default branch
|
||||
func UpdateDefaultBranch(ctx context.Context, repo *Repository) error {
|
||||
_, err := db.GetEngine(ctx).ID(repo.ID).Cols("default_branch").Update(repo)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -30,3 +31,82 @@ func TestDefaultTargetBranchSelection(t *testing.T) {
|
||||
repo.Units = nil
|
||||
assert.Equal(t, "branch2", repo.GetPullRequestTargetBranch(ctx))
|
||||
}
|
||||
|
||||
func TestPullRequestConfigFromDB(t *testing.T) {
|
||||
cases := []struct {
|
||||
// name describes the row shape under test; the comments capture why each row matters.
|
||||
name string
|
||||
json string
|
||||
wantMergeUpdate bool
|
||||
wantRebaseUpdate bool
|
||||
wantDefaultStyle UpdateStyle
|
||||
wantValidatesPass bool
|
||||
}{
|
||||
{
|
||||
// Empty object exercises the all-defaults path (e.g. fresh repos created via low-level paths).
|
||||
name: "defaults", json: "{}",
|
||||
wantMergeUpdate: true, wantRebaseUpdate: true,
|
||||
wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true,
|
||||
},
|
||||
{
|
||||
// Realistic upgrade case: pre-PR JSON lacks the new fields and has AllowRebaseUpdate=false.
|
||||
// Historical setting must be preserved while new fields take safe defaults.
|
||||
name: "legacy without new fields",
|
||||
json: `{"AllowMerge":true,"AllowRebase":true,"AllowRebaseMerge":true,"AllowSquash":true,"AllowRebaseUpdate":false}`,
|
||||
wantMergeUpdate: true, wantRebaseUpdate: false,
|
||||
wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true,
|
||||
},
|
||||
{
|
||||
// Partially-migrated row with explicit empty string must normalize so ValidateUpdateSettings passes.
|
||||
name: "empty default style", json: `{"DefaultUpdateStyle":""}`,
|
||||
wantMergeUpdate: true, wantRebaseUpdate: true,
|
||||
wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg := new(PullRequestsConfig)
|
||||
assert.NoError(t, cfg.FromDB([]byte(tc.json)))
|
||||
assert.Equal(t, tc.wantMergeUpdate, cfg.AllowMergeUpdate)
|
||||
assert.Equal(t, tc.wantRebaseUpdate, cfg.AllowRebaseUpdate)
|
||||
assert.Equal(t, tc.wantDefaultStyle, cfg.DefaultUpdateStyle)
|
||||
if tc.wantValidatesPass {
|
||||
assert.NoError(t, cfg.ValidateUpdateSettings())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullRequestConfigValidateUpdateSettingsInvalidArgument(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
cfg PullRequestsConfig
|
||||
}{
|
||||
{
|
||||
name: "invalid default style",
|
||||
cfg: PullRequestsConfig{
|
||||
AllowMergeUpdate: true,
|
||||
AllowRebaseUpdate: true,
|
||||
DefaultUpdateStyle: "invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no update style enabled",
|
||||
cfg: PullRequestsConfig{
|
||||
DefaultUpdateStyle: UpdateStyleMerge,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default update style disabled",
|
||||
cfg: PullRequestsConfig{
|
||||
AllowRebaseUpdate: true,
|
||||
DefaultUpdateStyle: UpdateStyleMerge,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.ErrorIs(t, tc.cfg.ValidateUpdateSettings(), util.ErrInvalidArgument)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,9 @@ type PullRequestsConfig struct {
|
||||
AllowFastForwardOnly bool
|
||||
AllowManualMerge bool
|
||||
AutodetectManualMerge bool
|
||||
AllowMergeUpdate bool
|
||||
AllowRebaseUpdate bool
|
||||
DefaultUpdateStyle UpdateStyle
|
||||
DefaultDeleteBranchAfterMerge bool
|
||||
DefaultMergeStyle MergeStyle
|
||||
DefaultAllowMaintainerEdit bool
|
||||
@@ -139,7 +141,9 @@ func DefaultPullRequestsConfig() *PullRequestsConfig {
|
||||
AllowRebaseMerge: true,
|
||||
AllowSquash: true,
|
||||
AllowFastForwardOnly: true,
|
||||
AllowMergeUpdate: true,
|
||||
AllowRebaseUpdate: true,
|
||||
DefaultUpdateStyle: UpdateStyleMerge,
|
||||
DefaultAllowMaintainerEdit: true,
|
||||
}
|
||||
cfg.DefaultDeleteBranchAfterMerge = setting.Repository.PullRequest.DefaultDeleteBranchAfterMerge
|
||||
@@ -152,7 +156,9 @@ func DefaultPullRequestsConfig() *PullRequestsConfig {
|
||||
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
|
||||
// set default values for existing PullRequestConfig in DB
|
||||
*cfg = *DefaultPullRequestsConfig()
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
_ = json.UnmarshalHandleDoubleEncode(bs, &cfg) // don't let corrupted database value cause unnecessary 500 error
|
||||
cfg.DefaultUpdateStyle = util.IfZero(cfg.DefaultUpdateStyle, UpdateStyleMerge)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a PullRequestsConfig to a serialized format.
|
||||
@@ -170,6 +176,32 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool {
|
||||
mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge
|
||||
}
|
||||
|
||||
// IsUpdateStyleAllowed returns if a pull request branch update style is allowed
|
||||
func (cfg *PullRequestsConfig) IsUpdateStyleAllowed(updateStyle UpdateStyle) bool {
|
||||
switch updateStyle {
|
||||
case UpdateStyleMerge:
|
||||
return cfg.AllowMergeUpdate
|
||||
case UpdateStyleRebase:
|
||||
return cfg.AllowRebaseUpdate
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateUpdateSettings checks that the AllowMerge/RebaseUpdate flags and DefaultUpdateStyle are mutually consistent.
|
||||
func (cfg *PullRequestsConfig) ValidateUpdateSettings() error {
|
||||
if cfg.DefaultUpdateStyle != UpdateStyleMerge && cfg.DefaultUpdateStyle != UpdateStyleRebase {
|
||||
return util.NewInvalidArgumentErrorf("default update style must be merge or rebase")
|
||||
}
|
||||
if !cfg.AllowMergeUpdate && !cfg.AllowRebaseUpdate {
|
||||
return util.NewInvalidArgumentErrorf("at least one pull request branch update style must be enabled")
|
||||
}
|
||||
if !cfg.IsUpdateStyleAllowed(cfg.DefaultUpdateStyle) {
|
||||
return util.NewInvalidArgumentErrorf("default update style must be enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefaultPullRequestsUnit(repoID int64) RepoUnit {
|
||||
return RepoUnit{RepoID: repoID, Type: unit.TypePullRequests, Config: DefaultPullRequestsConfig()}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user