enhance: fall back to DEFAULT_TEMPLATE.md when style-specific template is missing (#38803)

Closes #38801

Introduce `DEFAULT_TEMPLATE.md` as the default message for all merge
styles.

https://gitea.com/gitea/docs/pulls/491

By the way, fix incorrect os.Expand usage for merge message & repo
template

---------

Co-authored-by: waterWang <waterWang@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
water
2026-08-07 22:11:03 +08:00
committed by GitHub
parent a34cc4cac4
commit 2657756cac
9 changed files with 152 additions and 28 deletions
+21
View File
@@ -7,11 +7,18 @@ import (
"bytes"
"context"
"fmt"
"os"
"gitea.dev/modules/git/gitcmd"
"gitea.dev/modules/git/gitrepo"
"gitea.dev/modules/util"
)
type FastImportInit struct {
Bare bool
ObjectFormat string
}
type FastImportFile struct {
Mode EntryMode
Path string
@@ -24,6 +31,20 @@ type FastImportCommit struct {
Files []FastImportFile
}
// ForceFastImportWithInit is for mainly for testing purpose
func ForceFastImportWithInit(ctx context.Context, repoLocalPath string, commits []FastImportCommit, initOpts ...FastImportInit) (RepositoryFacade, error) {
repo := gitrepo.RepositoryUnmanaged(repoLocalPath)
initOpt := util.OptionalArg(initOpts, FastImportInit{Bare: true})
if exist, _ := IsRepositoryExist(ctx, repo); !exist {
_ = os.MkdirAll(repoLocalPath, 0o755)
err := InitRepositoryLocal(ctx, repoLocalPath, initOpt.Bare, util.IfZero(initOpt.ObjectFormat, "sha1"))
if err != nil {
return nil, err
}
}
return repo, ForceFastImport(ctx, repo, commits)
}
// ForceFastImport is for mainly for testing purpose
func ForceFastImport(ctx context.Context, repo RepositoryFacade, commits []FastImportCommit) error {
var buf bytes.Buffer
+1 -1
View File
@@ -149,7 +149,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
if hasExtTrackFormat && !ref.IsPull {
ctx.RenderOptions.Metas["index"] = ref.Issue
res, err := vars.Expand(ctx.RenderOptions.Metas["format"], ctx.RenderOptions.Metas)
res, err := vars.ExpandCurlyBrace(ctx.RenderOptions.Metas["format"], ctx.RenderOptions.Metas)
if err != nil {
// here we could just log the error and continue the rendering
log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err)
+29 -3
View File
@@ -5,14 +5,17 @@ package vars
import (
"fmt"
"regexp"
"strings"
"sync"
"unicode"
"unicode/utf8"
)
// Expand replaces all variables like {var} by `vars` map, it always returns the expanded string regardless of errors
// if error occurs, the error part doesn't change and is returned as it is.
func Expand(template string, vars map[string]string) (string, error) {
// ExpandCurlyBrace replaces all variables like {var} by `vars` map,
// it always returns the expanded string regardless of errors.
// if error occurs (wrong syntax, missing variable), the error part doesn't change and is returned as it is.
func ExpandCurlyBrace(template string, vars map[string]string) (string, error) {
// in the future, if necessary, we can introduce some escape-char,
// for example: it will use `#' as a reversed char, templates will use `{#{}` to do escape and output char '{'.
var buf strings.Builder
@@ -71,3 +74,26 @@ func Expand(template string, vars map[string]string) (string, error) {
return buf.String(), err
}
var globalVars = sync.OnceValue(func() (ret struct {
regexpShellLike *regexp.Regexp
},
) {
ret.regexpShellLike = regexp.MustCompile(`(\$\{[a-zA-Z_]\w*\}|\$[a-zA-Z_]\w*)`)
return ret
})
// ExpandShellLike works like os.Expand, the difference is that this function keeps the non-existing keys
func ExpandShellLike(template string, vars map[string]string) string {
re := globalVars().regexpShellLike
return re.ReplaceAllStringFunc(template, func(s string) string {
key := s[1:]
if strings.HasPrefix(key, "{") && strings.HasSuffix(key, "}") {
key = key[1 : len(key)-1]
}
if val, ok := vars[key]; ok {
return val
}
return s
})
}
+23 -7
View File
@@ -9,8 +9,8 @@ import (
"github.com/stretchr/testify/assert"
)
func TestExpandVars(t *testing.T) {
kases := []struct {
func TestExpandCurlyBrace(t *testing.T) {
cases := []struct {
tmpl string
data map[string]string
out string
@@ -57,11 +57,11 @@ func TestExpandVars(t *testing.T) {
},
}
for _, kase := range kases {
t.Run(kase.tmpl, func(t *testing.T) {
res, err := Expand(kase.tmpl, kase.data)
assert.Equal(t, kase.out, res)
if kase.error {
for _, c := range cases {
t.Run(c.tmpl, func(t *testing.T) {
res, err := ExpandCurlyBrace(c.tmpl, c.data)
assert.Equal(t, c.out, res)
if c.error {
assert.Error(t, err)
} else {
assert.NoError(t, err)
@@ -69,3 +69,19 @@ func TestExpandVars(t *testing.T) {
})
}
}
func TestExpandShellLike(t *testing.T) {
cases := []struct {
tmpl string
data map[string]string
out string
}{
{tmpl: "$key ${key} $other ${other}", data: map[string]string{"key": "val"}, out: "val val $other ${other}"},
{tmpl: "$ key ${key }", data: map[string]string{"key": "val"}, out: "$ key ${key }"},
}
for _, c := range cases {
out := ExpandShellLike(c.tmpl, c.data)
assert.Equal(t, c.out, out, "tmpl: %s", c.tmpl)
}
}