From ba4f8fa24672c95e292aeee583c6055cc8cfc911 Mon Sep 17 00:00:00 2001 From: bircni Date: Mon, 19 Oct 2026 11:15:41 +0200 Subject: [PATCH] enhance(pull)!: add per-repository default squash merge commit message (#39816) Adds a repository setting for the default squash merge commit message body with GitHub's options `PR_BODY`, `COMMIT_MESSAGES` and `BLANK`, also settable via `squash_merge_commit_message` in the API. The commit title stays the pull request title. 1. Breaking: API squash merges without `merge_message_field` now get the same default message as the web merge box instead of a title-only commit, which affects clients sending only `Do` like Renovate. An empty value keeps title-only. 2. Breaking: `[repository.pull-request] DEFAULT_SQUASH_MERGE_MESSAGE` replaces `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`, which still works as a deprecated fallback. Repositories store the default when created or next saved. 3. Deprecated app.ini keys now migrate through one table instead of per-loader fallbacks, which also fixes the ignored `[mailer] CERT_FILE` and `KEY_FILE` fallbacks. Deprecation warnings no longer claim a removal version. grafik --------- Co-authored-by: silverwind --- custom/conf/app.example.ini | 5 +- models/repo/git.go | 13 ++++ models/repo/repo_unit.go | 2 + modules/setting/config_deprecated.go | 64 ++++++++++++++++++++ modules/setting/config_provider.go | 4 +- modules/setting/git.go | 10 --- modules/setting/git_test.go | 1 + modules/setting/lfs.go | 12 ---- modules/setting/lfs_test.go | 2 + modules/setting/log.go | 12 ++-- modules/setting/mailer.go | 50 +-------------- modules/setting/migrations.go | 6 +- modules/setting/mirror.go | 9 --- modules/setting/oauth2.go | 6 -- modules/setting/repository.go | 11 +++- modules/setting/security.go | 4 -- modules/setting/server.go | 32 ++-------- modules/setting/service.go | 2 +- modules/setting/setting.go | 1 + modules/setting/task.go | 6 +- modules/setting/webhook.go | 2 +- modules/structs/repo.go | 3 + options/locale/locale_en-US.json | 4 ++ routers/api/v1/repo/pull.go | 15 ++++- routers/api/v1/repo/repo.go | 4 ++ routers/web/repo/setting/setting.go | 5 ++ services/convert/repository.go | 3 + services/forms/repo_form.go | 23 ++++--- services/forms/repo_form_test.go | 15 +++++ services/pull/pull.go | 14 +++-- services/pull/pull_test.go | 1 + templates/repo/settings/options.tmpl | 9 +++ templates/swagger/v1-openapi3.generated.json | 9 +++ templates/swagger/v1-swagger.generated.json | 9 +++ tests/integration/api_pull_test.go | 11 ++++ tests/integration/api_repo_edit_test.go | 12 +++- tests/integration/pull_merge_test.go | 38 +++++++++++- 37 files changed, 273 insertions(+), 156 deletions(-) create mode 100644 modules/setting/config_deprecated.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index c21d8308222..ffc45acb15e 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1218,8 +1218,9 @@ LEVEL = Info ;; In default merge messages only include approvers who are official ;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true ;; -;; In default squash-merge messages include the commit message of all commits comprising the pull request. -;POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES = false +;; Set default squash merge commit message body, valid options: PR_BODY (pull request description), COMMIT_MESSAGES (commit messages), BLANK (none) +;; Repositories store the current value when created or when their settings are next saved, and can change it there. +;DEFAULT_SQUASH_MERGE_MESSAGE = PR_BODY ;; ;; Add co-authored-by and co-committed-by trailers if committer does not match author ;ADD_CO_COMMITTER_TRAILERS = true diff --git a/models/repo/git.go b/models/repo/git.go index 8da93e393ee..443482e20c9 100644 --- a/models/repo/git.go +++ b/models/repo/git.go @@ -39,6 +39,19 @@ const ( UpdateStyleRebase UpdateStyle = "rebase" ) +// SquashMergeMessage is the source of the default squash merge commit message body, values match GitHub's squash_merge_commit_message: https://docs.github.com/en/rest/repos/repos#update-a-repository +type SquashMergeMessage string + +const ( + SquashMergeMessageBlank SquashMergeMessage = "BLANK" + SquashMergeMessageCommitMessages SquashMergeMessage = "COMMIT_MESSAGES" + SquashMergeMessagePRBody SquashMergeMessage = "PR_BODY" +) + +func (m SquashMergeMessage) IsValid() bool { + return m == SquashMergeMessageBlank || m == SquashMergeMessageCommitMessages || m == SquashMergeMessagePRBody +} + // 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) diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index dd4328648b9..4ea8f6d22d6 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -131,6 +131,7 @@ type PullRequestsConfig struct { DefaultUpdateStyle UpdateStyle DefaultDeleteBranchAfterMerge bool DefaultMergeStyle MergeStyle + DefaultSquashMergeMessage SquashMergeMessage DefaultAllowMaintainerEdit bool DefaultTargetBranch string } @@ -150,6 +151,7 @@ func DefaultPullRequestsConfig() *PullRequestsConfig { cfg.DefaultDeleteBranchAfterMerge = setting.Repository.PullRequest.DefaultDeleteBranchAfterMerge cfg.DefaultMergeStyle = MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle) cfg.DefaultMergeStyle = util.IfZero(cfg.DefaultMergeStyle, MergeStyleMerge) + cfg.DefaultSquashMergeMessage = SquashMergeMessage(setting.Repository.PullRequest.DefaultSquashMergeMessage) return cfg } diff --git a/modules/setting/config_deprecated.go b/modules/setting/config_deprecated.go new file mode 100644 index 00000000000..b4f0f27c03f --- /dev/null +++ b/modules/setting/config_deprecated.go @@ -0,0 +1,64 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "strconv" + + "gitea.dev/modules/util" +) + +type deprecatedKey struct { + oldSection, oldKey, newSection, newKey string + convert func(old ConfigKey) string +} + +func invertBool(old ConfigKey) string { + return strconv.FormatBool(!old.MustBool()) +} + +// keep rows, users upgrading from older versions still need them +var deprecatedKeys = []deprecatedKey{ + {"server", "LFS_CONTENT_PATH", "lfs", "PATH", nil}, + {"server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", nil}, + {"server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS", nil}, + {"server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY", nil}, + {"server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", nil}, + {"mailer", "MAILER_TYPE", "mailer", "PROTOCOL", func(old ConfigKey) string { return old.In("", []string{"sendmail"}) }}, + {"mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL", func(old ConfigKey) string { return util.Iif(old.MustBool(), "smtps", "smtp+starttls") }}, + {"mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO", invertBool}, + {"mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT", nil}, + {"mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT", nil}, + {"mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE", nil}, + {"mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE", nil}, + {"mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT", invertBool}, + {"repository", "DISABLE_MIRRORS", "mirror", "DISABLE_NEW_PULL", nil}, + {"git.reflog", "ENABLED", "git.config", "core.logAllRefUpdates", func(old ConfigKey) string { return old.In("true", []string{"true", "false"}) }}, + {"git.reflog", "EXPIRATION", "git.config", "gc.reflogExpire", func(old ConfigKey) string { return old.String() + ".days" }}, + {"oauth2", "ENABLE", "oauth2", "ENABLED", nil}, + {"cors", "X_FRAME_OPTIONS", "security", "X_FRAME_OPTIONS", nil}, + {"repository.pull-request", "POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES", "repository.pull-request", "DEFAULT_SQUASH_MERGE_MESSAGE", func(old ConfigKey) string { return util.Iif(old.MustBool(), "COMMIT_MESSAGES", "PR_BODY") }}, +} + +// applyDeprecatedKeys copies each deprecated key of the table to its replacement unless the replacement is present +func applyDeprecatedKeys(rootCfg ConfigProvider) { + for _, deprecated := range deprecatedKeys { + oldSec, _ := rootCfg.GetSection(deprecated.oldSection) + if oldSec == nil || !oldSec.HasKey(deprecated.oldKey) { + continue + } + deprecatedSetting(rootCfg, deprecated.oldSection, deprecated.oldKey, deprecated.newSection, deprecated.newKey) + if newSec, _ := rootCfg.GetSection(deprecated.newSection); newSec != nil && newSec.HasKey(deprecated.newKey) { + continue + } + oldKey := oldSec.Key(deprecated.oldKey) + value := oldKey.String() + if deprecated.convert != nil { + value = deprecated.convert(oldKey) + } + if value != "" { + _, _ = rootCfg.Section(deprecated.newSection).NewKey(deprecated.newKey, value) + } + } +} diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 1d4b3f9665c..3d01a8dfcdd 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -392,9 +392,9 @@ func LogStartupProblem(skip int, level log.Level, format string, args ...any) { StartupProblems = append(StartupProblems, msg) } -func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) { +func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey string) { if rootCfg.Section(oldSection).HasKey(oldKey) { - LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version) + LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead", oldSection, oldKey, newSection, newKey) } } diff --git a/modules/setting/git.go b/modules/setting/git.go index ff734d97b81..f3cf8e9ca38 100644 --- a/modules/setting/git.go +++ b/modules/setting/git.go @@ -88,16 +88,6 @@ func loadGitFrom(rootCfg ConfigProvider) { GitConfig.SetOption("diff.algorithm", "histogram") GitConfig.SetOption("core.logAllRefUpdates", "true") - secGitReflog := rootCfg.Section("git.reflog") - if secGitReflog.HasKey("ENABLED") { - deprecatedSetting(rootCfg, "git.reflog", "ENABLED", "git.config", "core.logAllRefUpdates", "1.21") - GitConfig.SetOption("core.logAllRefUpdates", secGitReflog.Key("ENABLED").In("true", []string{"true", "false"})) - } - if secGitReflog.HasKey("EXPIRATION") { - deprecatedSetting(rootCfg, "git.reflog", "EXPIRATION", "git.config", "gc.reflogExpire", "1.21") - GitConfig.SetOption("gc.reflogExpire", secGitReflog.Key("EXPIRATION").String()+".days") - } - for _, key := range secGitConfig.Keys() { GitConfig.SetOption(key.Name(), key.String()) } diff --git a/modules/setting/git_test.go b/modules/setting/git_test.go index 1101ec5bed2..ee2a8bea377 100644 --- a/modules/setting/git_test.go +++ b/modules/setting/git_test.go @@ -56,6 +56,7 @@ ENABLED = false EXPIRATION = 123 `) assert.NoError(t, err) + applyDeprecatedKeys(cfg) loadGitFrom(cfg) assert.Equal(t, "false", GitConfig.GetOption("core.logAllRefUpdates")) diff --git a/modules/setting/lfs.go b/modules/setting/lfs.go index 961b1b4d82d..f8f6f2a0b92 100644 --- a/modules/setting/lfs.go +++ b/modules/setting/lfs.go @@ -40,18 +40,6 @@ func loadLFSFrom(rootCfg ConfigProvider) error { lfsSec, _ := rootCfg.GetSection("lfs") - // Specifically default PATH to LFS_CONTENT_PATH - // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version - // if these are removed, the warning will not be shown - deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0") - - if val := sec.Key("LFS_CONTENT_PATH").String(); val != "" { - if lfsSec == nil { - lfsSec = rootCfg.Section("lfs") - } - lfsSec.Key("PATH").MustString(val) // FIXME: INI-MUST-SIDE-EFFECT - } - var err error LFS.Storage, err = getStorage(rootCfg, "lfs", "", lfsSec) if err != nil { diff --git a/modules/setting/lfs_test.go b/modules/setting/lfs_test.go index 1b829d8839c..5a1293c7d5a 100644 --- a/modules/setting/lfs_test.go +++ b/modules/setting/lfs_test.go @@ -29,6 +29,7 @@ PATH = path_used ` cfg, err = NewConfigProviderFromData(iniStr) assert.NoError(t, err) + applyDeprecatedKeys(cfg) assert.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "local", LFS.Storage.Type) @@ -40,6 +41,7 @@ LFS_CONTENT_PATH = deprecatedpath ` cfg, err = NewConfigProviderFromData(iniStr) assert.NoError(t, err) + applyDeprecatedKeys(cfg) assert.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "local", LFS.Storage.Type) diff --git a/modules/setting/log.go b/modules/setting/log.go index 0bd87add96f..e87bd5ff758 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -59,8 +59,8 @@ func prepareLoggerConfig(rootCfg ConfigProvider) { sec.Key("logger.default.MODE").MustString(",") // FIXME: INI-MUST-SIDE-EFFECT } - deprecatedSetting(rootCfg, "log", "ACCESS", "log", "logger.access.MODE", "1.21") - deprecatedSetting(rootCfg, "log", "ENABLE_ACCESS_LOG", "log", "logger.access.MODE", "1.21") + deprecatedSetting(rootCfg, "log", "ACCESS", "log", "logger.access.MODE") + deprecatedSetting(rootCfg, "log", "ENABLE_ACCESS_LOG", "log", "logger.access.MODE") if val := sec.Key("ACCESS").String(); val != "" { sec.Key("logger.access.MODE").MustString(val) // FIXME: INI-MUST-SIDE-EFFECT } @@ -68,8 +68,8 @@ func prepareLoggerConfig(rootCfg ConfigProvider) { sec.Key("logger.access.MODE").SetValue("") } - deprecatedSetting(rootCfg, "log", "ROUTER", "log", "logger.router.MODE", "1.21") - deprecatedSetting(rootCfg, "log", "DISABLE_ROUTER_LOG", "log", "logger.router.MODE", "1.21") + deprecatedSetting(rootCfg, "log", "ROUTER", "log", "logger.router.MODE") + deprecatedSetting(rootCfg, "log", "DISABLE_ROUTER_LOG", "log", "logger.router.MODE") if val := sec.Key("ROUTER").String(); val != "" { sec.Key("logger.router.MODE").MustString(val) // FIXME: INI-MUST-SIDE-EFFECT } @@ -81,8 +81,8 @@ func prepareLoggerConfig(rootCfg ConfigProvider) { sec.Key("logger.router.MODE").SetValue("") } - deprecatedSetting(rootCfg, "log", "XORM", "log", "logger.xorm.MODE", "1.21") - deprecatedSetting(rootCfg, "log", "ENABLE_XORM_LOG", "log", "logger.xorm.MODE", "1.21") + deprecatedSetting(rootCfg, "log", "XORM", "log", "logger.xorm.MODE") + deprecatedSetting(rootCfg, "log", "ENABLE_XORM_LOG", "log", "logger.xorm.MODE") if val := sec.Key("XORM").String(); val != "" { sec.Key("logger.xorm.MODE").MustString(val) // FIXME: INI-MUST-SIDE-EFFECT } diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index e28c467fcb3..b38e04b0f5a 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -76,14 +76,7 @@ func loadMailerFrom(rootCfg ConfigProvider) { // Handle Deprecations and map on to new configuration // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version // if these are removed, the warning will not be shown - deprecatedSetting(rootCfg, "mailer", "MAILER_TYPE", "mailer", "PROTOCOL", "v1.19.0") - if sec.HasKey("MAILER_TYPE") && !sec.HasKey("PROTOCOL") { - if sec.Key("MAILER_TYPE").String() == "sendmail" { - sec.Key("PROTOCOL").MustString("sendmail") // FIXME: INI-MUST-SIDE-EFFECT - } - } - - deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR", "v1.19.0") + deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR") if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") { givenHost := sec.Key("HOST").String() addr, port, err := net.SplitHostPort(givenHost) @@ -99,47 +92,8 @@ func loadMailerFrom(rootCfg ConfigProvider) { sec.Key("SMTP_PORT").MustString(port) // FIXME: INI-MUST-SIDE-EFFECT } - deprecatedSetting(rootCfg, "mailer", "IS_TLS_ENABLED", "mailer", "PROTOCOL", "v1.19.0") - if sec.HasKey("IS_TLS_ENABLED") && !sec.HasKey("PROTOCOL") { - if sec.Key("IS_TLS_ENABLED").MustBool() { - sec.Key("PROTOCOL").MustString("smtps") // FIXME: INI-MUST-SIDE-EFFECT - } else { - sec.Key("PROTOCOL").MustString("smtp+starttls") // FIXME: INI-MUST-SIDE-EFFECT - } - } - - deprecatedSetting(rootCfg, "mailer", "DISABLE_HELO", "mailer", "ENABLE_HELO", "v1.19.0") - if sec.HasKey("DISABLE_HELO") && !sec.HasKey("ENABLE_HELO") { - sec.Key("ENABLE_HELO").MustBool(!sec.Key("DISABLE_HELO").MustBool()) // FIXME: INI-MUST-SIDE-EFFECT - } - - deprecatedSetting(rootCfg, "mailer", "SKIP_VERIFY", "mailer", "FORCE_TRUST_SERVER_CERT", "v1.19.0") - if sec.HasKey("SKIP_VERIFY") && !sec.HasKey("FORCE_TRUST_SERVER_CERT") { - sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(sec.Key("SKIP_VERIFY").MustBool()) // FIXME: INI-MUST-SIDE-EFFECT - } - - deprecatedSetting(rootCfg, "mailer", "USE_CERTIFICATE", "mailer", "USE_CLIENT_CERT", "v1.19.0") - if sec.HasKey("USE_CERTIFICATE") && !sec.HasKey("USE_CLIENT_CERT") { - sec.Key("USE_CLIENT_CERT").MustBool(sec.Key("USE_CERTIFICATE").MustBool()) // FIXME: INI-MUST-SIDE-EFFECT - } - - deprecatedSetting(rootCfg, "mailer", "CERT_FILE", "mailer", "CLIENT_CERT_FILE", "v1.19.0") - if sec.HasKey("CERT_FILE") && !sec.HasKey("CLIENT_CERT_FILE") { - sec.Key("CERT_FILE").MustString(sec.Key("CERT_FILE").String()) - } - - deprecatedSetting(rootCfg, "mailer", "KEY_FILE", "mailer", "CLIENT_KEY_FILE", "v1.19.0") - if sec.HasKey("KEY_FILE") && !sec.HasKey("CLIENT_KEY_FILE") { - sec.Key("KEY_FILE").MustString(sec.Key("KEY_FILE").String()) - } - - deprecatedSetting(rootCfg, "mailer", "ENABLE_HTML_ALTERNATIVE", "mailer", "SEND_AS_PLAIN_TEXT", "v1.19.0") - if sec.HasKey("ENABLE_HTML_ALTERNATIVE") && !sec.HasKey("SEND_AS_PLAIN_TEXT") { - sec.Key("SEND_AS_PLAIN_TEXT").MustBool(!sec.Key("ENABLE_HTML_ALTERNATIVE").MustBool(false)) // FIXME: INI-MUST-SIDE-EFFECT - } - if sec.HasKey("PROTOCOL") && sec.Key("PROTOCOL").String() == "smtp+startls" { - log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls`` instead. This fallback will be removed in v1.19.0") + log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls` instead.") sec.Key("PROTOCOL").SetValue("smtp+starttls") } diff --git a/modules/setting/migrations.go b/modules/setting/migrations.go index 1833c259492..5c17d1865f1 100644 --- a/modules/setting/migrations.go +++ b/modules/setting/migrations.go @@ -27,9 +27,9 @@ func loadMigrationsFrom(rootCfg ConfigProvider) { egressModeSet := sec.HasKey("EGRESS_MODE") Migrations.EgressMode = normalizePolicyMode(sec.Key("EGRESS_MODE").String()) - deprecatedSetting(rootCfg, "migrations", "ALLOWED_DOMAINS", "migrations", "ALLOWED_HOST_LIST", "v28.0.0") - deprecatedSetting(rootCfg, "migrations", "BLOCKED_DOMAINS", "migrations", "BLOCKED_HOST_LIST", "v28.0.0") - deprecatedSetting(rootCfg, "migrations", "ALLOW_LOCALNETWORKS", "migrations", "ALLOWED_HOST_LIST", "v28.0.0") + deprecatedSetting(rootCfg, "migrations", "ALLOWED_DOMAINS", "migrations", "ALLOWED_HOST_LIST") + deprecatedSetting(rootCfg, "migrations", "BLOCKED_DOMAINS", "migrations", "BLOCKED_HOST_LIST") + deprecatedSetting(rootCfg, "migrations", "ALLOW_LOCALNETWORKS", "migrations", "ALLOWED_HOST_LIST") Migrations.AllowedHostList = ConfigSectionKeyString(sec, "ALLOWED_HOST_LIST") if Migrations.AllowedHostList == "" { var hosts []string diff --git a/modules/setting/mirror.go b/modules/setting/mirror.go index 6b2687088d5..49e29d228b1 100644 --- a/modules/setting/mirror.go +++ b/modules/setting/mirror.go @@ -25,15 +25,6 @@ var Mirror = struct { } func loadMirrorFrom(rootCfg ConfigProvider) { - // Handle old configuration through `[repository]` `DISABLE_MIRRORS` - // - please note this was badly named and only disabled the creation of new pull mirrors - // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version - // if these are removed, the warning will not be shown - deprecatedSetting(rootCfg, "repository", "DISABLE_MIRRORS", "mirror", "ENABLED", "v1.19.0") - if ConfigSectionKeyBool(rootCfg.Section("repository"), "DISABLE_MIRRORS") { - Mirror.DisableNewPull = true - } - if err := rootCfg.Section("mirror").MapTo(&Mirror); err != nil { log.Fatal("Failed to map Mirror settings: %v", err) } diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index d68b4344578..f7838c7c128 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -122,12 +122,6 @@ func loadOAuth2From(rootCfg ConfigProvider) { OAuth2.DefaultApplications = nil } - // Handle the rename of ENABLE to ENABLED - deprecatedSetting(rootCfg, "oauth2", "ENABLE", "oauth2", "ENABLED", "v1.23.0") - if sec.HasKey("ENABLE") && !sec.HasKey("ENABLED") { - OAuth2.Enabled = sec.Key("ENABLE").MustBool(OAuth2.Enabled) - } - if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) { OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile) } diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 05bf4a7274d..e8ee37a363c 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -6,6 +6,7 @@ package setting import ( "os/exec" "path/filepath" + "slices" "strings" "gitea.dev/modules/log" @@ -94,7 +95,7 @@ var ( DefaultMergeMessageAllAuthors bool DefaultMergeMessageMaxApprovers int DefaultMergeMessageOfficialApproversOnly bool - PopulateSquashCommentWithCommitMessages bool + DefaultSquashMergeMessage string AddCoCommitterTrailers bool RetargetChildrenOnMerge bool DelayCheckForInactiveDays int @@ -188,7 +189,7 @@ var ( DefaultMergeMessageAllAuthors bool DefaultMergeMessageMaxApprovers int DefaultMergeMessageOfficialApproversOnly bool - PopulateSquashCommentWithCommitMessages bool + DefaultSquashMergeMessage string AddCoCommitterTrailers bool RetargetChildrenOnMerge bool DelayCheckForInactiveDays int @@ -206,7 +207,7 @@ var ( DefaultMergeMessageAllAuthors: false, DefaultMergeMessageMaxApprovers: 10, DefaultMergeMessageOfficialApproversOnly: true, - PopulateSquashCommentWithCommitMessages: false, + DefaultSquashMergeMessage: "PR_BODY", AddCoCommitterTrailers: true, RetargetChildrenOnMerge: true, DelayCheckForInactiveDays: 7, @@ -339,6 +340,10 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { log.Fatal("Failed to map Repository.PullRequest settings: %v", err) } + if !slices.Contains([]string{"BLANK", "COMMIT_MESSAGES", "PR_BODY"}, Repository.PullRequest.DefaultSquashMergeMessage) { // uppercase unlike other config values, to match GitHub's squash_merge_commit_message which the API also uses + log.Fatal("Invalid repository.pull-request.DEFAULT_SQUASH_MERGE_MESSAGE: %s", Repository.PullRequest.DefaultSquashMergeMessage) + } + if !rootCfg.Section("packages").Key("ENABLED").MustBool(Packages.Enabled) { Repository.DisabledRepoUnits = append(Repository.DisabledRepoUnits, "repo.packages") } diff --git a/modules/setting/security.go b/modules/setting/security.go index 53a70b14100..900b8124961 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -180,10 +180,6 @@ func loadSecurityFrom(rootCfg ConfigProvider) { PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20) - deprecatedSetting(rootCfg, "cors", "X_FRAME_OPTIONS", "security", "X_FRAME_OPTIONS", "v1.26.0") - if !sec.HasKey("X_FRAME_OPTIONS") { - Security.XFrameOptions = rootCfg.Section("cors").Key("X_FRAME_OPTIONS").MustString(Security.XFrameOptions) - } if err := sec.MapTo(&Security); err != nil { log.Fatal("Failed to map security settings: %v", err) } diff --git a/modules/setting/server.go b/modules/setting/server.go index 289db6a044c..96b726d1f88 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -151,14 +151,7 @@ func loadServerFrom(rootCfg ConfigProvider) { HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") HTTPPort = sec.Key("HTTP_PORT").MustString("3000") - // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version - // if these are removed, the warning will not be shown - if sec.HasKey("ENABLE_ACME") { - EnableAcme = sec.Key("ENABLE_ACME").MustBool(false) - } else { - deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0") - EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) - } + EnableAcme = sec.Key("ENABLE_ACME").MustBool(false) protocolCfg := sec.Key("PROTOCOL").String() if protocolCfg != "https" && EnableAcme { @@ -175,29 +168,12 @@ func loadServerFrom(rootCfg ConfigProvider) { AcmeProfile = sec.Key("ACME_PROFILE").MustString("") AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("") - if sec.HasKey("ACME_ACCEPTTOS") { - AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false) - } else { - deprecatedSetting(rootCfg, "server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS", "v1.19.0") - AcmeTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false) - } + AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false) if !AcmeTOS { log.Fatal("ACME TOS is not accepted (ACME_ACCEPTTOS).") } - - if sec.HasKey("ACME_DIRECTORY") { - AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https") - } else { - deprecatedSetting(rootCfg, "server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY", "v1.19.0") - AcmeLiveDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https") - } - - if sec.HasKey("ACME_EMAIL") { - AcmeEmail = sec.Key("ACME_EMAIL").MustString("") - } else { - deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0") - AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("") - } + AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https") + AcmeEmail = sec.Key("ACME_EMAIL").MustString("") } else { CertFile = sec.Key("CERT_FILE").String() KeyFile = sec.Key("KEY_FILE").String() diff --git a/modules/setting/service.go b/modules/setting/service.go index 7a09c132bfb..2ffcd895f3c 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -158,7 +158,7 @@ func loadServiceFrom(rootCfg ConfigProvider) { Service.RegisterManualConfirm = false } if sec.HasKey("EMAIL_DOMAIN_WHITELIST") { - deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21") + deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST") } Service.EmailDomainAllowList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST") Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST") diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 77493c23872..ff92f5c4103 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -117,6 +117,7 @@ func LoadCommonSettings() { func loadCommonSettingsFrom(cfg ConfigProvider) error { // a lot of logic depends on InstallLock value, so it must be loaded before any other settings InstallLock = HasInstallLock(cfg) + applyDeprecatedKeys(cfg) // WARNING: don't change the sequence except you know what you are doing. loadRunModeFrom(cfg) diff --git a/modules/setting/task.go b/modules/setting/task.go index 6f2794c2db9..83ce11b058d 100644 --- a/modules/setting/task.go +++ b/modules/setting/task.go @@ -10,9 +10,9 @@ func loadTaskFrom(rootCfg ConfigProvider) { taskSec := rootCfg.Section("task") queueTaskSec := rootCfg.Section("queue.task") - deprecatedSetting(rootCfg, "task", "QUEUE_TYPE", "queue.task", "TYPE", "v1.19.0") - deprecatedSetting(rootCfg, "task", "QUEUE_CONN_STR", "queue.task", "CONN_STR", "v1.19.0") - deprecatedSetting(rootCfg, "task", "QUEUE_LENGTH", "queue.task", "LENGTH", "v1.19.0") + deprecatedSetting(rootCfg, "task", "QUEUE_TYPE", "queue.task", "TYPE") + deprecatedSetting(rootCfg, "task", "QUEUE_CONN_STR", "queue.task", "CONN_STR") + deprecatedSetting(rootCfg, "task", "QUEUE_LENGTH", "queue.task", "LENGTH") switch taskSec.Key("QUEUE_TYPE").MustString("channel") { case "channel": diff --git a/modules/setting/webhook.go b/modules/setting/webhook.go index a192c5b49b9..4b8dcc29ca6 100644 --- a/modules/setting/webhook.go +++ b/modules/setting/webhook.go @@ -35,7 +35,7 @@ func loadWebhookFrom(rootCfg ConfigProvider) { Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5) Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool() - deprecatedSetting(rootCfg, "webhook", "ALLOWED_HOST_LIST", "security", "ALLOWED_HOST_LIST", "v28.0.0") + deprecatedSetting(rootCfg, "webhook", "ALLOWED_HOST_LIST", "security", "ALLOWED_HOST_LIST") Webhook.AllowedHostList = sec.Key("ALLOWED_HOST_LIST").MustString(Security.AllowedHostList) if Webhook.AllowedHostList != Security.AllowedHostList { checkHostList("[webhook] ALLOWED_HOST_LIST", Webhook.AllowedHostList, false) diff --git a/modules/structs/repo.go b/modules/structs/repo.go index a2a2ef155b9..0156cd7909e 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -120,6 +120,7 @@ type Repository struct { DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"` DefaultMergeStyle string `json:"default_merge_style"` DefaultUpdateStyle string `json:"default_update_style"` + SquashMergeCommitMessage string `json:"squash_merge_commit_message"` DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"` AvatarURL string `json:"avatar_url"` Internal bool `json:"internal"` @@ -236,6 +237,8 @@ type EditRepoOption struct { DefaultMergeStyle *string `json:"default_merge_style,omitempty"` // set to an update style to be used by this repository: "merge" or "rebase". DefaultUpdateStyle *string `json:"default_update_style,omitempty"` + // set the default squash merge commit message body: "PR_BODY", "COMMIT_MESSAGES", or "BLANK". + SquashMergeCommitMessage *string `json:"squash_merge_commit_message,omitempty"` // set to `true` to allow edits from maintainers by default DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"` // set to `true` to archive this repository. diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index bf1b090a8d6..513738ae55e 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -2148,6 +2148,10 @@ "repo.settings.pulls.allow_merge_update": "Enable updating pull request branch by merge", "repo.settings.pulls.allow_rebase_update": "Enable updating pull request branch by rebase", "repo.settings.pulls.default_update_style": "Default branch update style", + "repo.settings.pulls.default_squash_merge_message": "Default squash merge commit message", + "repo.settings.pulls.default_squash_merge_message.title": "Pull request title", + "repo.settings.pulls.default_squash_merge_message.title_and_commit_details": "Pull request title and commit details", + "repo.settings.pulls.default_squash_merge_message.title_and_description": "Pull request title and description", "repo.settings.pulls.default_target_branch": "Default target branch for new pull requests", "repo.settings.pulls.default_target_branch_default": "Default branch (%s)", "repo.settings.pulls.default_delete_branch_after_merge": "Delete pull request branch after merge by default", diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index d853adeef7f..6a391f656c9 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -981,14 +981,23 @@ func MergePullRequest(ctx *context.APIContext) { } message := strings.TrimSpace(form.MergeTitleField) - if len(message) == 0 { - message, _, err = pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do)) + useDefaultSquashBody := !form.HasMergeMessageField && !ctx.Req.Form.Has("merge_message_field") && repo_model.MergeStyle(form.Do) == repo_model.MergeStyleSquash + if len(message) == 0 || useDefaultSquashBody { + defaultTitle, defaultBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do)) if err != nil { ctx.APIErrorInternal(err) return } + message = util.IfZero(message, defaultTitle) + if useDefaultSquashBody { + squashCommitMessages, err := pull_service.GetSquashMergeCommitMessages(ctx, pr) + if err != nil { + ctx.APIErrorInternal(err) + return + } + form.MergeMessageField = git.CommitMessageMerge(squashCommitMessages, defaultBody) + } } - form.MergeMessageField = strings.TrimSpace(form.MergeMessageField) if len(form.MergeMessageField) > 0 { message += "\n\n" + form.MergeMessageField diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 6ac4de3961b..2dc75c9b8c2 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -895,6 +895,10 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { optional.AssignPtrValue(changed, &config.DefaultAllowMaintainerEdit, opts.DefaultAllowMaintainerEdit) optional.AssignPtrString(changed, &config.DefaultMergeStyle, opts.DefaultMergeStyle) optional.AssignPtrString(changed, &config.DefaultUpdateStyle, opts.DefaultUpdateStyle) + optional.AssignPtrString(changed, &config.DefaultSquashMergeMessage, opts.SquashMergeCommitMessage) + if opts.SquashMergeCommitMessage != nil && !config.DefaultSquashMergeMessage.IsValid() { + return util.NewInvalidArgumentErrorf("invalid squash_merge_commit_message: %s", config.DefaultSquashMergeMessage) + } // only validate update-style fields when the caller is actually changing one of them, // so unrelated PATCH calls don't reject historical configs. if opts.AllowMergeUpdate != nil || opts.AllowRebaseUpdate != nil || opts.DefaultUpdateStyle != nil { diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 9b44abd5685..51ac3191e45 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -680,6 +680,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) { DefaultUpdateStyle: defaultUpdateStyle, DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), + DefaultSquashMergeMessage: util.IfZero(repo_model.SquashMergeMessage(form.PullsDefaultSquashMergeMessage), repo_model.DefaultPullRequestsConfig().DefaultSquashMergeMessage), DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, DefaultTargetBranch: strings.TrimSpace(form.DefaultTargetBranch), } @@ -687,6 +688,10 @@ func handleSettingsPostAdvanced(ctx *context.Context) { ctx.JSONErrorAuto(err) return } + if !prConfig.DefaultSquashMergeMessage.IsValid() { + ctx.JSONErrorAuto(util.NewInvalidArgumentErrorf("invalid default squash merge message: %s", prConfig.DefaultSquashMergeMessage)) + return + } units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, prConfig)) } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) diff --git a/services/convert/repository.go b/services/convert/repository.go index 269399f6279..49c853e1cb6 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -105,6 +105,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR defaultDeleteBranchAfterMerge := false defaultMergeStyle := repo_model.MergeStyleMerge defaultUpdateStyle := repo_model.UpdateStyleMerge + defaultSquashMergeMessage := repo_model.DefaultPullRequestsConfig().DefaultSquashMergeMessage defaultAllowMaintainerEdit := false defaultTargetBranch := "" if unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests); err == nil { @@ -123,6 +124,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge defaultMergeStyle = config.DefaultMergeStyle defaultUpdateStyle = config.DefaultUpdateStyle + defaultSquashMergeMessage = config.DefaultSquashMergeMessage defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit defaultTargetBranch = config.DefaultTargetBranch } @@ -251,6 +253,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR DefaultDeleteBranchAfterMerge: defaultDeleteBranchAfterMerge, DefaultMergeStyle: string(defaultMergeStyle), DefaultUpdateStyle: string(defaultUpdateStyle), + SquashMergeCommitMessage: string(defaultSquashMergeMessage), DefaultAllowMaintainerEdit: defaultAllowMaintainerEdit, DefaultTargetBranch: defaultTargetBranch, AvatarURL: repo.AvatarLink(ctx), diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 241ed59f4e4..f98cfc89c99 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -133,6 +133,7 @@ type RepoSettingForm struct { PullsAllowMergeUpdate bool PullsAllowRebaseUpdate bool PullsDefaultUpdateStyle string + PullsDefaultSquashMergeMessage string DefaultDeleteBranchAfterMerge bool DefaultAllowMaintainerEdit bool DefaultTargetBranch string @@ -424,20 +425,21 @@ type MergePullRequestForm struct { ForceMerge bool `json:"force_merge,omitempty"` MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed,omitempty"` DeleteBranchAfterMerge *bool `json:"delete_branch_after_merge,omitempty"` + HasMergeMessageField bool `form:"-" json:"-"` } func (f *MergePullRequestForm) UnmarshalJSON(b []byte) error { // This is for backward compatibility, to support both field names like "do" and "Do", // because old code doesn't have "json" tag for these fields type aux struct { - Do1 string `json:"do"` - Do2 string `json:"Do"` - MergeTitleField1 string `json:"merge_title_field"` - MergeTitleField2 string `json:"MergeTitleField"` - MergeMessageField1 string `json:"merge_message_field"` - MergeMessageField2 string `json:"MergeMessageField"` - MergeCommitID1 string `json:"merge_commit_id"` - MergeCommitID2 string `json:"MergeCommitID"` + Do1 string `json:"do"` + Do2 string `json:"Do"` + MergeTitleField1 string `json:"merge_title_field"` + MergeTitleField2 string `json:"MergeTitleField"` + MergeMessageField1 *string `json:"merge_message_field"` + MergeMessageField2 *string `json:"MergeMessageField"` + MergeCommitID1 string `json:"merge_commit_id"` + MergeCommitID2 string `json:"MergeCommitID"` HeadCommitID string `json:"head_commit_id"` ForceMerge bool `json:"force_merge"` @@ -450,7 +452,10 @@ func (f *MergePullRequestForm) UnmarshalJSON(b []byte) error { } f.Do = util.IfZero(a.Do1, a.Do2) f.MergeTitleField = util.IfZero(a.MergeTitleField1, a.MergeTitleField2) - f.MergeMessageField = util.IfZero(a.MergeMessageField1, a.MergeMessageField2) + f.MergeMessageField, f.HasMergeMessageField = "", false + if mergeMessageField := util.IfZero(a.MergeMessageField1, a.MergeMessageField2); mergeMessageField != nil { + f.MergeMessageField, f.HasMergeMessageField = *mergeMessageField, true + } f.MergeCommitID = util.IfZero(a.MergeCommitID1, a.MergeCommitID2) f.HeadCommitID = a.HeadCommitID f.ForceMerge = a.ForceMerge diff --git a/services/forms/repo_form_test.go b/services/forms/repo_form_test.go index 78cdd471506..bcb2bb7827b 100644 --- a/services/forms/repo_form_test.go +++ b/services/forms/repo_form_test.go @@ -51,6 +51,7 @@ func TestMergePullRequestForm(t *testing.T) { ForceMerge: true, MergeWhenChecksSucceed: true, DeleteBranchAfterMerge: new(true), + HasMergeMessageField: true, } t.Run("NewFields", func(t *testing.T) { @@ -84,4 +85,18 @@ func TestMergePullRequestForm(t *testing.T) { require.NoError(t, json.Unmarshal([]byte(input), &m)) assert.Equal(t, expected, m) }) + + t.Run("OmittedMergeMessageFieldClearsPreviousMessage", func(t *testing.T) { + form := &MergePullRequestForm{MergeMessageField: "previous message", HasMergeMessageField: true} + require.NoError(t, json.Unmarshal([]byte(`{"do": "squash"}`), &form)) + assert.Empty(t, form.MergeMessageField) + assert.False(t, form.HasMergeMessageField) + }) + + t.Run("EmptyMergeMessageField", func(t *testing.T) { + var form MergePullRequestForm + require.NoError(t, json.Unmarshal([]byte(`{"do": "squash", "merge_message_field": ""}`), &form)) + assert.Empty(t, form.MergeMessageField) + assert.True(t, form.HasMergeMessageField) + }) } diff --git a/services/pull/pull.go b/services/pull/pull.go index 40bff08cfc7..7c74b855761 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -781,9 +781,15 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ return "", err } - mergeMessage := strings.TrimSpace(pr.Issue.Content) // use PR's title and description as squash commit message - if setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages { - mergeMessage = formatSquashMergeCommitMessages(limitedCommits) // use PR's commit messages as squash commit message + if err := pr.LoadBaseRepo(ctx); err != nil { + return "", err + } + var mergeMessage string + switch pr.BaseRepo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig().DefaultSquashMergeMessage { + case repo_model.SquashMergeMessageCommitMessages: + mergeMessage = formatSquashMergeCommitMessages(limitedCommits) + case repo_model.SquashMergeMessagePRBody: + mergeMessage = strings.TrimSpace(pr.Issue.Content) } coAuthors := collectSquashMergeCommitCoAuthors(ctx, gitRepo, pr, headCommitRef, mergeBaseRef, limit, limitedCommits) return buildSquashMergeCommitMessages(mergeMessage, coAuthors), nil @@ -797,7 +803,7 @@ func buildSquashMergeCommitMessages(mergeMessage string, coAuthors []string) str msgContent, msgSep, msgTrailer := git.CommitMessageSplitTrailer(mergeMessage) if (msgSep == "" || msgSep == "\n\n") && msgTrailer == "" { msgContent = strings.TrimRightFunc(msgContent, unicode.IsSpace) - msgSep = "\n\n---------\n\n" + msgSep = util.Iif(msgContent == "", "", "\n\n---------\n\n") } var sb strings.Builder sb.WriteString(msgContent) diff --git a/services/pull/pull_test.go b/services/pull/pull_test.go index f2a23133b62..653db28f414 100644 --- a/services/pull/pull_test.go +++ b/services/pull/pull_test.go @@ -99,6 +99,7 @@ func TestBuildSquashMergeCommitMessages(t *testing.T) { coAuthors []string expected string }{ + {"", []string{"the-user"}, "Co-authored-by: the-user\n"}, {"title", nil, "title"}, {"title", []string{"the-user"}, "title\n\n---------\n\nCo-authored-by: the-user\n"}, {"title\n\n", []string{"the-user"}, "title\n\n---------\n\nCo-authored-by: the-user\n"}, diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 1756e247561..0354725cb57 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -605,6 +605,15 @@ +
+ + {{$squashMergeMessage := $prUnit.PullRequestsConfig.DefaultSquashMergeMessage}} + +