// Copyright 2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package template import ( "fmt" "io" "path/filepath" "strconv" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "gopkg.in/yaml.v2" ) // CouldBe indicates a file with the filename could be a template, // it is a low cost check before further processing. func CouldBe(filename string) bool { it := &api.IssueTemplate{ FileName: filename, } return it.Type() != "" } // Unmarshal parses out a valid template from the content func Unmarshal(filename string, content []byte) (*api.IssueTemplate, error) { it, err := unmarshal(filename, content) if err != nil { return nil, err } if err := Validate(it); err != nil { return nil, err } return it, nil } // UnmarshalFromEntry parses out a valid template from the blob in entry func UnmarshalFromEntry(entry *git.TreeEntry, dir string) (*api.IssueTemplate, error) { return unmarshalFromEntry(entry, filepath.Join(dir, entry.Name())) } // UnmarshalFromCommit parses out a valid template from the commit func UnmarshalFromCommit(commit *git.Commit, filename string) (*api.IssueTemplate, error) { entry, err := commit.GetTreeEntryByPath(filename) if err != nil { return nil, fmt.Errorf("get entry for %q: %w", filename, err) } return unmarshalFromEntry(entry, filename) } // UnmarshalFromRepo parses out a valid template from the head commit of the branch func UnmarshalFromRepo(repo *git.Repository, branch, filename string) (*api.IssueTemplate, error) { commit, err := repo.GetBranchCommit(branch) if err != nil { return nil, fmt.Errorf("get commit on branch %q: %w", branch, err) } return UnmarshalFromCommit(commit, filename) } func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTemplate, error) { if size := entry.Blob().Size(); size > setting.UI.MaxDisplayFileSize { return nil, fmt.Errorf("too large: %v > MaxDisplayFileSize", size) } r, err := entry.Blob().DataAsync() if err != nil { return nil, fmt.Errorf("data async: %w", err) } defer r.Close() content, err := io.ReadAll(r) if err != nil { return nil, fmt.Errorf("read all: %w", err) } return Unmarshal(filename, content) } func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) { it := &api.IssueTemplate{ FileName: filename, } // Compatible with treating description as about compatibleTemplate := &struct { About string `yaml:"description"` }{} if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown { templateBody, err := markdown.ExtractMetadata(string(content), it) if err != nil { return nil, err } it.Content = templateBody if it.About == "" { if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" { it.About = compatibleTemplate.About } } } else if typ == api.IssueTemplateTypeYaml { if err := yaml.Unmarshal(content, it); err != nil { return nil, fmt.Errorf("yaml unmarshal: %w", err) } if it.About == "" { if err := yaml.Unmarshal(content, compatibleTemplate); err == nil && compatibleTemplate.About != "" { it.About = compatibleTemplate.About } } for i, v := range it.Fields { if v.ID == "" { v.ID = strconv.Itoa(i) } } } return it, nil }