diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index d39f709e7..a377bfda8 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -758,7 +758,10 @@ settings.tracker_issue_style.alphanumeric = Alphanumeric settings.tracker_url_format_desc = You can use placeholder {user} {repo} {index} for user name, repository name and issue index. settings.pulls_desc = Enable pull requests to accept contributions between repositories and branches settings.pulls.ignore_whitespace = Ignore changes in whitespace -settings.pulls.allow_rebase_merge = Allow use rebase to merge commits +settings.pulls.merge_only = Always use merge to merge commits +settings.pulls.merge_default = Merge by default, but allow to use rebase to merge commits +settings.pulls.rebase_default = Rebase by default, but allow to use merge to merge commits +settings.pulls.rebase_only = Always use rebase to merge commits settings.danger_zone = Danger Zone settings.cannot_fork_to_same_owner = You cannot fork a repository to its original owner. settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name. diff --git a/internal/database/migrations/migrations.go b/internal/database/migrations/migrations.go index 04c1e3636..c431e46ec 100644 --- a/internal/database/migrations/migrations.go +++ b/internal/database/migrations/migrations.go @@ -63,6 +63,10 @@ var migrations = []Migration{ // on v22. Let's make a noop v22 to make sure every instance will not miss a // real future migration. NewMigration("noop", func(*gorm.DB) error { return nil }), + NewMigration( + "rename repository.pulls_allow_rebase -> repository.pulls_allow_alt", + renameRepoPullsAllowRebaseToPullsAllowAlt, + ), } var errMigrationSkipped = errors.New("the migration has been skipped") diff --git a/internal/database/migrations/v23.go b/internal/database/migrations/v23.go new file mode 100644 index 000000000..20b98ed35 --- /dev/null +++ b/internal/database/migrations/v23.go @@ -0,0 +1,20 @@ +// Copyright 2025 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "gorm.io/gorm" +) + +func renameRepoPullsAllowRebaseToPullsAllowAlt(db *gorm.DB) error { + type Repository struct { + PullsAllowRebase bool `gorm:"not null;default:FALSE"` + PullsAllowAlt bool `gorm:"not null;default:FALSE"` + } + if db.Migrator().HasColumn(&Repository{}, "PullsAllowAlt") { + return errMigrationSkipped + } + return db.Migrator().RenameColumn(&Repository{}, "PullsAllowRebase", "PullsAllowAlt") +} diff --git a/internal/database/migrations/v23_test.go b/internal/database/migrations/v23_test.go new file mode 100644 index 000000000..f5d158c92 --- /dev/null +++ b/internal/database/migrations/v23_test.go @@ -0,0 +1,56 @@ +// Copyright 2025 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gogs.io/gogs/internal/dbtest" +) + +type repositoryPreV22 struct { + PullsAllowRebase bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"` +} + +func (*repositoryPreV22) TableName() string { + return "repository" +} + +type repositoryV22 struct { + PullsAllowAlt bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"` +} + +func (*repositoryV22) TableName() string { + return "repository" +} + +func TestRenameRepoPullsAllowRebaseToPullsAllowAlt(t *testing.T) { + if testing.Short() { + t.Skip() + } + t.Parallel() + + db := dbtest.NewDB(t, "renamePullsAllowRebaseToPullsAllowAlt", new(repositoryPreV22)) + err := db.Create( + &repositoryPreV22{ + PullsAllowRebase: false, + }, + ).Error + require.NoError(t, err) + assert.False(t, db.Migrator().HasColumn(&repositoryV22{}, "PullsAllowAlt")) + assert.True(t, db.Migrator().HasColumn(&repositoryPreV22{}, "PullsAllowRebase")) + + err = renameRepoPullsAllowRebaseToPullsAllowAlt(db) + require.NoError(t, err) + assert.True(t, db.Migrator().HasColumn(&repositoryV22{}, "PullsAllowAlt")) + assert.False(t, db.Migrator().HasColumn(&repositoryPreV22{}, "PullsAllowRebase")) + + // Re-run should be skipped + err = renameRepoPullsAllowRebaseToPullsAllowAlt(db) + require.Equal(t, errMigrationSkipped, err) +} diff --git a/internal/database/pull.go b/internal/database/pull.go index 06f88b877..eec127958 100644 --- a/internal/database/pull.go +++ b/internal/database/pull.go @@ -254,8 +254,12 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle remoteHeadBranch := "head_repo/" + pr.HeadBranch // Check if merge style is allowed, reset to default style if not - if mergeStyle == MERGE_STYLE_REBASE && !pr.BaseRepo.PullsAllowRebase { - mergeStyle = MERGE_STYLE_REGULAR + if !pr.BaseRepo.PullsAllowAlt { + if pr.BaseRepo.PullsPreferRebase { + mergeStyle = MERGE_STYLE_REBASE + } else { + mergeStyle = MERGE_STYLE_REGULAR + } } switch mergeStyle { diff --git a/internal/database/repo.go b/internal/database/repo.go index f55c35160..35572d8a0 100644 --- a/internal/database/repo.go +++ b/internal/database/repo.go @@ -201,7 +201,8 @@ type Repository struct { ExternalMetas map[string]string `xorm:"-" gorm:"-" json:"-"` EnablePulls bool `xorm:"NOT NULL DEFAULT true" gorm:"not null;default:TRUE"` PullsIgnoreWhitespace bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"` - PullsAllowRebase bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"` + PullsPreferRebase bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"` + PullsAllowAlt bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"` IsFork bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"` ForkID int64 @@ -288,6 +289,14 @@ func (repo *Repository) MustOwner() *User { return repo.mustOwner(x) } +func (repo *Repository) PullsAllowMerge() bool { + return !repo.PullsPreferRebase || repo.PullsAllowAlt +} + +func (repo *Repository) PullsAllowRebase() bool { + return repo.PullsPreferRebase || repo.PullsAllowAlt +} + func (repo *Repository) FullName() string { return repo.MustOwner().Name + "/" + repo.Name } diff --git a/internal/form/repo.go b/internal/form/repo.go index 67066de29..5485598d5 100644 --- a/internal/form/repo.go +++ b/internal/form/repo.go @@ -7,6 +7,7 @@ package form import ( "net/url" "strings" + "slices" "github.com/go-macaron/binding" "github.com/unknwon/com" @@ -117,10 +118,36 @@ type RepoSetting struct { TrackerIssueStyle string EnablePulls bool PullsIgnoreWhitespace bool - PullsAllowRebase bool + PullsMergeType string } +func (f *RepoSetting) PullsPreferRebase() bool { + return strings.HasPrefix(f.PullsMergeType, "rebase_") +} + +func (f *RepoSetting) PullsAllowAlt() bool { + return strings.HasSuffix(f.PullsMergeType, "_default") +} + +const ( + MERGE_STYLE_SETTING_MERGE_ONLY = "merge_only" + MERGE_STYLE_SETTING_MERGE_DEFAULT = "merge_default" + MERGE_STYLE_SETTING_REBASE_DEFAULT = "rebase_default" + MERGE_STYLE_SETTING_REBASE_ONLY = "rebase_only" +) + +const ERR_INVALID_MERGE_TYPE = "InvalidMergeTypeError" + func (f *RepoSetting) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + valid := []string{ + MERGE_STYLE_SETTING_MERGE_ONLY, + MERGE_STYLE_SETTING_MERGE_DEFAULT, + MERGE_STYLE_SETTING_REBASE_DEFAULT, + MERGE_STYLE_SETTING_REBASE_ONLY, + } + if mt := f.PullsMergeType; !slices.Contains(valid, mt) { + errs.Add([]string{mt}, ERR_INVALID_MERGE_TYPE, "InvalidMergeType") + } return validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/internal/route/repo/setting.go b/internal/route/repo/setting.go index 196c1c62b..95059d2f8 100644 --- a/internal/route/repo/setting.go +++ b/internal/route/repo/setting.go @@ -155,7 +155,8 @@ func SettingsPost(c *context.Context, f form.RepoSetting) { repo.ExternalTrackerStyle = f.TrackerIssueStyle repo.EnablePulls = f.EnablePulls repo.PullsIgnoreWhitespace = f.PullsIgnoreWhitespace - repo.PullsAllowRebase = f.PullsAllowRebase + repo.PullsPreferRebase = f.PullsPreferRebase() + repo.PullsAllowAlt = f.PullsAllowAlt() if !repo.EnableWiki || repo.EnableExternalWiki { repo.AllowPublicWiki = false diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index b83b11d0d..33a3eae38 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -195,16 +195,18 @@
{{.CSRFTokenHTML}} -
-
- - + {{if .Issue.Repo.PullsAllowMerge}} +
+
+ + +
-
+ {{end}} {{if .Issue.Repo.PullsAllowRebase}}
- +
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 5122fc37c..98f400cfa 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -231,9 +231,27 @@
-
- - +
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +