mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-20 08:34:12 +02:00
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:
1 parent
458eda0f1d
commit
ba4f8fa246
37 files changed
+273
-156
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ ENABLED = false
|
||||
EXPIRATION = 123
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
applyDeprecatedKeys(cfg)
|
||||
loadGitFrom(cfg)
|
||||
|
||||
assert.Equal(t, "false", GitConfig.GetOption("core.logAllRefUpdates"))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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: ")
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user