From 995dba14ecd4aec46c2620ff369aa18848338e2e Mon Sep 17 00:00:00 2001 From: Ruud van Asseldonk Date: Tue, 26 Aug 2025 22:08:10 +0200 Subject: [PATCH] feat: search in the `docs` directory for issue and pull request templates (#8863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For pull request templates, Forgejo currently does not look in the `docs` directory, but [GitHub does](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository#adding-a-pull-request-template). By making Forgejo also look there, it becomes possible to have the same repository work on both sites, without the need for vendor-specific paths. There was duplication in the list of accepted file paths. On the one hand it’s nice for greppability that they are all spelled out, but it does mean adding 6 variants, I thought it would be more maintainable to deduplicate the Cartesian product. I added one fully spelled out path in the comment to still maintain some greppability. Resolves forgejo/forgejo#8284 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8863 Reviewed-by: Gusted Co-authored-by: Ruud van Asseldonk Co-committed-by: Ruud van Asseldonk --- routers/web/repo/issue.go | 47 ++++++++----------- routers/web/repo/pull.go | 44 ++++++++--------- services/issue/template.go | 4 ++ tests/integration/api_issue_config_test.go | 3 +- tests/integration/api_issue_templates_test.go | 4 +- tests/integration/pull_create_test.go | 2 + 6 files changed, 50 insertions(+), 54 deletions(-) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 0373d06ea0..cab508e6c6 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -76,34 +76,27 @@ const ( issueTemplateTitleKey = "IssueTemplateTitle" ) -// IssueTemplateCandidates issue templates -var IssueTemplateCandidates = []string{ - "ISSUE_TEMPLATE.md", - "ISSUE_TEMPLATE.yaml", - "ISSUE_TEMPLATE.yml", - "issue_template.md", - "issue_template.yaml", - "issue_template.yml", - ".forgejo/ISSUE_TEMPLATE.md", - ".forgejo/ISSUE_TEMPLATE.yaml", - ".forgejo/ISSUE_TEMPLATE.yml", - ".forgejo/issue_template.md", - ".forgejo/issue_template.yaml", - ".forgejo/issue_template.yml", - ".gitea/ISSUE_TEMPLATE.md", - ".gitea/ISSUE_TEMPLATE.yaml", - ".gitea/ISSUE_TEMPLATE.yml", - ".gitea/issue_template.md", - ".gitea/issue_template.yaml", - ".gitea/issue_template.yml", - ".github/ISSUE_TEMPLATE.md", - ".github/ISSUE_TEMPLATE.yaml", - ".github/ISSUE_TEMPLATE.yml", - ".github/issue_template.md", - ".github/issue_template.yaml", - ".github/issue_template.yml", +// generateIssueTemplateLocations generates all the file paths where we +// look for an issue template, e.g. ".forgejo/ISSUE_TEMPLATE.md". +func generateIssueTemplateLocations() []string { + var result []string + prefixes := []string{"", ".forgejo/", ".gitea/", ".github/", "docs/"} + filenames := []string{"ISSUE_TEMPLATE", "issue_template"} + extensions := []string{".md", ".yaml", ".yml"} + + for _, prefix := range prefixes { + for _, filename := range filenames { + for _, extension := range extensions { + result = append(result, prefix+filename+extension) + } + } + } + + return result } +var issueTemplateCandidates = generateIssueTemplateLocations() + // MustAllowUserComment checks to make sure if an issue is locked. // If locked and user has permissions to write to the repository, // then the comment is allowed, else it is blocked @@ -1005,7 +998,7 @@ func NewIssue(ctx *context.Context) { ctx.Data["Tags"] = tags _, templateErrs := issue_service.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) - templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates) + templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, issueTemplateCandidates) for k, v := range errs { templateErrs[k] = v } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 18ba2b2a9f..65772086bc 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -66,33 +66,27 @@ const ( pullRequestTemplateKey = "PullRequestTemplate" ) -var pullRequestTemplateCandidates = []string{ - "PULL_REQUEST_TEMPLATE.md", - "PULL_REQUEST_TEMPLATE.yaml", - "PULL_REQUEST_TEMPLATE.yml", - "pull_request_template.md", - "pull_request_template.yaml", - "pull_request_template.yml", - ".forgejo/PULL_REQUEST_TEMPLATE.md", - ".forgejo/PULL_REQUEST_TEMPLATE.yaml", - ".forgejo/PULL_REQUEST_TEMPLATE.yml", - ".forgejo/pull_request_template.md", - ".forgejo/pull_request_template.yaml", - ".forgejo/pull_request_template.yml", - ".gitea/PULL_REQUEST_TEMPLATE.md", - ".gitea/PULL_REQUEST_TEMPLATE.yaml", - ".gitea/PULL_REQUEST_TEMPLATE.yml", - ".gitea/pull_request_template.md", - ".gitea/pull_request_template.yaml", - ".gitea/pull_request_template.yml", - ".github/PULL_REQUEST_TEMPLATE.md", - ".github/PULL_REQUEST_TEMPLATE.yaml", - ".github/PULL_REQUEST_TEMPLATE.yml", - ".github/pull_request_template.md", - ".github/pull_request_template.yaml", - ".github/pull_request_template.yml", +// generatePullRequestTemplateLocations generates all the file paths where we +// look for a pull request template, e.g. ".forgejo/PULL_REQUEST_TEMPLATE.md". +func generatePullRequestTemplateLocations() []string { + var result []string + prefixes := []string{"", ".forgejo/", ".gitea/", ".github/", "docs/"} + filenames := []string{"PULL_REQUEST_TEMPLATE", "pull_request_template"} + extensions := []string{".md", ".yaml", ".yml"} + + for _, prefix := range prefixes { + for _, filename := range filenames { + for _, extension := range extensions { + result = append(result, prefix+filename+extension) + } + } + } + + return result } +var pullRequestTemplateCandidates = generatePullRequestTemplateLocations() + func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository { repo, err := repo_model.GetRepositoryByID(ctx, repoID) if err != nil { diff --git a/services/issue/template.go b/services/issue/template.go index 36ee3a3027..07f59af630 100644 --- a/services/issue/template.go +++ b/services/issue/template.go @@ -31,6 +31,8 @@ var templateDirCandidates = []string{ ".github/issue_template", ".gitlab/ISSUE_TEMPLATE", ".gitlab/issue_template", + "docs/ISSUE_TEMPLATE", + "docs/issue_template", } var templateConfigCandidates = []string{ @@ -40,6 +42,8 @@ var templateConfigCandidates = []string{ ".gitea/issue_template/config", ".github/ISSUE_TEMPLATE/config", ".github/issue_template/config", + "docs/ISSUE_TEMPLATE/config", + "docs/issue_template/config", } func GetDefaultTemplateConfig() api.IssueConfig { diff --git a/tests/integration/api_issue_config_test.go b/tests/integration/api_issue_config_test.go index dd4f95e934..99b2829fbf 100644 --- a/tests/integration/api_issue_config_test.go +++ b/tests/integration/api_issue_config_test.go @@ -133,6 +133,7 @@ func TestAPIRepoIssueConfigPaths(t *testing.T) { ".gitea/issue_template/config", ".github/ISSUE_TEMPLATE/config", ".github/issue_template/config", + "docs/issue_template/config", } for _, candidate := range templateConfigCandidates { @@ -184,7 +185,7 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) { }) t.Run("Invalid", func(t *testing.T) { - dirs := []string{".gitea", ".forgejo"} + dirs := []string{".gitea", ".forgejo", "docs"} for _, dir := range dirs { t.Run(dir, func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_issue_templates_test.go b/tests/integration/api_issue_templates_test.go index 47ba34198a..3d119c1157 100644 --- a/tests/integration/api_issue_templates_test.go +++ b/tests/integration/api_issue_templates_test.go @@ -42,6 +42,7 @@ func TestAPIIssueTemplateList(t *testing.T) { ".gitea/issue_template/test.md", ".github/ISSUE_TEMPLATE/test.md", ".github/issue_template/test.md", + "docs/issue_template/test.md", } for _, template := range templateCandidates { @@ -79,6 +80,7 @@ This is the template!`) ".forgejo/issue_template/test.md", ".gitea/issue_template/test.md", ".github/issue_template/test.md", + "docs/issue_template/test.md", } defer func() { for _, template := range templatePriority { @@ -107,7 +109,7 @@ This is the template!`) // If templates have the same filename and content, but in different // directories, they count as different templates, and all are // considered. - assert.Len(t, issueTemplates, 3) + assert.Len(t, issueTemplates, 4) }) }) } diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 8c9bdcdfc6..7af70b4e5c 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -136,6 +136,8 @@ func TestPullCreateWithPullTemplate(t *testing.T) { ".gitea/pull_request_template.md", ".github/PULL_REQUEST_TEMPLATE.md", ".github/pull_request_template.md", + "docs/pull_request_template.md", + "docs/PULL_REQUEST_TEMPLATE.md", } createBaseRepo := func(t *testing.T, templateFiles []string, message string) (*repo_model.Repository, func()) {