code.gitea.io/gitea@v1.22.3/modules/issue/template/unmarshal.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package template 5 6 import ( 7 "fmt" 8 "io" 9 "path" 10 "strconv" 11 12 "code.gitea.io/gitea/modules/git" 13 "code.gitea.io/gitea/modules/markup/markdown" 14 "code.gitea.io/gitea/modules/setting" 15 api "code.gitea.io/gitea/modules/structs" 16 "code.gitea.io/gitea/modules/util" 17 18 "gopkg.in/yaml.v3" 19 ) 20 21 // CouldBe indicates a file with the filename could be a template, 22 // it is a low cost check before further processing. 23 func CouldBe(filename string) bool { 24 it := &api.IssueTemplate{ 25 FileName: filename, 26 } 27 return it.Type() != "" 28 } 29 30 // Unmarshal parses out a valid template from the content 31 func Unmarshal(filename string, content []byte) (*api.IssueTemplate, error) { 32 it, err := unmarshal(filename, content) 33 if err != nil { 34 return nil, err 35 } 36 37 if err := Validate(it); err != nil { 38 return nil, err 39 } 40 41 return it, nil 42 } 43 44 // UnmarshalFromEntry parses out a valid template from the blob in entry 45 func UnmarshalFromEntry(entry *git.TreeEntry, dir string) (*api.IssueTemplate, error) { 46 return unmarshalFromEntry(entry, path.Join(dir, entry.Name())) // Filepaths in Git are ALWAYS '/' separated do not use filepath here 47 } 48 49 // UnmarshalFromCommit parses out a valid template from the commit 50 func UnmarshalFromCommit(commit *git.Commit, filename string) (*api.IssueTemplate, error) { 51 entry, err := commit.GetTreeEntryByPath(filename) 52 if err != nil { 53 return nil, fmt.Errorf("get entry for %q: %w", filename, err) 54 } 55 return unmarshalFromEntry(entry, filename) 56 } 57 58 // UnmarshalFromRepo parses out a valid template from the head commit of the branch 59 func UnmarshalFromRepo(repo *git.Repository, branch, filename string) (*api.IssueTemplate, error) { 60 commit, err := repo.GetBranchCommit(branch) 61 if err != nil { 62 return nil, fmt.Errorf("get commit on branch %q: %w", branch, err) 63 } 64 65 return UnmarshalFromCommit(commit, filename) 66 } 67 68 func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTemplate, error) { 69 if size := entry.Blob().Size(); size > setting.UI.MaxDisplayFileSize { 70 return nil, fmt.Errorf("too large: %v > MaxDisplayFileSize", size) 71 } 72 73 r, err := entry.Blob().DataAsync() 74 if err != nil { 75 return nil, fmt.Errorf("data async: %w", err) 76 } 77 defer r.Close() 78 79 content, err := io.ReadAll(r) 80 if err != nil { 81 return nil, fmt.Errorf("read all: %w", err) 82 } 83 84 return Unmarshal(filename, content) 85 } 86 87 func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) { 88 it := &api.IssueTemplate{ 89 FileName: filename, 90 } 91 92 // Compatible with treating description as about 93 compatibleTemplate := &struct { 94 About string `yaml:"description"` 95 }{} 96 97 if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown { 98 if templateBody, err := markdown.ExtractMetadata(string(content), it); err != nil { 99 // The only thing we know here is that we can't extract metadata from the content, 100 // it's hard to tell if metadata doesn't exist or metadata isn't valid. 101 // There's an example template: 102 // 103 // --- 104 // # Title 105 // --- 106 // Content 107 // 108 // It could be a valid markdown with two horizontal lines, or an invalid markdown with wrong metadata. 109 110 it.Content = string(content) 111 it.Name = path.Base(it.FileName) // paths in Git are always '/' separated - do not use filepath! 112 it.About, _ = util.SplitStringAtByteN(it.Content, 80) 113 } else { 114 it.Content = templateBody 115 if it.About == "" { 116 if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" { 117 it.About = compatibleTemplate.About 118 } 119 } 120 } 121 } else if typ == api.IssueTemplateTypeYaml { 122 if err := yaml.Unmarshal(content, it); err != nil { 123 return nil, fmt.Errorf("yaml unmarshal: %w", err) 124 } 125 if it.About == "" { 126 if err := yaml.Unmarshal(content, compatibleTemplate); err == nil && compatibleTemplate.About != "" { 127 it.About = compatibleTemplate.About 128 } 129 } 130 for i, v := range it.Fields { 131 // set default id value 132 if v.ID == "" { 133 v.ID = strconv.Itoa(i) 134 } 135 // set default visibility 136 if v.Visible == nil { 137 v.Visible = []api.IssueFormFieldVisible{api.IssueFormFieldVisibleForm} 138 // markdown is not submitted by default 139 if v.Type != api.IssueFormFieldTypeMarkdown { 140 v.Visible = append(v.Visible, api.IssueFormFieldVisibleContent) 141 } 142 } 143 } 144 } 145 146 return it, nil 147 }