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.

<img width="1891" height="571" alt="grafik"
src="https://github.com/user-attachments/assets/0fdcde13-1f11-4156-b598-d80eb0d04cbe"
/>

---------

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
bircniandsilverwind authored and GitHub committed 2026-10-19 09:15:41 +00:00
1 parent 458eda0f1d
commit ba4f8fa246
37 files changed
+273 -156

No files matched your search

+3 -2
View File
@@ -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
+13
View File
@@ -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)
+2
View File
@@ -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
}
+64
View File
@@ -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)
}
}
}
+2 -2
View File
@@ -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)
}
}
-10
View File
@@ -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())
}
+1
View File
@@ -56,6 +56,7 @@ ENABLED = false
EXPIRATION = 123
`)
assert.NoError(t, err)
applyDeprecatedKeys(cfg)
loadGitFrom(cfg)
assert.Equal(t, "false", GitConfig.GetOption("core.logAllRefUpdates"))
-12
View File
@@ -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 {
+2
View File
@@ -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)
+6 -6
View File
@@ -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
}
+2 -48
View File
@@ -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")
}
+3 -3
View File
@@ -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
-9
View File
@@ -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)
}
-6
View File
@@ -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)
}
+8 -3
View File
@@ -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")
}
-4
View File
@@ -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)
}
+4 -28
View File
@@ -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()
+1 -1
View File
@@ -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")
+1
View File
@@ -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)
+3 -3
View File
@@ -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":
+1 -1
View File
@@ -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)
+3
View File
@@ -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.
+4
View File
@@ -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",
+12 -3
View File
@@ -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
+4
View File
@@ -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 {
+5
View File
@@ -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)
+3
View File
@@ -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),
+14 -9
View File
@@ -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
+15
View File
@@ -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)
})
}
+10 -4
View File
@@ -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)
+1
View File
@@ -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"},
+9
View File
@@ -605,6 +605,15 @@
</div>
</div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pulls.default_squash_merge_message"}}</label>
{{$squashMergeMessage := $prUnit.PullRequestsConfig.DefaultSquashMergeMessage}}
<select class="ui selection dropdown" name="pulls_default_squash_merge_message">
<option value="BLANK" {{if eq $squashMergeMessage "BLANK"}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.pulls.default_squash_merge_message.title"}}</option>
<option value="COMMIT_MESSAGES" {{if eq $squashMergeMessage "COMMIT_MESSAGES"}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.pulls.default_squash_merge_message.title_and_commit_details"}}</option>
<option value="PR_BODY" {{if eq $squashMergeMessage "PR_BODY"}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.pulls.default_squash_merge_message.title_and_description"}}</option>
</select>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pulls.default_target_branch"}}</label>
<div class="ui search selection dropdown">
+9
View File
@@ -6167,6 +6167,11 @@
"type": "string",
"x-go-name": "ProjectsMode"
},
"squash_merge_commit_message": {
"description": "set the default squash merge commit message body: \"PR_BODY\", \"COMMIT_MESSAGES\", or \"BLANK\".",
"type": "string",
"x-go-name": "SquashMergeCommitMessage"
},
"template": {
"description": "either `true` to make this repository a template or `false` to make it a normal repository",
"type": "boolean",
@@ -9876,6 +9881,10 @@
"type": "integer",
"x-go-name": "Size"
},
"squash_merge_commit_message": {
"type": "string",
"x-go-name": "SquashMergeCommitMessage"
},
"ssh_url": {
"format": "uri",
"type": "string",
+9
View File
@@ -29273,6 +29273,11 @@
"type": "string",
"x-go-name": "ProjectsMode"
},
"squash_merge_commit_message": {
"description": "set the default squash merge commit message body: \"PR_BODY\", \"COMMIT_MESSAGES\", or \"BLANK\".",
"type": "string",
"x-go-name": "SquashMergeCommitMessage"
},
"template": {
"description": "either `true` to make this repository a template or `false` to make it a normal repository",
"type": "boolean",
@@ -32962,6 +32967,10 @@
"format": "int64",
"x-go-name": "Size"
},
"squash_merge_commit_message": {
"type": "string",
"x-go-name": "SquashMergeCommitMessage"
},
"ssh_url": {
"type": "string",
"x-go-name": "SSHURL"
+11
View File
@@ -283,6 +283,17 @@ func TestAPIMergePull(t *testing.T) {
performMerge(t, prDTO.Index, map[string]any{"do": "merge", "delete_branch_after_merge": true})
checkBranchExists(t, newBranch, http.StatusNotFound)
})
t.Run("SquashMergePreservesFormMessage", func(t *testing.T) {
req := NewRequestWithValues(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner.Name, repo.Name, createTestBranchPR(t, "squash-form-message").Index), map[string]string{
"do": "squash",
"merge_message_field": "custom squash message",
}).AddTokenAuth(apiCtx.Token)
MakeRequest(t, req, http.StatusOK)
req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/git/commits/%s", owner.Name, repo.Name, repo.DefaultBranch).AddTokenAuth(apiCtx.Token)
assert.Contains(t, DecodeJSON(t, MakeRequest(t, req, http.StatusOK), &api.Commit{}).RepoCommit.Message, "\n\ncustom squash message")
})
})
}
+11 -1
View File
@@ -506,7 +506,7 @@ func TestAPIRepoEdit(t *testing.T) {
})
}
func TestAPIRepoEditPullUpdateSettingsValidation(t *testing.T) {
func TestAPIRepoEditPullSettings(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
@@ -532,4 +532,14 @@ func TestAPIRepoEditPullUpdateSettingsValidation(t *testing.T) {
DefaultUpdateStyle: &defaultUpdateStyle,
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusBadRequest)
req = NewRequestWithJSON(t, "PATCH", repoURL, &api.EditRepoOption{
SquashMergeCommitMessage: new("invalid"),
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusBadRequest)
req = NewRequestWithJSON(t, "PATCH", repoURL, &api.EditRepoOption{
SquashMergeCommitMessage: new(string(repo_model.SquashMergeMessageBlank)),
}).AddTokenAuth(token)
assert.Equal(t, string(repo_model.SquashMergeMessageBlank), DecodeJSON(t, MakeRequest(t, req, http.StatusOK), &api.Repository{}).SquashMergeCommitMessage)
}
+37 -1
View File
@@ -24,6 +24,7 @@ import (
"gitea.dev/models/perm"
pull_model "gitea.dev/models/pull"
repo_model "gitea.dev/models/repo"
unit_model "gitea.dev/models/unit"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/models/webhook"
@@ -1214,7 +1215,7 @@ func TestPullSquashMessage(t *testing.T) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user2Session := loginUser(t, user2.Name)
defer test.MockVariableValue(&setting.Repository.PullRequest.PopulateSquashCommentWithCommitMessages, true)()
defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultSquashMergeMessage, "COMMIT_MESSAGES")()
defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultMergeMessageSize, 80)()
repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{
@@ -1344,5 +1345,40 @@ Co-authored-by: user4 <user4@example.com>
assert.Equal(t, tc.expectedMessage, squashMergeCommitMessage)
})
}
t.Run("Repository default squash merge message", func(t *testing.T) {
pullRequest := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, Index: 1})
require.NoError(t, pullRequest.LoadIssue(t.Context()))
pullRequest.Issue.Content = "PR description"
pullRequest.BaseRepo = repo
prUnit, err := repo.GetUnit(t.Context(), unit_model.TypePullRequests)
require.NoError(t, err)
for style, expected := range map[repo_model.SquashMergeMessage]string{
repo_model.SquashMergeMessageBlank: "",
repo_model.SquashMergeMessagePRBody: "PR description",
} {
prUnit.PullRequestsConfig().DefaultSquashMergeMessage = style
msg, err := pull_service.GetSquashMergeCommitMessages(t.Context(), pullRequest)
require.NoError(t, err)
assert.Equal(t, expected, msg, style)
}
})
t.Run("API squash merge without message uses repository default", func(t *testing.T) {
defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultSquashMergeMessage, "PR_BODY")()
token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository)
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/1/merge", user2.Name, repo.Name), map[string]string{"do": "squash"}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusOK)
pullRequest := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: repo.ID, Index: 1})
gitRepo, err := git.OpenRepository(t.Context(), repo)
require.NoError(t, err)
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(t.Context(), pullRequest.MergedCommitID)
require.NoError(t, err)
assert.Contains(t, commit.CommitMessage.MessageRaw, "Pull for test-branch-0 (#1)\n\n* commit msg 1\n\n* commit msg 2\n")
assert.Contains(t, commit.CommitMessage.MessageRaw, "\nReviewed-on: ")
})
})
}