Skip to content

Commit dd8bc1d

Browse files
authored
Refactor issue template parsing and fix API endpoint (#29069) (#29140)
Backport #29069 The old code `GetTemplatesFromDefaultBranch(...) ([]*api.IssueTemplate, map[string]error)` doesn't really follow Golang's habits, then the second returned value might be misused. For example, the API function `GetIssueTemplates` incorrectly checked the second returned value and always responds 500 error. This PR refactors GetTemplatesFromDefaultBranch to ParseTemplatesFromDefaultBranch and clarifies its behavior, and fixes the API endpoint bug, and adds some tests. And by the way, add proper prefix `X-` for the header generated in `checkDeprecatedAuthMethods`, because non-standard HTTP headers should have `X-` prefix, and it is also consistent with the new code in `GetIssueTemplates`
1 parent 0ac3186 commit dd8bc1d

File tree

6 files changed

+88
-33
lines changed

6 files changed

+88
-33
lines changed

routers/api/v1/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ func individualPermsChecker(ctx *context.APIContext) {
810810
// check for and warn against deprecated authentication options
811811
func checkDeprecatedAuthMethods(ctx *context.APIContext) {
812812
if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
813-
ctx.Resp.Header().Set("Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.")
813+
ctx.Resp.Header().Set("X-Gitea-Warning", "token and access_token API authentication is deprecated and will be removed in gitea 1.23. Please use AuthorizationHeaderToken instead. Existing queries will continue to work but without authorization.")
814814
}
815815
}
816816

routers/api/v1/repo/repo.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net/http"
1010
"slices"
11+
"strconv"
1112
"strings"
1213
"time"
1314

@@ -1159,12 +1160,11 @@ func GetIssueTemplates(ctx *context.APIContext) {
11591160
// "$ref": "#/responses/IssueTemplates"
11601161
// "404":
11611162
// "$ref": "#/responses/notFound"
1162-
ret, err := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
1163-
if err != nil {
1164-
ctx.Error(http.StatusInternalServerError, "GetTemplatesFromDefaultBranch", err)
1165-
return
1163+
ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
1164+
if cnt := len(ret.TemplateErrors); cnt != 0 {
1165+
ctx.Resp.Header().Add("X-Gitea-Warning", "error occurs when parsing issue template: count="+strconv.Itoa(cnt))
11661166
}
1167-
ctx.JSON(http.StatusOK, ret)
1167+
ctx.JSON(http.StatusOK, ret.IssueTemplates)
11681168
}
11691169

11701170
// GetIssueConfig returns the issue config for a repo

routers/web/repo/issue.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -963,19 +963,17 @@ func NewIssue(ctx *context.Context) {
963963
}
964964
ctx.Data["Tags"] = tags
965965

966-
_, templateErrs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
966+
ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
967967
templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
968-
if len(errs) > 0 {
969-
for k, v := range errs {
970-
templateErrs[k] = v
971-
}
968+
for k, v := range errs {
969+
ret.TemplateErrors[k] = v
972970
}
973971
if ctx.Written() {
974972
return
975973
}
976974

977-
if len(templateErrs) > 0 {
978-
ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true)
975+
if len(ret.TemplateErrors) > 0 {
976+
ctx.Flash.Warning(renderErrorOfTemplates(ctx, ret.TemplateErrors), true)
979977
}
980978

981979
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypeIssues)
@@ -1018,11 +1016,11 @@ func NewIssueChooseTemplate(ctx *context.Context) {
10181016
ctx.Data["Title"] = ctx.Tr("repo.issues.new")
10191017
ctx.Data["PageIsIssueList"] = true
10201018

1021-
issueTemplates, errs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
1022-
ctx.Data["IssueTemplates"] = issueTemplates
1019+
ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
1020+
ctx.Data["IssueTemplates"] = ret.IssueTemplates
10231021

1024-
if len(errs) > 0 {
1025-
ctx.Flash.Warning(renderErrorOfTemplates(ctx, errs), true)
1022+
if len(ret.TemplateErrors) > 0 {
1023+
ctx.Flash.Warning(renderErrorOfTemplates(ctx, ret.TemplateErrors), true)
10261024
}
10271025

10281026
if !issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo) {

routers/web/repo/milestone.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
294294

295295
issues(ctx, milestoneID, projectID, util.OptionalBoolNone)
296296

297-
ret, _ := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
298-
ctx.Data["NewIssueChooseTemplate"] = len(ret) > 0
297+
ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
298+
ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0
299299

300300
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false)
301301
ctx.Data["CanWritePulls"] = ctx.Repo.CanWriteIssuesOrPulls(true)

services/issue/template.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,23 @@ func IsTemplateConfig(path string) bool {
109109
return false
110110
}
111111

112-
// GetTemplatesFromDefaultBranch checks for issue templates in the repo's default branch,
113-
// returns valid templates and the errors of invalid template files.
114-
func GetTemplatesFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repository) ([]*api.IssueTemplate, map[string]error) {
115-
var issueTemplates []*api.IssueTemplate
116-
112+
// ParseTemplatesFromDefaultBranch parses the issue templates in the repo's default branch,
113+
// returns valid templates and the errors of invalid template files (the errors map is guaranteed to be non-nil).
114+
func ParseTemplatesFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repository) (ret struct {
115+
IssueTemplates []*api.IssueTemplate
116+
TemplateErrors map[string]error
117+
},
118+
) {
119+
ret.TemplateErrors = map[string]error{}
117120
if repo.IsEmpty {
118-
return issueTemplates, nil
121+
return ret
119122
}
120123

121124
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
122125
if err != nil {
123-
return issueTemplates, nil
126+
return ret
124127
}
125128

126-
invalidFiles := map[string]error{}
127129
for _, dirName := range templateDirCandidates {
128130
tree, err := commit.SubTree(dirName)
129131
if err != nil {
@@ -133,24 +135,24 @@ func GetTemplatesFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repositor
133135
entries, err := tree.ListEntries()
134136
if err != nil {
135137
log.Debug("list entries in %s: %v", dirName, err)
136-
return issueTemplates, nil
138+
return ret
137139
}
138140
for _, entry := range entries {
139141
if !template.CouldBe(entry.Name()) {
140142
continue
141143
}
142144
fullName := path.Join(dirName, entry.Name())
143145
if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil {
144-
invalidFiles[fullName] = err
146+
ret.TemplateErrors[fullName] = err
145147
} else {
146148
if !strings.HasPrefix(it.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
147149
it.Ref = git.BranchPrefix + it.Ref
148150
}
149-
issueTemplates = append(issueTemplates, it)
151+
ret.IssueTemplates = append(ret.IssueTemplates, it)
150152
}
151153
}
152154
}
153-
return issueTemplates, invalidFiles
155+
return ret
154156
}
155157

156158
// GetTemplateConfigFromDefaultBranch returns the issue config for this repo.
@@ -179,8 +181,8 @@ func GetTemplateConfigFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repo
179181
}
180182

181183
func HasTemplatesOrContactLinks(repo *repo.Repository, gitRepo *git.Repository) bool {
182-
ret, _ := GetTemplatesFromDefaultBranch(repo, gitRepo)
183-
if len(ret) > 0 {
184+
ret := ParseTemplatesFromDefaultBranch(repo, gitRepo)
185+
if len(ret.IssueTemplates) > 0 {
184186
return true
185187
}
186188

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package integration
5+
6+
import (
7+
"net/http"
8+
"net/url"
9+
"testing"
10+
11+
repo_model "code.gitea.io/gitea/models/repo"
12+
"code.gitea.io/gitea/models/unittest"
13+
user_model "code.gitea.io/gitea/models/user"
14+
api "code.gitea.io/gitea/modules/structs"
15+
16+
"github.com/stretchr/testify/assert"
17+
)
18+
19+
func TestAPIIssueTemplateList(t *testing.T) {
20+
onGiteaRun(t, func(*testing.T, *url.URL) {
21+
var issueTemplates []*api.IssueTemplate
22+
23+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
24+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
25+
26+
// no issue template
27+
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates")
28+
resp := MakeRequest(t, req, http.StatusOK)
29+
issueTemplates = nil
30+
DecodeJSON(t, resp, &issueTemplates)
31+
assert.Empty(t, issueTemplates)
32+
33+
// one correct issue template and some incorrect issue templates
34+
err := createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-ok.md", repo.DefaultBranch, `----
35+
name: foo
36+
about: bar
37+
----
38+
`)
39+
assert.NoError(t, err)
40+
41+
err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err1.yml", repo.DefaultBranch, `name: '`)
42+
assert.NoError(t, err)
43+
44+
err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err2.yml", repo.DefaultBranch, `other: `)
45+
assert.NoError(t, err)
46+
47+
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates")
48+
resp = MakeRequest(t, req, http.StatusOK)
49+
issueTemplates = nil
50+
DecodeJSON(t, resp, &issueTemplates)
51+
assert.Len(t, issueTemplates, 1)
52+
assert.Equal(t, "foo", issueTemplates[0].Name)
53+
assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning"))
54+
})
55+
}

0 commit comments

Comments
 (0)