fix: avoid markup render panic (#38698)

fix #38697

The root problem is that "commitChecker.repo" can be nil when the render
is called without a real repo.
This commit is contained in:
wxiaoguang
2026-07-29 20:26:21 +00:00
committed by GitHub
parent 43390e802c
commit f34073b887
5 changed files with 38 additions and 50 deletions
+10 -7
View File
@@ -13,35 +13,38 @@ import (
) )
type commitChecker struct { type commitChecker struct {
ctx context.Context ctx context.Context
commitCache map[string]bool commitCache map[string]bool
repo *repo_model.Repository repoOptional *repo_model.Repository
gitRepo *git.Repository gitRepo *git.Repository
gitRepoCloser io.Closer gitRepoCloser io.Closer
} }
func newCommitChecker(ctx context.Context, repo *repo_model.Repository) *commitChecker { func newCommitChecker(ctx context.Context, repo *repo_model.Repository) *commitChecker {
return &commitChecker{ctx: ctx, commitCache: make(map[string]bool), repo: repo} return &commitChecker{ctx: ctx, commitCache: make(map[string]bool), repoOptional: repo}
} }
func (c *commitChecker) Close() error { func (c *commitChecker) Close() error {
if c != nil && c.gitRepoCloser != nil { if c.gitRepoCloser != nil {
return c.gitRepoCloser.Close() return c.gitRepoCloser.Close()
} }
return nil return nil
} }
func (c *commitChecker) IsCommitIDExisting(commitID string) bool { func (c *commitChecker) IsCommitIDExisting(commitID string) bool {
if c.repoOptional == nil {
return false
}
exist, inCache := c.commitCache[commitID] exist, inCache := c.commitCache[commitID]
if inCache { if inCache {
return exist return exist
} }
if c.gitRepo == nil { if c.gitRepo == nil {
r, closer, err := git.RepositoryFromContextOrOpen(c.ctx, c.repo) r, closer, err := git.RepositoryFromContextOrOpen(c.ctx, c.repoOptional)
if err != nil { if err != nil {
log.Error("Unable to open repository: %s, error: %v", c.repo.FullName(), err) log.Error("Unable to open repository: %s, error: %v", c.repoOptional.FullName(), err)
return false return false
} }
c.gitRepo, c.gitRepoCloser = r, closer c.gitRepo, c.gitRepoCloser = r, closer
+1 -1
View File
@@ -51,10 +51,10 @@ func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repositor
helper := &RepoComment{opts: util.OptionalArg(opts)} helper := &RepoComment{opts: util.OptionalArg(opts)}
rctx := markup.NewRenderContext(ctx) rctx := markup.NewRenderContext(ctx)
helper.ctx = rctx helper.ctx = rctx
helper.commitChecker = newCommitChecker(ctx, repo)
var metas map[string]string var metas map[string]string
if repo != nil { if repo != nil {
helper.repoLink = repo.Link() helper.repoLink = repo.Link()
helper.commitChecker = newCommitChecker(ctx, repo)
metas = repo.ComposeCommentMetas(ctx) metas = repo.ComposeCommentMetas(ctx)
} else { } else {
// repo can be nil when rendering a commit message in user's dashboard feedback whose repository has been deleted // repo can be nil when rendering a commit message in user's dashboard feedback whose repository has been deleted
+1 -1
View File
@@ -58,9 +58,9 @@ func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository,
helper := &RepoFile{opts: util.OptionalArg(opts)} helper := &RepoFile{opts: util.OptionalArg(opts)}
rctx := markup.NewRenderContext(ctx) rctx := markup.NewRenderContext(ctx)
helper.ctx = rctx helper.ctx = rctx
helper.commitChecker = newCommitChecker(ctx, repo)
if repo != nil { if repo != nil {
helper.repoLink = repo.Link() helper.repoLink = repo.Link()
helper.commitChecker = newCommitChecker(ctx, repo)
rctx = rctx.WithMetas(repo.ComposeRepoFileMetas(ctx)) rctx = rctx.WithMetas(repo.ComposeRepoFileMetas(ctx))
} else { } else {
// this is almost dead code, only to pass the incorrect tests // this is almost dead code, only to pass the incorrect tests
+1 -1
View File
@@ -57,9 +57,9 @@ type RepoWikiOptions struct {
func NewRenderContextRepoWiki(ctx context.Context, repo *repo_model.Repository, opts ...RepoWikiOptions) *markup.RenderContext { func NewRenderContextRepoWiki(ctx context.Context, repo *repo_model.Repository, opts ...RepoWikiOptions) *markup.RenderContext {
helper := &RepoWiki{opts: util.OptionalArg(opts)} helper := &RepoWiki{opts: util.OptionalArg(opts)}
rctx := markup.NewRenderContext(ctx).WithMarkupType(markdown.MarkupName) rctx := markup.NewRenderContext(ctx).WithMarkupType(markdown.MarkupName)
helper.commitChecker = newCommitChecker(ctx, repo)
if repo != nil { if repo != nil {
helper.repoLink = repo.Link() helper.repoLink = repo.Link()
helper.commitChecker = newCommitChecker(ctx, repo)
rctx = rctx.WithMetas(repo.ComposeWikiMetas(ctx)) rctx = rctx.WithMetas(repo.ComposeWikiMetas(ctx))
} else { } else {
// this is almost dead code, only to pass the incorrect tests // this is almost dead code, only to pass the incorrect tests
+25 -40
View File
@@ -177,49 +177,34 @@ Here are some links to the most important topics. You can find the full list of
testRenderMarkup(t, "unknown", false, "", "## Test", "unsupported render mode: unknown\n", http.StatusUnprocessableEntity) testRenderMarkup(t, "unknown", false, "", "## Test", "unsupported render mode: unknown\n", http.StatusUnprocessableEntity)
} }
var simpleCases = []string{
// Guard wiki sidebar: special syntax
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
// rendered
`<p>[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]</p>
`,
// special syntax
`[[Name|Link]]`,
// rendered
`<p>[[Name|Link]]</p>
`,
// empty
``,
// rendered
``,
}
func TestAPI_RenderSimple(t *testing.T) { func TestAPI_RenderSimple(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
options := api.MarkdownOption{
Mode: "markdown",
Text: "",
Context: "/user2/repo1",
}
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
for i := 0; i < len(simpleCases); i += 2 {
options.Text = simpleCases[i]
web.SetForm(ctx, &options)
Markdown(ctx)
assert.Equal(t, simpleCases[i+1], resp.Body.String())
resp.Body.Reset()
}
}
func TestAPI_RenderRaw(t *testing.T) { testCases := []struct {
setting.AppURL = AppURL in, out string
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true mode string
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown") }{
for i := 0; i < len(simpleCases); i += 2 { {in: "", out: ""},
ctx.Req.Body = io.NopCloser(strings.NewReader(simpleCases[i])) {in: "[[special-syntax]]", out: "<p>[[special-syntax]]</p>\n", mode: "markdown"},
MarkdownRaw(ctx) {in: "[[special|syntax]]", out: "<p>[[special|syntax]]</p>\n", mode: "markdown"},
assert.Equal(t, simpleCases[i+1], resp.Body.String()) {in: "01234567890123456789", out: "<p>01234567890123456789</p>\n", mode: "gfm"}, // commit-like content should not crash the render
resp.Body.Reset()
} }
t.Run("markdown", func(t *testing.T) {
for _, c := range testCases {
options := api.MarkdownOption{Mode: c.mode, Text: c.in, Context: "/user2/repo1"}
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
web.SetForm(ctx, &options)
Markdown(ctx)
assert.Equal(t, c.out, resp.Body.String())
}
})
t.Run("markdown-raw", func(t *testing.T) {
for _, c := range testCases {
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
ctx.Req.Body = io.NopCloser(strings.NewReader(c.in))
MarkdownRaw(ctx)
assert.Equal(t, c.out, resp.Body.String())
}
})
} }