From 45ac0907dc565d9dfc5a6781f1211d970df470b5 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 27 Mar 2023 23:24:01 +0800 Subject: [PATCH 01/12] fix --- modules/label/parser.go | 17 +++----- modules/repository/init.go | 40 +++++++++++-------- routers/web/org/setting.go | 2 +- routers/web/repo/issue_label.go | 2 +- routers/web/repo/repo.go | 4 +- templates/repo/create.tmpl | 4 +- .../issue/labels/label_load_template.tmpl | 4 +- 7 files changed, 37 insertions(+), 36 deletions(-) diff --git a/modules/label/parser.go b/modules/label/parser.go index 55bf570de6b95..d45f0f6ec629b 100644 --- a/modules/label/parser.go +++ b/modules/label/parser.go @@ -36,21 +36,14 @@ func (err ErrTemplateLoad) Error() string { // GetTemplateFile loads the label template file by given name, // then parses and returns a list of name-color pairs and optionally description. func GetTemplateFile(name string) ([]*Label, error) { - data, err := options.Labels(name + ".yaml") - if err == nil && len(data) > 0 { - return parseYamlFormat(name+".yaml", data) - } - - data, err = options.Labels(name + ".yml") - if err == nil && len(data) > 0 { - return parseYamlFormat(name+".yml", data) - } - - data, err = options.Labels(name) + data, err := options.Labels(name) if err != nil { - return nil, ErrTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)} + return nil, ErrTemplateLoad{name, fmt.Errorf("GetTemplateFile: %w", err)} } + if strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml") { + return parseYamlFormat(name, data) + } return parseLegacyFormat(name, data) } diff --git a/modules/repository/init.go b/modules/repository/init.go index f9a33cd4f68c9..995129a8ae521 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -27,6 +27,12 @@ import ( asymkey_service "code.gitea.io/gitea/services/asymkey" ) +type OptionFile struct { + FileName string + DisplayName string + Description string +} + var ( // Gitignores contains the gitiginore files Gitignores []string @@ -37,8 +43,8 @@ var ( // Readmes contains the readme files Readmes []string - // LabelTemplates contains the label template files and the list of labels for each file - LabelTemplates map[string]string + // LabelTemplateFiles contains the label template files and the list of labels for each file + LabelTemplateFiles []OptionFile ) // LoadRepoConfig loads the repository config @@ -51,14 +57,6 @@ func LoadRepoConfig() { if err != nil { log.Fatal("Failed to get %s files: %v", t, err) } - if t == "label" { - for i, f := range files { - ext := strings.ToLower(filepath.Ext(f)) - if ext == ".yaml" || ext == ".yml" { - files[i] = f[:len(f)-len(ext)] - } - } - } customPath := path.Join(setting.CustomPath, "options", t) isDir, err := util.IsDir(customPath) if err != nil { @@ -71,7 +69,14 @@ func LoadRepoConfig() { } for _, f := range customFiles { - if !util.SliceContainsString(files, f, true) { + stat, err := os.Stat(filepath.Join(customPath, f)) + if err != nil { + log.Fatal("Failed to stat custom %s files: %v", t, err) + } + if stat.Size() == 0 { + // it's good to give end users a chance to hide builtin options if they put an empty file in their custom directory + files = util.SliceRemoveAllFunc(files, func(s string) bool { return strings.EqualFold(s, f) }) + } else if !util.SliceContainsString(files, f, true) { files = append(files, f) } } @@ -82,20 +87,23 @@ func LoadRepoConfig() { Gitignores = typeFiles[0] Licenses = typeFiles[1] Readmes = typeFiles[2] - LabelTemplatesFiles := typeFiles[3] + labelTemplatesFiles := typeFiles[3] sort.Strings(Gitignores) sort.Strings(Licenses) sort.Strings(Readmes) - sort.Strings(LabelTemplatesFiles) + sort.Strings(labelTemplatesFiles) // Load label templates - LabelTemplates = make(map[string]string) - for _, templateFile := range LabelTemplatesFiles { + for _, templateFile := range labelTemplatesFiles { labels, err := label.LoadFormatted(templateFile) if err != nil { log.Error("Failed to load labels: %v", err) } - LabelTemplates[templateFile] = labels + LabelTemplateFiles = append(LabelTemplateFiles, OptionFile{ + DisplayName: strings.TrimSuffix(templateFile, filepath.Ext(templateFile)), + FileName: templateFile, + Description: labels, + }) } // Filter out invalid names and promote preferred licenses. diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 654e9000fa1b5..d6af0eefd135d 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -247,6 +247,6 @@ func Labels(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettingsLabels"] = true ctx.Data["RequireTribute"] = true - ctx.Data["LabelTemplates"] = repo_module.LabelTemplates + ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles ctx.HTML(http.StatusOK, tplSettingsLabels) } diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index 31bf85fedb29a..a4ea53d13d63c 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -29,7 +29,7 @@ func Labels(ctx *context.Context) { ctx.Data["PageIsIssueList"] = true ctx.Data["PageIsLabels"] = true ctx.Data["RequireTribute"] = true - ctx.Data["LabelTemplates"] = repo_module.LabelTemplates + ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles ctx.HTML(http.StatusOK, tplLabels) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index b4e7b5a46e2a7..9b80e85324769 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -132,7 +132,7 @@ func Create(ctx *context.Context) { // Give default value for template to render. ctx.Data["Gitignores"] = repo_module.Gitignores - ctx.Data["LabelTemplates"] = repo_module.LabelTemplates + ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles ctx.Data["Licenses"] = repo_module.Licenses ctx.Data["Readmes"] = repo_module.Readmes ctx.Data["readme"] = "Default" @@ -200,7 +200,7 @@ func CreatePost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("new_repo") ctx.Data["Gitignores"] = repo_module.Gitignores - ctx.Data["LabelTemplates"] = repo_module.LabelTemplates + ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles ctx.Data["Licenses"] = repo_module.Licenses ctx.Data["Readmes"] = repo_module.Readmes diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 27e3a74c1b75a..c72f51a956a5d 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -117,8 +117,8 @@
{{.locale.Tr "repo.issue_labels_helper"}}
diff --git a/templates/repo/issue/labels/label_load_template.tmpl b/templates/repo/issue/labels/label_load_template.tmpl index 010b691638468..35f2d03d90da2 100644 --- a/templates/repo/issue/labels/label_load_template.tmpl +++ b/templates/repo/issue/labels/label_load_template.tmpl @@ -10,8 +10,8 @@
{{.locale.Tr "repo.issues.label_templates.helper"}}
{{svg "octicon-triangle-down" 18 "dropdown icon"}} From 13def4b6e4da4ea4b63f1bfdd296af4d6f99cfae Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 28 Mar 2023 00:11:10 +0800 Subject: [PATCH 02/12] fix merge --- templates/repo/create.tmpl | 2 +- templates/repo/issue/labels/label_load_template.tmpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 22d5652a12a7a..088aaf5b7579d 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -118,7 +118,7 @@ diff --git a/templates/repo/issue/labels/label_load_template.tmpl b/templates/repo/issue/labels/label_load_template.tmpl index 47315e0ed9843..7f8c6677bdf4f 100644 --- a/templates/repo/issue/labels/label_load_template.tmpl +++ b/templates/repo/issue/labels/label_load_template.tmpl @@ -11,7 +11,7 @@
{{.locale.Tr "repo.issues.label_templates.helper"}}
{{svg "octicon-triangle-down" 18 "dropdown icon"}} From 68565f0fda59a63a17e386e6e8954c524779ad67 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 28 Mar 2023 12:21:43 +0800 Subject: [PATCH 03/12] fine tune error messages --- modules/repository/init.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/modules/repository/init.go b/modules/repository/init.go index 995129a8ae521..2c584943ac75d 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -8,7 +8,6 @@ import ( "context" "fmt" "os" - "path" "path/filepath" "sort" "strings" @@ -55,26 +54,23 @@ func LoadRepoConfig() { for i, t := range types { files, err := options.Dir(t) if err != nil { - log.Fatal("Failed to get %s files: %v", t, err) + log.Fatal("Failed to list %s files: %v", t, err) } - customPath := path.Join(setting.CustomPath, "options", t) - isDir, err := util.IsDir(customPath) - if err != nil { - log.Fatal("Failed to get custom %s files: %v", t, err) - } - if isDir { + customPath := filepath.Join(setting.CustomPath, "options", t) + if isDir, err := util.IsDir(customPath); err != nil { + log.Fatal("Failed to check custom %s dir: %v", t, err) + } else if isDir { customFiles, err := util.StatDir(customPath) if err != nil { - log.Fatal("Failed to get custom %s files: %v", t, err) + log.Fatal("Failed to list custom %s files: %v", t, err) } - for _, f := range customFiles { stat, err := os.Stat(filepath.Join(customPath, f)) if err != nil { - log.Fatal("Failed to stat custom %s files: %v", t, err) + log.Fatal("Failed to stat custom %s file %q: %v", t, f, err) } + // give end users a chance to hide builtin options if they put an empty file in their custom directory if stat.Size() == 0 { - // it's good to give end users a chance to hide builtin options if they put an empty file in their custom directory files = util.SliceRemoveAllFunc(files, func(s string) bool { return strings.EqualFold(s, f) }) } else if !util.SliceContainsString(files, f, true) { files = append(files, f) From 8894acfbef58ff41c325b7818a25fb0d80749b20 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 28 Mar 2023 21:18:23 +0800 Subject: [PATCH 04/12] use "return error" instead of log.Fatal --- modules/repository/init.go | 13 +++++++------ services/repository/repository.go | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/repository/init.go b/modules/repository/init.go index 2c584943ac75d..f155476f51356 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -47,27 +47,27 @@ var ( ) // LoadRepoConfig loads the repository config -func LoadRepoConfig() { +func LoadRepoConfig() error { // Load .gitignore and license files and readme templates. types := []string{"gitignore", "license", "readme", "label"} typeFiles := make([][]string, 4) for i, t := range types { files, err := options.Dir(t) if err != nil { - log.Fatal("Failed to list %s files: %v", t, err) + return fmt.Errorf("failed to list %s files: %v", t, err) } customPath := filepath.Join(setting.CustomPath, "options", t) if isDir, err := util.IsDir(customPath); err != nil { - log.Fatal("Failed to check custom %s dir: %v", t, err) + return fmt.Errorf("failed to check custom %s dir: %v", t, err) } else if isDir { customFiles, err := util.StatDir(customPath) if err != nil { - log.Fatal("Failed to list custom %s files: %v", t, err) + return fmt.Errorf("failed to list custom %s files: %v", t, err) } for _, f := range customFiles { stat, err := os.Stat(filepath.Join(customPath, f)) if err != nil { - log.Fatal("Failed to stat custom %s file %q: %v", t, f, err) + return fmt.Errorf("failed to stat custom %s file %q: %v", t, f, err) } // give end users a chance to hide builtin options if they put an empty file in their custom directory if stat.Size() == 0 { @@ -93,7 +93,7 @@ func LoadRepoConfig() { for _, templateFile := range labelTemplatesFiles { labels, err := label.LoadFormatted(templateFile) if err != nil { - log.Error("Failed to load labels: %v", err) + return fmt.Errorf("failed to load labels: %v", err) } LabelTemplateFiles = append(LabelTemplateFiles, OptionFile{ DisplayName: strings.TrimSuffix(templateFile, filepath.Ext(templateFile)), @@ -115,6 +115,7 @@ func LoadRepoConfig() { } } Licenses = sortedLicenses + return nil } func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, repoPath string, opts CreateRepoOptions) error { diff --git a/services/repository/repository.go b/services/repository/repository.go index 000b1a3da6bce..0d6529383cc8d 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -81,7 +81,9 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN // Init start repository service func Init() error { - repo_module.LoadRepoConfig() + if err := repo_module.LoadRepoConfig(); err != nil { + return err + } system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath()) return initPushQueue() From 7be96adb7a3f00c3ba7159b6c806199015a2f533 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 28 Mar 2023 23:26:21 +0800 Subject: [PATCH 05/12] use %w for fmt.Errorf --- modules/repository/init.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/repository/init.go b/modules/repository/init.go index f155476f51356..012838170170c 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -54,20 +54,20 @@ func LoadRepoConfig() error { for i, t := range types { files, err := options.Dir(t) if err != nil { - return fmt.Errorf("failed to list %s files: %v", t, err) + return fmt.Errorf("failed to list %s files: %w", t, err) } customPath := filepath.Join(setting.CustomPath, "options", t) if isDir, err := util.IsDir(customPath); err != nil { - return fmt.Errorf("failed to check custom %s dir: %v", t, err) + return fmt.Errorf("failed to check custom %s dir: %w", t, err) } else if isDir { customFiles, err := util.StatDir(customPath) if err != nil { - return fmt.Errorf("failed to list custom %s files: %v", t, err) + return fmt.Errorf("failed to list custom %s files: %w", t, err) } for _, f := range customFiles { stat, err := os.Stat(filepath.Join(customPath, f)) if err != nil { - return fmt.Errorf("failed to stat custom %s file %q: %v", t, f, err) + return fmt.Errorf("failed to stat custom %s file %q: %w", t, f, err) } // give end users a chance to hide builtin options if they put an empty file in their custom directory if stat.Size() == 0 { @@ -93,7 +93,7 @@ func LoadRepoConfig() error { for _, templateFile := range labelTemplatesFiles { labels, err := label.LoadFormatted(templateFile) if err != nil { - return fmt.Errorf("failed to load labels: %v", err) + return fmt.Errorf("failed to load labels: %w", err) } LabelTemplateFiles = append(LabelTemplateFiles, OptionFile{ DisplayName: strings.TrimSuffix(templateFile, filepath.Ext(templateFile)), From 5cb8967e6aac7454f5b0ece7cfb75d321894ea4b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 29 Mar 2023 21:58:18 +0800 Subject: [PATCH 06/12] remove empty file check, compare file names case-sensitively. --- modules/repository/init.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/repository/init.go b/modules/repository/init.go index 012838170170c..3f1a9567896e9 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -65,14 +65,7 @@ func LoadRepoConfig() error { return fmt.Errorf("failed to list custom %s files: %w", t, err) } for _, f := range customFiles { - stat, err := os.Stat(filepath.Join(customPath, f)) - if err != nil { - return fmt.Errorf("failed to stat custom %s file %q: %w", t, f, err) - } - // give end users a chance to hide builtin options if they put an empty file in their custom directory - if stat.Size() == 0 { - files = util.SliceRemoveAllFunc(files, func(s string) bool { return strings.EqualFold(s, f) }) - } else if !util.SliceContainsString(files, f, true) { + if !util.SliceContainsString(files, f) { files = append(files, f) } } From 9a3947cb8da3ec2e3e3b17a63e0357f224cd589d Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 29 Mar 2023 23:16:26 +0800 Subject: [PATCH 07/12] de-duplicate Label sets --- modules/repository/init.go | 65 +++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/modules/repository/init.go b/modules/repository/init.go index 3f1a9567896e9..4825d51f304d3 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -46,41 +46,70 @@ var ( LabelTemplateFiles []OptionFile ) +type optionFileList struct { + builtin []string + custom []string +} + +// mergeBuiltinCustomFiles merges the builtin and custom files, de-duplicates and returns a sorted list of files +func mergeBuiltinCustomFiles(fl optionFileList) []string { + files := fl.builtin + for _, f := range fl.custom { + // for most cases, the SliceContainsString is good enough because there are only a few custom files + if !util.SliceContainsString(files, f) { + files = append(files, f) + } + } + sort.Strings(files) + return files +} + +// mergeBuiltinCustomLabels merges the builtin and custom files. Always use the file's main name as the key to de-duplicate. +func mergeBuiltinCustomLabels(fl optionFileList) []string { + files := fl.builtin + if len(fl.custom) > 0 { + m := map[string]string{} + for _, f := range fl.builtin { + m[strings.TrimSuffix(f, filepath.Ext(f))] = f + } + for _, f := range fl.custom { + m[strings.TrimSuffix(f, filepath.Ext(f))] = f + } + files = make([]string, 0, len(m)) + for _, f := range m { + files = append(files, f) + } + } + sort.Strings(files) + return files +} + // LoadRepoConfig loads the repository config func LoadRepoConfig() error { // Load .gitignore and license files and readme templates. types := []string{"gitignore", "license", "readme", "label"} - typeFiles := make([][]string, 4) + optionTypeFiles := make([]optionFileList, len(types)) + for i, t := range types { - files, err := options.Dir(t) - if err != nil { + var err error + if optionTypeFiles[i].builtin, err = options.Dir(t); err != nil { return fmt.Errorf("failed to list %s files: %w", t, err) } customPath := filepath.Join(setting.CustomPath, "options", t) if isDir, err := util.IsDir(customPath); err != nil { return fmt.Errorf("failed to check custom %s dir: %w", t, err) } else if isDir { - customFiles, err := util.StatDir(customPath) + optionTypeFiles[i].custom, err = util.StatDir(customPath) if err != nil { return fmt.Errorf("failed to list custom %s files: %w", t, err) } - for _, f := range customFiles { - if !util.SliceContainsString(files, f) { - files = append(files, f) - } - } } - typeFiles[i] = files } - Gitignores = typeFiles[0] - Licenses = typeFiles[1] - Readmes = typeFiles[2] - labelTemplatesFiles := typeFiles[3] - sort.Strings(Gitignores) - sort.Strings(Licenses) - sort.Strings(Readmes) - sort.Strings(labelTemplatesFiles) + Gitignores = mergeBuiltinCustomFiles(optionTypeFiles[0]) + Licenses = mergeBuiltinCustomFiles(optionTypeFiles[1]) + Readmes = mergeBuiltinCustomFiles(optionTypeFiles[2]) + labelTemplatesFiles := mergeBuiltinCustomLabels(optionTypeFiles[3]) // Load label templates for _, templateFile := range labelTemplatesFiles { From 9783a19ba88c31d4fb64544ab6f92c61d033c39f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 29 Mar 2023 23:42:02 +0800 Subject: [PATCH 08/12] define extension priority --- modules/repository/init.go | 40 +++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/modules/repository/init.go b/modules/repository/init.go index 4825d51f304d3..02b8cfdd8f8c7 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -47,13 +47,13 @@ var ( ) type optionFileList struct { - builtin []string - custom []string + all []string + custom []string } -// mergeBuiltinCustomFiles merges the builtin and custom files, de-duplicates and returns a sorted list of files -func mergeBuiltinCustomFiles(fl optionFileList) []string { - files := fl.builtin +// mergeCustomFiles merges the custom files, de-duplicates and returns a sorted list of files +func mergeCustomFiles(fl optionFileList) []string { + files := fl.all for _, f := range fl.custom { // for most cases, the SliceContainsString is good enough because there are only a few custom files if !util.SliceContainsString(files, f) { @@ -64,12 +64,24 @@ func mergeBuiltinCustomFiles(fl optionFileList) []string { return files } -// mergeBuiltinCustomLabels merges the builtin and custom files. Always use the file's main name as the key to de-duplicate. -func mergeBuiltinCustomLabels(fl optionFileList) []string { - files := fl.builtin +// mergeCustomLabels merges the custom label files. Always use the file's main name as the key to de-duplicate. +func mergeCustomLabels(fl optionFileList) []string { + exts := map[string]int{ + "": 0, + ".yml": 1, + ".yaml": 2, + } + sort.Slice(fl.all, func(i, j int) bool { + return exts[filepath.Ext(fl.all[i])] < exts[filepath.Ext(fl.all[j])] + }) + sort.Slice(fl.custom, func(i, j int) bool { + return exts[filepath.Ext(fl.custom[i])] < exts[filepath.Ext(fl.custom[j])] + }) + + files := fl.all if len(fl.custom) > 0 { m := map[string]string{} - for _, f := range fl.builtin { + for _, f := range fl.all { m[strings.TrimSuffix(f, filepath.Ext(f))] = f } for _, f := range fl.custom { @@ -92,7 +104,7 @@ func LoadRepoConfig() error { for i, t := range types { var err error - if optionTypeFiles[i].builtin, err = options.Dir(t); err != nil { + if optionTypeFiles[i].all, err = options.Dir(t); err != nil { return fmt.Errorf("failed to list %s files: %w", t, err) } customPath := filepath.Join(setting.CustomPath, "options", t) @@ -106,10 +118,10 @@ func LoadRepoConfig() error { } } - Gitignores = mergeBuiltinCustomFiles(optionTypeFiles[0]) - Licenses = mergeBuiltinCustomFiles(optionTypeFiles[1]) - Readmes = mergeBuiltinCustomFiles(optionTypeFiles[2]) - labelTemplatesFiles := mergeBuiltinCustomLabels(optionTypeFiles[3]) + Gitignores = mergeCustomFiles(optionTypeFiles[0]) + Licenses = mergeCustomFiles(optionTypeFiles[1]) + Readmes = mergeCustomFiles(optionTypeFiles[2]) + labelTemplatesFiles := mergeCustomLabels(optionTypeFiles[3]) // Load label templates for _, templateFile := range labelTemplatesFiles { From 1ba0a416e906fb75f8df8d9e1b1bb1ab3cebbdc8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 30 Mar 2023 00:03:33 +0800 Subject: [PATCH 09/12] simplify code --- modules/repository/init.go | 42 +++++++++++--------------------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/modules/repository/init.go b/modules/repository/init.go index 02b8cfdd8f8c7..1e2ad246b76df 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -47,30 +47,13 @@ var ( ) type optionFileList struct { - all []string - custom []string -} - -// mergeCustomFiles merges the custom files, de-duplicates and returns a sorted list of files -func mergeCustomFiles(fl optionFileList) []string { - files := fl.all - for _, f := range fl.custom { - // for most cases, the SliceContainsString is good enough because there are only a few custom files - if !util.SliceContainsString(files, f) { - files = append(files, f) - } - } - sort.Strings(files) - return files + all []string // all files provided by bindata & custom-path. sorted. + custom []string // custom files provided by custom-path. non-sorted. } // mergeCustomLabels merges the custom label files. Always use the file's main name as the key to de-duplicate. func mergeCustomLabels(fl optionFileList) []string { - exts := map[string]int{ - "": 0, - ".yml": 1, - ".yaml": 2, - } + exts := map[string]int{"": 0, ".yml": 1, ".yaml": 2} // "yaml" file has the highest priority to be used. sort.Slice(fl.all, func(i, j int) bool { return exts[filepath.Ext(fl.all[i])] < exts[filepath.Ext(fl.all[j])] }) @@ -98,32 +81,31 @@ func mergeCustomLabels(fl optionFileList) []string { // LoadRepoConfig loads the repository config func LoadRepoConfig() error { - // Load .gitignore and license files and readme templates. - types := []string{"gitignore", "license", "readme", "label"} - optionTypeFiles := make([]optionFileList, len(types)) - + types := []string{"gitignore", "license", "readme", "label"} // option file directories + typeFiles := make([]optionFileList, len(types)) for i, t := range types { var err error - if optionTypeFiles[i].all, err = options.Dir(t); err != nil { + if typeFiles[i].all, err = options.Dir(t); err != nil { return fmt.Errorf("failed to list %s files: %w", t, err) } + sort.Strings(typeFiles[i].all) customPath := filepath.Join(setting.CustomPath, "options", t) if isDir, err := util.IsDir(customPath); err != nil { return fmt.Errorf("failed to check custom %s dir: %w", t, err) } else if isDir { - optionTypeFiles[i].custom, err = util.StatDir(customPath) + typeFiles[i].custom, err = util.StatDir(customPath) if err != nil { return fmt.Errorf("failed to list custom %s files: %w", t, err) } } } - Gitignores = mergeCustomFiles(optionTypeFiles[0]) - Licenses = mergeCustomFiles(optionTypeFiles[1]) - Readmes = mergeCustomFiles(optionTypeFiles[2]) - labelTemplatesFiles := mergeCustomLabels(optionTypeFiles[3]) + Gitignores = typeFiles[0].all + Licenses = typeFiles[1].all + Readmes = typeFiles[2].all // Load label templates + labelTemplatesFiles := mergeCustomLabels(typeFiles[3]) for _, templateFile := range labelTemplatesFiles { labels, err := label.LoadFormatted(templateFile) if err != nil { From 77ab78634060cf6fd3654692bafa5180ba2bb1b0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 30 Mar 2023 00:21:36 +0800 Subject: [PATCH 10/12] add tests --- modules/repository/init.go | 25 ++++++++++++------------- modules/repository/init_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 modules/repository/init_test.go diff --git a/modules/repository/init.go b/modules/repository/init.go index 1e2ad246b76df..2b96b18a8f68c 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -61,20 +61,19 @@ func mergeCustomLabels(fl optionFileList) []string { return exts[filepath.Ext(fl.custom[i])] < exts[filepath.Ext(fl.custom[j])] }) - files := fl.all - if len(fl.custom) > 0 { - m := map[string]string{} - for _, f := range fl.all { - m[strings.TrimSuffix(f, filepath.Ext(f))] = f - } - for _, f := range fl.custom { - m[strings.TrimSuffix(f, filepath.Ext(f))] = f - } - files = make([]string, 0, len(m)) - for _, f := range m { - files = append(files, f) - } + m := map[string]string{} + for _, f := range fl.all { + m[strings.TrimSuffix(f, filepath.Ext(f))] = f + } + for _, f := range fl.custom { + m[strings.TrimSuffix(f, filepath.Ext(f))] = f } + + files := make([]string, 0, len(m)) + for _, f := range m { + files = append(files, f) + } + sort.Strings(files) return files } diff --git a/modules/repository/init_test.go b/modules/repository/init_test.go new file mode 100644 index 0000000000000..d592e9a9fc084 --- /dev/null +++ b/modules/repository/init_test.go @@ -0,0 +1,30 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMergeCustomLabels(t *testing.T) { + files := mergeCustomLabels(optionFileList{ + all: []string{"a", "a.yaml", "a.yml"}, + custom: nil, + }) + assert.EqualValues(t, []string{"a.yaml"}, files, "yaml file should win") + + files = mergeCustomLabels(optionFileList{ + all: []string{"a", "a.yaml"}, + custom: []string{"a"}, + }) + assert.EqualValues(t, []string{"a"}, files, "custom file should win") + + files = mergeCustomLabels(optionFileList{ + all: []string{"a", "a.yml", "a.yaml"}, + custom: []string{"a", "a.yml"}, + }) + assert.EqualValues(t, []string{"a.yml"}, files, "custom yml file should win if no yaml") +} From 964d81b0bece7db9d86e9b78f586aab3fd1ceeef Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 30 Mar 2023 00:53:20 +0800 Subject: [PATCH 11/12] always use DisplayName --- modules/label/parser.go | 38 +++++++++---------- modules/repository/create.go | 3 +- modules/repository/init.go | 22 ++++++++--- templates/repo/create.tmpl | 2 +- .../issue/labels/label_load_template.tmpl | 2 +- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/modules/label/parser.go b/modules/label/parser.go index d45f0f6ec629b..c3f18ec057845 100644 --- a/modules/label/parser.go +++ b/modules/label/parser.go @@ -30,24 +30,24 @@ func IsErrTemplateLoad(err error) bool { } func (err ErrTemplateLoad) Error() string { - return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError) + return fmt.Sprintf("failed to load label template file %q: %v", err.TemplateFile, err.OriginalError) } -// GetTemplateFile loads the label template file by given name, +// LoadTemplateFile loads the label template file by given file name, // then parses and returns a list of name-color pairs and optionally description. -func GetTemplateFile(name string) ([]*Label, error) { - data, err := options.Labels(name) +func LoadTemplateFile(fileName string) ([]*Label, error) { + data, err := options.Labels(fileName) if err != nil { - return nil, ErrTemplateLoad{name, fmt.Errorf("GetTemplateFile: %w", err)} + return nil, ErrTemplateLoad{fileName, fmt.Errorf("LoadTemplateFile: %w", err)} } - if strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml") { - return parseYamlFormat(name, data) + if strings.HasSuffix(fileName, ".yaml") || strings.HasSuffix(fileName, ".yml") { + return parseYamlFormat(fileName, data) } - return parseLegacyFormat(name, data) + return parseLegacyFormat(fileName, data) } -func parseYamlFormat(name string, data []byte) ([]*Label, error) { +func parseYamlFormat(fileName string, data []byte) ([]*Label, error) { lf := &labelFile{} if err := yaml.Unmarshal(data, lf); err != nil { @@ -58,11 +58,11 @@ func parseYamlFormat(name string, data []byte) ([]*Label, error) { for _, l := range lf.Labels { l.Color = strings.TrimSpace(l.Color) if len(l.Name) == 0 || len(l.Color) == 0 { - return nil, ErrTemplateLoad{name, errors.New("label name and color are required fields")} + return nil, ErrTemplateLoad{fileName, errors.New("label name and color are required fields")} } color, err := NormalizeColor(l.Color) if err != nil { - return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in label: %s", l.Color, l.Name)} + return nil, ErrTemplateLoad{fileName, fmt.Errorf("bad HTML color code '%s' in label: %s", l.Color, l.Name)} } l.Color = color } @@ -70,7 +70,7 @@ func parseYamlFormat(name string, data []byte) ([]*Label, error) { return lf.Labels, nil } -func parseLegacyFormat(name string, data []byte) ([]*Label, error) { +func parseLegacyFormat(fileName string, data []byte) ([]*Label, error) { lines := strings.Split(string(data), "\n") list := make([]*Label, 0, len(lines)) for i := 0; i < len(lines); i++ { @@ -81,18 +81,18 @@ func parseLegacyFormat(name string, data []byte) ([]*Label, error) { parts, description, _ := strings.Cut(line, ";") - color, name, ok := strings.Cut(parts, " ") + color, labelName, ok := strings.Cut(parts, " ") if !ok { - return nil, ErrTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)} + return nil, ErrTemplateLoad{fileName, fmt.Errorf("line is malformed: %s", line)} } color, err := NormalizeColor(color) if err != nil { - return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in line: %s", color, line)} + return nil, ErrTemplateLoad{fileName, fmt.Errorf("bad HTML color code '%s' in line: %s", color, line)} } list = append(list, &Label{ - Name: strings.TrimSpace(name), + Name: strings.TrimSpace(labelName), Color: color, Description: strings.TrimSpace(description), }) @@ -101,10 +101,10 @@ func parseLegacyFormat(name string, data []byte) ([]*Label, error) { return list, nil } -// LoadFormatted loads the labels' list of a template file as a string separated by comma -func LoadFormatted(name string) (string, error) { +// LoadLabelFileDescription loads the labels' list of a template file as a string separated by comma +func LoadLabelFileDescription(fileName string) (string, error) { var buf strings.Builder - list, err := GetTemplateFile(name) + list, err := LoadTemplateFile(fileName) if err != nil { return "", err } diff --git a/modules/repository/create.go b/modules/repository/create.go index 6a1fa41b6b87d..c1395242c577b 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -23,7 +23,6 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -190,7 +189,7 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m // Check if label template exist if len(opts.IssueLabels) > 0 { - if _, err := label.GetTemplateFile(opts.IssueLabels); err != nil { + if _, err := LoadTemplateLabelsByDisplayName(opts.IssueLabels); err != nil { return nil, err } } diff --git a/modules/repository/init.go b/modules/repository/init.go index 2b96b18a8f68c..d35864a619467 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -43,7 +43,8 @@ var ( Readmes []string // LabelTemplateFiles contains the label template files and the list of labels for each file - LabelTemplateFiles []OptionFile + LabelTemplateFiles []OptionFile + labelTemplateFileMap = map[string]string{} ) type optionFileList struct { @@ -106,14 +107,16 @@ func LoadRepoConfig() error { // Load label templates labelTemplatesFiles := mergeCustomLabels(typeFiles[3]) for _, templateFile := range labelTemplatesFiles { - labels, err := label.LoadFormatted(templateFile) + description, err := label.LoadLabelFileDescription(templateFile) if err != nil { return fmt.Errorf("failed to load labels: %w", err) } + displayName := strings.TrimSuffix(templateFile, filepath.Ext(templateFile)) + labelTemplateFileMap[displayName] = templateFile LabelTemplateFiles = append(LabelTemplateFiles, OptionFile{ - DisplayName: strings.TrimSuffix(templateFile, filepath.Ext(templateFile)), + DisplayName: displayName, FileName: templateFile, - Description: labels, + Description: description, }) } @@ -364,7 +367,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re // InitializeLabels adds a label set to a repository using a template func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error { - list, err := label.GetTemplateFile(labelTemplate) + list, err := LoadTemplateLabelsByDisplayName(labelTemplate) if err != nil { return err } @@ -390,3 +393,12 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg } return nil } + +// LoadTemplateLabelsByDisplayName loads a label template by its display name +func LoadTemplateLabelsByDisplayName(name string) ([]*label.Label, error) { + fileName, ok := labelTemplateFileMap[name] + if !ok { + return nil, fmt.Errorf("label template %s not found", name) + } + return label.LoadTemplateFile(fileName) +} diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 088aaf5b7579d..85b02f394d5cf 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -118,7 +118,7 @@ diff --git a/templates/repo/issue/labels/label_load_template.tmpl b/templates/repo/issue/labels/label_load_template.tmpl index 7f8c6677bdf4f..d883953bf6e5e 100644 --- a/templates/repo/issue/labels/label_load_template.tmpl +++ b/templates/repo/issue/labels/label_load_template.tmpl @@ -11,7 +11,7 @@
{{.locale.Tr "repo.issues.label_templates.helper"}}
{{svg "octicon-triangle-down" 18 "dropdown icon"}} From b42fa8d07c35fa79419d1a264f2aa085df08e654 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 30 Mar 2023 01:09:39 +0800 Subject: [PATCH 12/12] fine tune --- modules/label/parser.go | 7 ++-- modules/repository/init.go | 61 ++++++++++++---------------- modules/repository/init_test.go | 6 +-- routers/web/repo/issue_label_test.go | 2 + 4 files changed, 33 insertions(+), 43 deletions(-) diff --git a/modules/label/parser.go b/modules/label/parser.go index c3f18ec057845..511bac823ff8d 100644 --- a/modules/label/parser.go +++ b/modules/label/parser.go @@ -33,8 +33,7 @@ func (err ErrTemplateLoad) Error() string { return fmt.Sprintf("failed to load label template file %q: %v", err.TemplateFile, err.OriginalError) } -// LoadTemplateFile loads the label template file by given file name, -// then parses and returns a list of name-color pairs and optionally description. +// LoadTemplateFile loads the label template file by given file name, returns a slice of Label structs. func LoadTemplateFile(fileName string) ([]*Label, error) { data, err := options.Labels(fileName) if err != nil { @@ -101,8 +100,8 @@ func parseLegacyFormat(fileName string, data []byte) ([]*Label, error) { return list, nil } -// LoadLabelFileDescription loads the labels' list of a template file as a string separated by comma -func LoadLabelFileDescription(fileName string) (string, error) { +// LoadTemplateDescription loads the labels from a template file, returns a description string by joining each Label.Name with comma +func LoadTemplateDescription(fileName string) (string, error) { var buf strings.Builder list, err := LoadTemplateFile(fileName) if err != nil { diff --git a/modules/repository/init.go b/modules/repository/init.go index d35864a619467..38dd8a0c4f969 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -27,7 +27,6 @@ import ( ) type OptionFile struct { - FileName string DisplayName string Description string } @@ -42,39 +41,34 @@ var ( // Readmes contains the readme files Readmes []string - // LabelTemplateFiles contains the label template files and the list of labels for each file + // LabelTemplateFiles contains the label template files, each item has its DisplayName and Description LabelTemplateFiles []OptionFile - labelTemplateFileMap = map[string]string{} + labelTemplateFileMap = map[string]string{} // DisplayName => FileName mapping ) type optionFileList struct { - all []string // all files provided by bindata & custom-path. sorted. - custom []string // custom files provided by custom-path. non-sorted. + all []string // all files provided by bindata & custom-path. Sorted. + custom []string // custom files provided by custom-path. Non-sorted, internal use only. } -// mergeCustomLabels merges the custom label files. Always use the file's main name as the key to de-duplicate. -func mergeCustomLabels(fl optionFileList) []string { +// mergeCustomLabelFiles merges the custom label files. Always use the file's main name (DisplayName) as the key to de-duplicate. +func mergeCustomLabelFiles(fl optionFileList) []string { exts := map[string]int{"": 0, ".yml": 1, ".yaml": 2} // "yaml" file has the highest priority to be used. - sort.Slice(fl.all, func(i, j int) bool { - return exts[filepath.Ext(fl.all[i])] < exts[filepath.Ext(fl.all[j])] - }) - sort.Slice(fl.custom, func(i, j int) bool { - return exts[filepath.Ext(fl.custom[i])] < exts[filepath.Ext(fl.custom[j])] - }) m := map[string]string{} - for _, f := range fl.all { - m[strings.TrimSuffix(f, filepath.Ext(f))] = f - } - for _, f := range fl.custom { - m[strings.TrimSuffix(f, filepath.Ext(f))] = f + merge := func(list []string) { + sort.Slice(list, func(i, j int) bool { return exts[filepath.Ext(list[i])] < exts[filepath.Ext(list[j])] }) + for _, f := range list { + m[strings.TrimSuffix(f, filepath.Ext(f))] = f + } } + merge(fl.all) + merge(fl.custom) files := make([]string, 0, len(m)) for _, f := range m { files = append(files, f) } - sort.Strings(files) return files } @@ -93,8 +87,7 @@ func LoadRepoConfig() error { if isDir, err := util.IsDir(customPath); err != nil { return fmt.Errorf("failed to check custom %s dir: %w", t, err) } else if isDir { - typeFiles[i].custom, err = util.StatDir(customPath) - if err != nil { + if typeFiles[i].custom, err = util.StatDir(customPath); err != nil { return fmt.Errorf("failed to list custom %s files: %w", t, err) } } @@ -105,19 +98,16 @@ func LoadRepoConfig() error { Readmes = typeFiles[2].all // Load label templates - labelTemplatesFiles := mergeCustomLabels(typeFiles[3]) - for _, templateFile := range labelTemplatesFiles { - description, err := label.LoadLabelFileDescription(templateFile) + LabelTemplateFiles = nil + labelTemplateFileMap = map[string]string{} + for _, file := range mergeCustomLabelFiles(typeFiles[3]) { + description, err := label.LoadTemplateDescription(file) if err != nil { return fmt.Errorf("failed to load labels: %w", err) } - displayName := strings.TrimSuffix(templateFile, filepath.Ext(templateFile)) - labelTemplateFileMap[displayName] = templateFile - LabelTemplateFiles = append(LabelTemplateFiles, OptionFile{ - DisplayName: displayName, - FileName: templateFile, - Description: description, - }) + displayName := strings.TrimSuffix(file, filepath.Ext(file)) + labelTemplateFileMap[displayName] = file + LabelTemplateFiles = append(LabelTemplateFiles, OptionFile{DisplayName: displayName, Description: description}) } // Filter out invalid names and promote preferred licenses. @@ -395,10 +385,9 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg } // LoadTemplateLabelsByDisplayName loads a label template by its display name -func LoadTemplateLabelsByDisplayName(name string) ([]*label.Label, error) { - fileName, ok := labelTemplateFileMap[name] - if !ok { - return nil, fmt.Errorf("label template %s not found", name) +func LoadTemplateLabelsByDisplayName(displayName string) ([]*label.Label, error) { + if fileName, ok := labelTemplateFileMap[displayName]; ok { + return label.LoadTemplateFile(fileName) } - return label.LoadTemplateFile(fileName) + return nil, label.ErrTemplateLoad{TemplateFile: displayName, OriginalError: fmt.Errorf("label template %q not found", displayName)} } diff --git a/modules/repository/init_test.go b/modules/repository/init_test.go index d592e9a9fc084..227efdc1db0cb 100644 --- a/modules/repository/init_test.go +++ b/modules/repository/init_test.go @@ -10,19 +10,19 @@ import ( ) func TestMergeCustomLabels(t *testing.T) { - files := mergeCustomLabels(optionFileList{ + files := mergeCustomLabelFiles(optionFileList{ all: []string{"a", "a.yaml", "a.yml"}, custom: nil, }) assert.EqualValues(t, []string{"a.yaml"}, files, "yaml file should win") - files = mergeCustomLabels(optionFileList{ + files = mergeCustomLabelFiles(optionFileList{ all: []string{"a", "a.yaml"}, custom: []string{"a"}, }) assert.EqualValues(t, []string{"a"}, files, "custom file should win") - files = mergeCustomLabels(optionFileList{ + files = mergeCustomLabelFiles(optionFileList{ all: []string{"a", "a.yml", "a.yaml"}, custom: []string{"a", "a.yml"}, }) diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go index a62d2afaa830c..c24fe898b6bca 100644 --- a/routers/web/repo/issue_label_test.go +++ b/routers/web/repo/issue_label_test.go @@ -10,6 +10,7 @@ import ( issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" @@ -30,6 +31,7 @@ func int64SliceToCommaSeparated(a []int64) string { func TestInitializeLabels(t *testing.T) { unittest.PrepareTestEnv(t) + assert.NoError(t, repository.LoadRepoConfig()) ctx := test.MockContext(t, "user2/repo1/labels/initialize") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 2)