forked from mirror/gitea
ee242a08e9
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`
192 lines
5.1 KiB
Go
192 lines
5.1 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package issue
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/models/repo"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/issue/template"
|
|
"code.gitea.io/gitea/modules/log"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// templateDirCandidates issue templates directory
|
|
var templateDirCandidates = []string{
|
|
"ISSUE_TEMPLATE",
|
|
"issue_template",
|
|
".gitea/ISSUE_TEMPLATE",
|
|
".gitea/issue_template",
|
|
".github/ISSUE_TEMPLATE",
|
|
".github/issue_template",
|
|
".gitlab/ISSUE_TEMPLATE",
|
|
".gitlab/issue_template",
|
|
}
|
|
|
|
var templateConfigCandidates = []string{
|
|
".gitea/ISSUE_TEMPLATE/config",
|
|
".gitea/issue_template/config",
|
|
".github/ISSUE_TEMPLATE/config",
|
|
".github/issue_template/config",
|
|
}
|
|
|
|
func GetDefaultTemplateConfig() api.IssueConfig {
|
|
return api.IssueConfig{
|
|
BlankIssuesEnabled: true,
|
|
ContactLinks: make([]api.IssueConfigContactLink, 0),
|
|
}
|
|
}
|
|
|
|
// GetTemplateConfig loads the given issue config file.
|
|
// It never returns a nil config.
|
|
func GetTemplateConfig(gitRepo *git.Repository, path string, commit *git.Commit) (api.IssueConfig, error) {
|
|
if gitRepo == nil {
|
|
return GetDefaultTemplateConfig(), nil
|
|
}
|
|
|
|
var err error
|
|
|
|
treeEntry, err := commit.GetTreeEntryByPath(path)
|
|
if err != nil {
|
|
return GetDefaultTemplateConfig(), err
|
|
}
|
|
|
|
reader, err := treeEntry.Blob().DataAsync()
|
|
if err != nil {
|
|
log.Debug("DataAsync: %v", err)
|
|
return GetDefaultTemplateConfig(), nil
|
|
}
|
|
|
|
defer reader.Close()
|
|
|
|
configContent, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return GetDefaultTemplateConfig(), err
|
|
}
|
|
|
|
issueConfig := GetDefaultTemplateConfig()
|
|
if err := yaml.Unmarshal(configContent, &issueConfig); err != nil {
|
|
return GetDefaultTemplateConfig(), err
|
|
}
|
|
|
|
for pos, link := range issueConfig.ContactLinks {
|
|
if link.Name == "" {
|
|
return GetDefaultTemplateConfig(), fmt.Errorf("contact_link at position %d is missing name key", pos+1)
|
|
}
|
|
|
|
if link.URL == "" {
|
|
return GetDefaultTemplateConfig(), fmt.Errorf("contact_link at position %d is missing url key", pos+1)
|
|
}
|
|
|
|
if link.About == "" {
|
|
return GetDefaultTemplateConfig(), fmt.Errorf("contact_link at position %d is missing about key", pos+1)
|
|
}
|
|
|
|
_, err = url.ParseRequestURI(link.URL)
|
|
if err != nil {
|
|
return GetDefaultTemplateConfig(), fmt.Errorf("%s is not a valid URL", link.URL)
|
|
}
|
|
}
|
|
|
|
return issueConfig, nil
|
|
}
|
|
|
|
// IsTemplateConfig returns if the given path is a issue config file.
|
|
func IsTemplateConfig(path string) bool {
|
|
for _, configName := range templateConfigCandidates {
|
|
if path == configName+".yaml" || path == configName+".yml" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 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 {
|
|
IssueTemplates []*api.IssueTemplate
|
|
TemplateErrors map[string]error
|
|
},
|
|
) {
|
|
ret.TemplateErrors = map[string]error{}
|
|
if repo.IsEmpty {
|
|
return ret
|
|
}
|
|
|
|
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
|
|
if err != nil {
|
|
return ret
|
|
}
|
|
|
|
for _, dirName := range templateDirCandidates {
|
|
tree, err := commit.SubTree(dirName)
|
|
if err != nil {
|
|
log.Debug("get sub tree of %s: %v", dirName, err)
|
|
continue
|
|
}
|
|
entries, err := tree.ListEntries()
|
|
if err != nil {
|
|
log.Debug("list entries in %s: %v", dirName, err)
|
|
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 {
|
|
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
|
|
}
|
|
ret.IssueTemplates = append(ret.IssueTemplates, it)
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// GetTemplateConfigFromDefaultBranch returns the issue config for this repo.
|
|
// It never returns a nil config.
|
|
func GetTemplateConfigFromDefaultBranch(repo *repo.Repository, gitRepo *git.Repository) (api.IssueConfig, error) {
|
|
if repo.IsEmpty {
|
|
return GetDefaultTemplateConfig(), nil
|
|
}
|
|
|
|
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
|
|
if err != nil {
|
|
return GetDefaultTemplateConfig(), err
|
|
}
|
|
|
|
for _, configName := range templateConfigCandidates {
|
|
if _, err := commit.GetTreeEntryByPath(configName + ".yaml"); err == nil {
|
|
return GetTemplateConfig(gitRepo, configName+".yaml", commit)
|
|
}
|
|
|
|
if _, err := commit.GetTreeEntryByPath(configName + ".yml"); err == nil {
|
|
return GetTemplateConfig(gitRepo, configName+".yml", commit)
|
|
}
|
|
}
|
|
|
|
return GetDefaultTemplateConfig(), nil
|
|
}
|
|
|
|
func HasTemplatesOrContactLinks(repo *repo.Repository, gitRepo *git.Repository) bool {
|
|
ret := ParseTemplatesFromDefaultBranch(repo, gitRepo)
|
|
if len(ret.IssueTemplates) > 0 {
|
|
return true
|
|
}
|
|
|
|
issueConfig, _ := GetTemplateConfigFromDefaultBranch(repo, gitRepo)
|
|
return len(issueConfig.ContactLinks) > 0
|
|
}
|