Skip to content

Commit 5835afd

Browse files
wxiaoguangsilverwind
authored andcommitted
Refactor issue template parsing and fix API endpoint (go-gitea#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 9778e2e commit 5835afd

File tree

6 files changed

+87
-30
lines changed

6 files changed

+87
-30
lines changed

routers/api/v1/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ func individualPermsChecker(ctx *context.APIContext) {
811811
// check for and warn against deprecated authentication options
812812
func checkDeprecatedAuthMethods(ctx *context.APIContext) {
813813
if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
814-
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.")
814+
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.")
815815
}
816816
}
817817

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

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

11721172
// GetIssueConfig returns the issue config for a repo

routers/web/repo/issue.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -993,17 +993,17 @@ func NewIssue(ctx *context.Context) {
993993
}
994994
ctx.Data["Tags"] = tags
995995

996-
_, templateErrs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
996+
ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
997997
templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
998998
for k, v := range errs {
999-
templateErrs[k] = v
999+
ret.TemplateErrors[k] = v
10001000
}
10011001
if ctx.Written() {
10021002
return
10031003
}
10041004

1005-
if len(templateErrs) > 0 {
1006-
ctx.Flash.Warning(renderErrorOfTemplates(ctx, templateErrs), true)
1005+
if len(ret.TemplateErrors) > 0 {
1006+
ctx.Flash.Warning(renderErrorOfTemplates(ctx, ret.TemplateErrors), true)
10071007
}
10081008

10091009
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(unit.TypeIssues)
@@ -1046,11 +1046,11 @@ func NewIssueChooseTemplate(ctx *context.Context) {
10461046
ctx.Data["Title"] = ctx.Tr("repo.issues.new")
10471047
ctx.Data["PageIsIssueList"] = true
10481048

1049-
issueTemplates, errs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
1050-
ctx.Data["IssueTemplates"] = issueTemplates
1049+
ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
1050+
ctx.Data["IssueTemplates"] = ret.IssueTemplates
10511051

1052-
if len(errs) > 0 {
1053-
ctx.Flash.Warning(renderErrorOfTemplates(ctx, errs), true)
1052+
if len(ret.TemplateErrors) > 0 {
1053+
ctx.Flash.Warning(renderErrorOfTemplates(ctx, ret.TemplateErrors), true)
10541054
}
10551055

10561056
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)