Refactor template render (#36438)

This commit is contained in:
wxiaoguang
2026-01-24 13:11:49 +08:00
committed by GitHub
parent 47717d4435
commit 9de659437e
31 changed files with 475 additions and 459 deletions

View File

@@ -17,6 +17,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
@@ -137,9 +138,27 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
return ctx
}
func ContexterInstallPage(data map[string]any) func(next http.Handler) http.Handler {
rnd := templates.PageRenderer()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base := NewBaseContext(resp, req)
ctx := NewWebContext(base, rnd, session.GetContextSession(req))
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
ctx.Data.MergeFrom(reqctx.ContextData{
"Title": ctx.Locale.Tr("install.install"),
"PageIsInstall": true,
"AllLangs": translation.AllLangs(),
})
ctx.Data.MergeFrom(data)
next.ServeHTTP(resp, ctx.Req)
})
}
}
// Contexter initializes a classic context for a request.
func Contexter() func(next http.Handler) http.Handler {
rnd := templates.HTMLRenderer()
rnd := templates.PageRenderer()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base := NewBaseContext(resp, req)

View File

@@ -150,7 +150,7 @@ func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.A
// PackageContexter initializes a package context for a request.
func PackageContexter() func(next http.Handler) http.Handler {
renderer := templates.HTMLRenderer()
renderer := templates.PageRenderer()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base := NewBaseContext(resp, req)

View File

@@ -15,7 +15,6 @@ import (
"mime"
"regexp"
"strings"
"sync/atomic"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
@@ -32,12 +31,10 @@ import (
const mailMaxSubjectRunes = 256 // There's no actual limit for subject in RFC 5322
var loadedTemplates atomic.Pointer[templates.MailTemplates]
var subjectRemoveSpaces = regexp.MustCompile(`[\s]+`)
func LoadedTemplates() *templates.MailTemplates {
return loadedTemplates.Load()
func LoadedTemplates() *templates.MailRender {
return templates.MailRenderer()
}
// SendTestMail sends a test mail

View File

@@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
sender_service "code.gitea.io/gitea/services/mailer/sender"
"code.gitea.io/gitea/services/mailer/token"
@@ -122,9 +123,7 @@ func composeIssueCommentMessages(ctx context.Context, comment *mailComment, lang
var mailSubject bytes.Buffer
if err := LoadedTemplates().SubjectTemplates.ExecuteTemplate(&mailSubject, tplName, mailMeta); err == nil {
subject = sanitizeSubject(mailSubject.String())
if subject == "" {
subject = fallback
}
subject = util.IfZero(subject, fallback)
} else {
log.Error("ExecuteTemplate [%s]: %v", tplName+"/subject", err)
}
@@ -261,14 +260,14 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act
}
template = "repo/" + typeName + "/" + name
ok := LoadedTemplates().BodyTemplates.Lookup(template) != nil
ok := LoadedTemplates().BodyTemplates.HasTemplate(template)
if !ok && typeName != "issue" {
template = "repo/issue/" + name
ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
ok = LoadedTemplates().BodyTemplates.HasTemplate(template)
}
if !ok {
template = "repo/" + typeName + "/default"
ok = LoadedTemplates().BodyTemplates.Lookup(template) != nil
ok = LoadedTemplates().BodyTemplates.HasTemplate(template)
}
if !ok {
template = "repo/issue/default"

View File

@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
sender_service "code.gitea.io/gitea/services/mailer/sender"
"github.com/stretchr/testify/assert"
@@ -19,18 +20,10 @@ import (
func TestMailNewReleaseFiltersUnauthorizedWatchers(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
origMailService := setting.MailService
origDomain := setting.Domain
origAppName := setting.AppName
origAppURL := setting.AppURL
origTemplates := LoadedTemplates()
defer func() {
setting.MailService = origMailService
setting.Domain = origDomain
setting.AppName = origAppName
setting.AppURL = origAppURL
loadedTemplates.Store(origTemplates)
}()
defer test.MockVariableValue(&setting.MailService)()
defer test.MockVariableValue(&setting.Domain)()
defer test.MockVariableValue(&setting.AppName)()
defer test.MockVariableValue(&setting.AppURL)()
setting.MailService = &setting.Mailer{
From: "Gitea",
@@ -39,7 +32,7 @@ func TestMailNewReleaseFiltersUnauthorizedWatchers(t *testing.T) {
setting.Domain = "example.com"
setting.AppName = "Gitea"
setting.AppURL = "https://example.com/"
prepareMailTemplates(string(tplNewReleaseMail), "{{.Subject}}", "<p>{{.Release.TagName}}</p>")
defer mockMailTemplates(string(tplNewReleaseMail), "{{.Subject}}", "<p>{{.Release.TagName}}</p>")()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
require.True(t, repo.IsPrivate)

View File

@@ -96,11 +96,8 @@ func prepareMailerBase64Test(t *testing.T) (doer *user_model.User, repo *repo_mo
return user, repo, issue, att1, att2
}
func prepareMailTemplates(name, subjectTmpl, bodyTmpl string) {
loadedTemplates.Store(&templates.MailTemplates{
SubjectTemplates: texttmpl.Must(texttmpl.New(name).Parse(subjectTmpl)),
BodyTemplates: template.Must(template.New(name).Parse(bodyTmpl)),
})
func mockMailTemplates(name, subjectTmpl, bodyTmpl string) func() {
return templates.MailRenderer().MockTemplate(name, subjectTmpl, bodyTmpl)
}
func TestComposeIssueComment(t *testing.T) {
@@ -112,10 +109,8 @@ func TestComposeIssueComment(t *testing.T) {
},
})
setting.IncomingEmail.Enabled = true
defer func() { setting.IncomingEmail.Enabled = false }()
prepareMailTemplates("repo/issue/comment", subjectTpl, bodyTpl)
defer test.MockVariableValue(&setting.IncomingEmail.Enabled, true)()
defer mockMailTemplates("repo/issue/comment", subjectTpl, bodyTpl)()
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
@@ -160,7 +155,7 @@ func TestComposeIssueComment(t *testing.T) {
func TestMailMentionsComment(t *testing.T) {
doer, _, issue, comment := prepareMailerTest(t)
comment.Poster = doer
prepareMailTemplates("repo/issue/comment", subjectTpl, bodyTpl)
defer mockMailTemplates("repo/issue/comment", subjectTpl, bodyTpl)()
mails := 0
defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) {
@@ -175,7 +170,7 @@ func TestMailMentionsComment(t *testing.T) {
func TestComposeIssueMessage(t *testing.T) {
doer, _, issue, _ := prepareMailerTest(t)
prepareMailTemplates("repo/issue/new", subjectTpl, bodyTpl)
defer mockMailTemplates("repo/issue/new", subjectTpl, bodyTpl)()
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}, {Name: "Test2", Email: "test2@gitea.com"}}
msgs, err := composeIssueCommentMessages(t.Context(), &mailComment{
Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
@@ -204,14 +199,10 @@ func TestTemplateSelection(t *testing.T) {
doer, repo, issue, comment := prepareMailerTest(t)
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
prepareMailTemplates("repo/issue/default", "repo/issue/default/subject", "repo/issue/default/body")
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("repo/issue/new").Parse("repo/issue/new/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("repo/pull/comment").Parse("repo/pull/comment/subject"))
texttmpl.Must(LoadedTemplates().SubjectTemplates.New("repo/issue/close").Parse("")) // Must default to a fallback subject
template.Must(LoadedTemplates().BodyTemplates.New("repo/issue/new").Parse("repo/issue/new/body"))
template.Must(LoadedTemplates().BodyTemplates.New("repo/pull/comment").Parse("repo/pull/comment/body"))
template.Must(LoadedTemplates().BodyTemplates.New("repo/issue/close").Parse("repo/issue/close/body"))
defer mockMailTemplates("repo/issue/default", "repo/issue/default/subject", "repo/issue/default/body")()
defer mockMailTemplates("repo/issue/new", "repo/issue/new/subject", "repo/issue/new/body")()
defer mockMailTemplates("repo/pull/comment", "repo/pull/comment/subject", "repo/pull/comment/body")()
defer mockMailTemplates("repo/issue/close", "", "repo/issue/close/body")() // Must default to a fallback subject
expect := func(t *testing.T, msg *sender_service.Message, expSubject, expBody string) {
subject := msg.ToMessage().GetGenHeader("Subject")
@@ -256,7 +247,7 @@ func TestTemplateServices(t *testing.T) {
expect := func(t *testing.T, issue *issues_model.Issue, comment *issues_model.Comment, doer *user_model.User,
actionType activities_model.ActionType, fromMention bool, tplSubject, tplBody, expSubject, expBody string,
) {
prepareMailTemplates("repo/issue/default", tplSubject, tplBody)
defer mockMailTemplates("repo/issue/default", tplSubject, tplBody)()
recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
msg := testComposeIssueCommentMessage(t, &mailComment{
Issue: issue, Doer: doer, ActionType: actionType,
@@ -523,7 +514,7 @@ func TestEmbedBase64Images(t *testing.T) {
att2ImgBase64 := fmt.Sprintf(`<img src="%s"/>`, att2Base64)
t.Run("ComposeMessage", func(t *testing.T) {
prepareMailTemplates("repo/issue/new", subjectTpl, bodyTpl)
defer mockMailTemplates("repo/issue/new", subjectTpl, bodyTpl)()
issue.Content = fmt.Sprintf(`MSG-BEFORE <image src="attachments/%s"> MSG-AFTER`, att1.UUID)
require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "content"))

View File

@@ -43,7 +43,7 @@ func NewContext(ctx context.Context) {
sender = &sender_service.SMTPSender{}
}
templates.LoadMailTemplates(ctx, &loadedTemplates)
_ = templates.MailRenderer()
mailQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "mail", func(items ...*sender_service.Message) []*sender_service.Message {
for _, msg := range items {

View File

@@ -18,7 +18,7 @@ import (
func TestRenderHelperCodePreview(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()})
htm, err := renderRepoFileCodePreview(ctx, markup.RenderCodePreviewOptions{
FullURL: "http://full",
OwnerName: "user2",
@@ -46,7 +46,7 @@ func TestRenderHelperCodePreview(t *testing.T) {
</div>
`, string(htm))
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()})
htm, err = renderRepoFileCodePreview(ctx, markup.RenderCodePreviewOptions{
FullURL: "http://full",
OwnerName: "user2",
@@ -70,7 +70,7 @@ func TestRenderHelperCodePreview(t *testing.T) {
</div>
`, string(htm))
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()})
_, err = renderRepoFileCodePreview(ctx, markup.RenderCodePreviewOptions{
FullURL: "http://full",
OwnerName: "user15",

View File

@@ -19,7 +19,7 @@ import (
func TestRenderHelperIssueIconTitle(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()})
ctx.Repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
htm, err := renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
LinkHref: "/link",
@@ -28,7 +28,7 @@ func TestRenderHelperIssueIconTitle(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, `<a href="/link"><span>octicon-issue-opened(16/text green)</span> issue1 (#1)</a>`, string(htm))
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()})
htm, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
OwnerName: "user2",
RepoName: "repo1",
@@ -38,7 +38,7 @@ func TestRenderHelperIssueIconTitle(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, `<a href="/link"><span>octicon-issue-opened(16/text green)</span> issue1 (user2/repo1#1)</a>`, string(htm))
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.PageRenderer()})
_, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
OwnerName: "user2",
RepoName: "repo2",