-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor issue template parsing and fix API endpoint #29069
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,21 +109,23 @@ func IsTemplateConfig(path string) bool { | |
return false | ||
} | ||
|
||
// GetTemplatesFromDefaultBranch checks for issue templates in the repo's default branch, | ||
// returns valid templates and the errors of invalid template files. | ||
func GetTemplatesFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repository) ([]*api.IssueTemplate, map[string]error) { | ||
var issueTemplates []*api.IssueTemplate | ||
|
||
// ParseTemplatesFromDefaultBranch parses the issue templates in the repo's default branch, | ||
// returns valid templates and the errors of invalid template files (the errors map is guaranteed to be non-nil). | ||
func ParseTemplatesFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repository) (ret struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we perhaps not define an inline return struct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See: #29069 (comment) Anonymous struct is perfect use case for such function, I prefer to use this syntax here. But if you have better ideas, feel free to edit this PR directly. |
||
IssueTemplates []*api.IssueTemplate | ||
TemplateErrors map[string]error | ||
}, | ||
Comment on lines
+114
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'd rather define a concrete struct type above, even if not exported. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's impossible to pass the lint, it violates "do not export private names"
But I do love this usage. Anonymous struct is perfect use case for such function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is nearly the same thing, I'd rather nolint this case than use an anonymous struct, personally. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry but I didn't get the point why "anonymous struct" is bad, we use it a lot. The code is readable enough and there should be no maintainability problem, and no one needs to "grep" the name. If you have better ideas, feel free to edit this PR directly. |
||
) { | ||
ret.TemplateErrors = map[string]error{} | ||
if repo.IsEmpty { | ||
return issueTemplates, nil | ||
return ret | ||
} | ||
|
||
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) | ||
if err != nil { | ||
return issueTemplates, nil | ||
return ret | ||
} | ||
|
||
invalidFiles := map[string]error{} | ||
for _, dirName := range templateDirCandidates { | ||
tree, err := commit.SubTree(dirName) | ||
if err != nil { | ||
|
@@ -133,24 +135,24 @@ func GetTemplatesFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repositor | |
entries, err := tree.ListEntries() | ||
if err != nil { | ||
log.Debug("list entries in %s: %v", dirName, err) | ||
return issueTemplates, nil | ||
return ret | ||
} | ||
for _, entry := range entries { | ||
if !template.CouldBe(entry.Name()) { | ||
continue | ||
} | ||
fullName := path.Join(dirName, entry.Name()) | ||
if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil { | ||
invalidFiles[fullName] = err | ||
ret.TemplateErrors[fullName] = err | ||
} else { | ||
if !strings.HasPrefix(it.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref> | ||
it.Ref = git.BranchPrefix + it.Ref | ||
} | ||
issueTemplates = append(issueTemplates, it) | ||
ret.IssueTemplates = append(ret.IssueTemplates, it) | ||
} | ||
} | ||
} | ||
return issueTemplates, invalidFiles | ||
return ret | ||
} | ||
|
||
// GetTemplateConfigFromDefaultBranch returns the issue config for this repo. | ||
|
@@ -179,8 +181,8 @@ func GetTemplateConfigFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repo | |
} | ||
|
||
func HasTemplatesOrContactLinks(repo *repo.Repository, gitRepo *git.Repository) bool { | ||
ret, _ := GetTemplatesFromDefaultBranch(repo, gitRepo) | ||
if len(ret) > 0 { | ||
ret := ParseTemplatesFromDefaultBranch(repo, gitRepo) | ||
if len(ret.IssueTemplates) > 0 { | ||
return true | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package integration | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
"testing" | ||
|
||
repo_model "code.gitea.io/gitea/models/repo" | ||
"code.gitea.io/gitea/models/unittest" | ||
user_model "code.gitea.io/gitea/models/user" | ||
api "code.gitea.io/gitea/modules/structs" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestAPIIssueTemplateList(t *testing.T) { | ||
onGiteaRun(t, func(*testing.T, *url.URL) { | ||
var issueTemplates []*api.IssueTemplate | ||
|
||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) | ||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) | ||
|
||
// no issue template | ||
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") | ||
resp := MakeRequest(t, req, http.StatusOK) | ||
issueTemplates = nil | ||
DecodeJSON(t, resp, &issueTemplates) | ||
assert.Empty(t, issueTemplates) | ||
|
||
// one correct issue template and some incorrect issue templates | ||
err := createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-ok.md", repo.DefaultBranch, `---- | ||
name: foo | ||
about: bar | ||
---- | ||
`) | ||
assert.NoError(t, err) | ||
|
||
err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err1.yml", repo.DefaultBranch, `name: '`) | ||
assert.NoError(t, err) | ||
|
||
err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err2.yml", repo.DefaultBranch, `other: `) | ||
assert.NoError(t, err) | ||
|
||
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") | ||
resp = MakeRequest(t, req, http.StatusOK) | ||
issueTemplates = nil | ||
DecodeJSON(t, resp, &issueTemplates) | ||
assert.Len(t, issueTemplates, 1) | ||
assert.Equal(t, "foo", issueTemplates[0].Name) | ||
assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That line should be fixed as per my suggestion above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #29069 (comment) I'd like to keep things simple at the moment, until someone really needs it, then it needs a complete solution for "warning" messages. |
||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is no end user really needs this error message, so I'd like to keep the message simple, and to avoid putting anything uncontrolled into the header.
So I'd like to keep the old code, if there is any user really needs the detailed error message, I will propose a complete solution in the future.