Merge remote-tracking branch 'forgejo/forgejo-dependency' into wip-forgejo

Conflicts:
	.forgejo/workflows/testing.yml
	trivial conflict
This commit is contained in:
Earl Warren 2024-02-09 19:00:54 +01:00
commit 7cbf05fafb
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
10 changed files with 288 additions and 37 deletions

View file

@ -8,7 +8,7 @@ on:
- 'v*/forgejo*' - 'v*/forgejo*'
jobs: jobs:
lint-backend: backend-checks:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
container: container:
@ -20,26 +20,13 @@ jobs:
go-version: "1.21" go-version: "1.21"
check-latest: true check-latest: true
- run: make deps-backend deps-tools - run: make deps-backend deps-tools
- run: make lint-backend - run: make --always-make -j$(nproc) lint-backend checks-backend # ensure the "go-licenses" make target runs
env: env:
TAGS: bindata sqlite sqlite_unlock_notify TAGS: bindata sqlite sqlite_unlock_notify
checks-backend:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker
container:
image: 'docker.io/node:20-bookworm'
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version: "1.21"
check-latest: true
- run: make deps-backend deps-tools
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
test-unit: test-unit:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [lint-backend, checks-backend] needs: [backend-checks]
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
services: services:
@ -80,7 +67,7 @@ jobs:
test-mysql: test-mysql:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [lint-backend, checks-backend] needs: [backend-checks]
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
services: services:
@ -126,7 +113,7 @@ jobs:
test-pgsql: test-pgsql:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [lint-backend, checks-backend] needs: [backend-checks]
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
services: services:
@ -174,7 +161,7 @@ jobs:
test-sqlite: test-sqlite:
if: ${{ !startsWith(vars.ROLE, 'forgejo-') }} if: ${{ !startsWith(vars.ROLE, 'forgejo-') }}
runs-on: docker runs-on: docker
needs: [lint-backend, checks-backend] needs: [backend-checks]
container: container:
image: 'docker.io/node:20-bookworm' image: 'docker.io/node:20-bookworm'
steps: steps:

View file

@ -1174,11 +1174,7 @@ func GetIssueTemplates(ctx *context.APIContext) {
// "$ref": "#/responses/IssueTemplates" // "$ref": "#/responses/IssueTemplates"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
ret, err := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) ret, _ := issue.GetTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetTemplatesFromDefaultBranch", err)
return
}
ctx.JSON(http.StatusOK, ret) ctx.JSON(http.StatusOK, ret)
} }

View file

@ -77,6 +77,12 @@ var IssueTemplateCandidates = []string{
"issue_template.md", "issue_template.md",
"issue_template.yaml", "issue_template.yaml",
"issue_template.yml", "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.md",
".gitea/ISSUE_TEMPLATE.yaml", ".gitea/ISSUE_TEMPLATE.yaml",
".gitea/ISSUE_TEMPLATE.yml", ".gitea/ISSUE_TEMPLATE.yml",

View file

@ -65,6 +65,12 @@ var pullRequestTemplateCandidates = []string{
"pull_request_template.md", "pull_request_template.md",
"pull_request_template.yaml", "pull_request_template.yaml",
"pull_request_template.yml", "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.md",
".gitea/PULL_REQUEST_TEMPLATE.yaml", ".gitea/PULL_REQUEST_TEMPLATE.yaml",
".gitea/PULL_REQUEST_TEMPLATE.yml", ".gitea/PULL_REQUEST_TEMPLATE.yml",

View file

@ -94,6 +94,10 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try
if entry.Name() == "docs" || docsEntries[0] == nil { if entry.Name() == "docs" || docsEntries[0] == nil {
docsEntries[0] = entry docsEntries[0] = entry
} }
case ".forgejo":
if entry.Name() == ".forgejo" || docsEntries[1] == nil {
docsEntries[1] = entry
}
case ".gitea": case ".gitea":
if entry.Name() == ".gitea" || docsEntries[1] == nil { if entry.Name() == ".gitea" || docsEntries[1] == nil {
docsEntries[1] = entry docsEntries[1] = entry

View file

@ -23,6 +23,8 @@ import (
var templateDirCandidates = []string{ var templateDirCandidates = []string{
"ISSUE_TEMPLATE", "ISSUE_TEMPLATE",
"issue_template", "issue_template",
".forgejo/ISSUE_TEMPLATE",
".forgejo/issue_template",
".gitea/ISSUE_TEMPLATE", ".gitea/ISSUE_TEMPLATE",
".gitea/issue_template", ".gitea/issue_template",
".github/ISSUE_TEMPLATE", ".github/ISSUE_TEMPLATE",
@ -32,6 +34,8 @@ var templateDirCandidates = []string{
} }
var templateConfigCandidates = []string{ var templateConfigCandidates = []string{
".forgejo/ISSUE_TEMPLATE/config",
".forgejo/issue_template/config",
".gitea/ISSUE_TEMPLATE/config", ".gitea/ISSUE_TEMPLATE/config",
".gitea/issue_template/config", ".gitea/issue_template/config",
".github/ISSUE_TEMPLATE/config", ".github/ISSUE_TEMPLATE/config",

View file

@ -55,12 +55,18 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
} }
if mergeStyle != "" { if mergeStyle != "" {
templateFilepath := fmt.Sprintf(".gitea/default_merge_message/%s_TEMPLATE.md", strings.ToUpper(string(mergeStyle)))
commit, err := baseGitRepo.GetBranchCommit(pr.BaseRepo.DefaultBranch) commit, err := baseGitRepo.GetBranchCommit(pr.BaseRepo.DefaultBranch)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
templateContent, err := commit.GetFileContent(templateFilepath, setting.Repository.PullRequest.DefaultMergeMessageSize)
templateFilepathForgejo := fmt.Sprintf(".forgejo/default_merge_message/%s_TEMPLATE.md", strings.ToUpper(string(mergeStyle)))
templateFilepathGitea := fmt.Sprintf(".gitea/default_merge_message/%s_TEMPLATE.md", strings.ToUpper(string(mergeStyle)))
templateContent, err := commit.GetFileContent(templateFilepathForgejo, setting.Repository.PullRequest.DefaultMergeMessageSize)
if _, ok := err.(git.ErrNotExist); ok {
templateContent, err = commit.GetFileContent(templateFilepathGitea, setting.Repository.PullRequest.DefaultMergeMessageSize)
}
if err != nil { if err != nil {
if !git.IsErrNotExist(err) { if !git.IsErrNotExist(err) {
return "", "", err return "", "", err

View file

@ -1,4 +1,5 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package integration package integration
@ -18,14 +19,18 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func createIssueConfig(t *testing.T, user *user_model.User, repo *repo_model.Repository, issueConfig map[string]any) { func createIssueConfigInDirectory(t *testing.T, user *user_model.User, repo *repo_model.Repository, dir string, issueConfig map[string]any) {
config, err := yaml.Marshal(issueConfig) config, err := yaml.Marshal(issueConfig)
assert.NoError(t, err) assert.NoError(t, err)
err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/config.yaml", repo.DefaultBranch, string(config)) err = createOrReplaceFileInBranch(user, repo, fmt.Sprintf("%s/ISSUE_TEMPLATE/config.yaml", dir), repo.DefaultBranch, string(config))
assert.NoError(t, err) assert.NoError(t, err)
} }
func createIssueConfig(t *testing.T, user *user_model.User, repo *repo_model.Repository, issueConfig map[string]any) {
createIssueConfigInDirectory(t, user, repo, ".gitea", issueConfig)
}
func getIssueConfig(t *testing.T, owner, repo string) api.IssueConfig { func getIssueConfig(t *testing.T, owner, repo string) api.IssueConfig {
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config", owner, repo) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config", owner, repo)
req := NewRequest(t, "GET", urlStr) req := NewRequest(t, "GET", urlStr)
@ -44,6 +49,8 @@ func TestAPIRepoGetIssueConfig(t *testing.T) {
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
t.Run("Default", func(t *testing.T) { t.Run("Default", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
issueConfig := getIssueConfig(t, owner.Name, repo.Name) issueConfig := getIssueConfig(t, owner.Name, repo.Name)
assert.True(t, issueConfig.BlankIssuesEnabled) assert.True(t, issueConfig.BlankIssuesEnabled)
@ -51,6 +58,8 @@ func TestAPIRepoGetIssueConfig(t *testing.T) {
}) })
t.Run("DisableBlankIssues", func(t *testing.T) { t.Run("DisableBlankIssues", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
config := make(map[string]any) config := make(map[string]any)
config["blank_issues_enabled"] = false config["blank_issues_enabled"] = false
@ -63,6 +72,8 @@ func TestAPIRepoGetIssueConfig(t *testing.T) {
}) })
t.Run("ContactLinks", func(t *testing.T) { t.Run("ContactLinks", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
contactLink := make(map[string]string) contactLink := make(map[string]string)
contactLink["name"] = "TestName" contactLink["name"] = "TestName"
contactLink["url"] = "https://example.com" contactLink["url"] = "https://example.com"
@ -84,6 +95,8 @@ func TestAPIRepoGetIssueConfig(t *testing.T) {
}) })
t.Run("Full", func(t *testing.T) { t.Run("Full", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
contactLink := make(map[string]string) contactLink := make(map[string]string)
contactLink["name"] = "TestName" contactLink["name"] = "TestName"
contactLink["url"] = "https://example.com" contactLink["url"] = "https://example.com"
@ -113,6 +126,8 @@ func TestAPIRepoIssueConfigPaths(t *testing.T) {
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
templateConfigCandidates := []string{ templateConfigCandidates := []string{
".forgejo/ISSUE_TEMPLATE/config",
".forgejo/issue_template/config",
".gitea/ISSUE_TEMPLATE/config", ".gitea/ISSUE_TEMPLATE/config",
".gitea/issue_template/config", ".gitea/issue_template/config",
".github/ISSUE_TEMPLATE/config", ".github/ISSUE_TEMPLATE/config",
@ -123,6 +138,8 @@ func TestAPIRepoIssueConfigPaths(t *testing.T) {
for _, extension := range []string{".yaml", ".yml"} { for _, extension := range []string{".yaml", ".yml"} {
fullPath := canidate + extension fullPath := canidate + extension
t.Run(fullPath, func(t *testing.T) { t.Run(fullPath, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
configMap := make(map[string]any) configMap := make(map[string]any)
configMap["blank_issues_enabled"] = false configMap["blank_issues_enabled"] = false
@ -153,6 +170,8 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) {
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name)
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", urlStr) req := NewRequest(t, "GET", urlStr)
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
@ -164,10 +183,18 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) {
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
dirs := []string{".gitea", ".forgejo"}
for _, dir := range dirs {
t.Run(dir, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer func() {
deleteFileInBranch(owner, repo, fmt.Sprintf("%s/ISSUE_TEMPLATE/config.yaml", dir), repo.DefaultBranch)
}()
config := make(map[string]any) config := make(map[string]any)
config["blank_issues_enabled"] = "Test" config["blank_issues_enabled"] = "Test"
createIssueConfig(t, owner, repo, config) createIssueConfigInDirectory(t, owner, repo, dir, config)
req := NewRequest(t, "GET", urlStr) req := NewRequest(t, "GET", urlStr)
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
@ -179,3 +206,5 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) {
assert.NotEmpty(t, issueConfigValidation.Message) assert.NotEmpty(t, issueConfigValidation.Message)
}) })
} }
})
}

View file

@ -0,0 +1,114 @@
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestAPIIssueTemplateList(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
t.Run("no templates", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/issue_templates", repo.FullName()))
resp := MakeRequest(t, req, http.StatusOK)
var issueTemplates []*api.IssueTemplate
DecodeJSON(t, resp, &issueTemplates)
assert.Empty(t, issueTemplates)
})
t.Run("existing template", func(t *testing.T) {
templateCandidates := []string{
".forgejo/ISSUE_TEMPLATE/test.md",
".forgejo/issue_template/test.md",
".gitea/ISSUE_TEMPLATE/test.md",
".gitea/issue_template/test.md",
".github/ISSUE_TEMPLATE/test.md",
".github/issue_template/test.md",
}
for _, template := range templateCandidates {
t.Run(template, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer func() {
deleteFileInBranch(user, repo, template, repo.DefaultBranch)
}()
err := createOrReplaceFileInBranch(user, repo, template, repo.DefaultBranch,
`---
name: 'Template Name'
about: 'This template is for testing!'
title: '[TEST] '
ref: 'main'
---
This is the template!`)
assert.NoError(t, err)
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/issue_templates", repo.FullName()))
resp := MakeRequest(t, req, http.StatusOK)
var issueTemplates []*api.IssueTemplate
DecodeJSON(t, resp, &issueTemplates)
assert.Len(t, issueTemplates, 1)
assert.Equal(t, "Template Name", issueTemplates[0].Name)
assert.Equal(t, "This template is for testing!", issueTemplates[0].About)
assert.Equal(t, "refs/heads/main", issueTemplates[0].Ref)
assert.Equal(t, template, issueTemplates[0].FileName)
})
}
})
t.Run("multiple templates", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
templatePriority := []string{
".forgejo/issue_template/test.md",
".gitea/issue_template/test.md",
".github/issue_template/test.md",
}
defer func() {
for _, template := range templatePriority {
deleteFileInBranch(user, repo, template, repo.DefaultBranch)
}
}()
for _, template := range templatePriority {
err := createOrReplaceFileInBranch(user, repo, template, repo.DefaultBranch,
`---
name: 'Template Name'
about: 'This template is for testing!'
title: '[TEST] '
ref: 'main'
---
This is the template!`)
assert.NoError(t, err)
}
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/issue_templates", repo.FullName()))
resp := MakeRequest(t, req, http.StatusOK)
var issueTemplates []*api.IssueTemplate
DecodeJSON(t, resp, &issueTemplates)
// 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)
})
})
}

View file

@ -96,6 +96,105 @@ func TestPullCreate(t *testing.T) {
}) })
} }
func TestPullCreateWithPullTemplate(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
baseUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
forkUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
templateCandidates := []string{
".forgejo/PULL_REQUEST_TEMPLATE.md",
".forgejo/pull_request_template.md",
".gitea/PULL_REQUEST_TEMPLATE.md",
".gitea/pull_request_template.md",
".github/PULL_REQUEST_TEMPLATE.md",
".github/pull_request_template.md",
}
createBaseRepo := func(t *testing.T, templateFiles []string, message string) (*repo_model.Repository, func()) {
t.Helper()
changeOps := make([]*files_service.ChangeRepoFile, len(templateFiles))
for i, template := range templateFiles {
changeOps[i] = &files_service.ChangeRepoFile{
Operation: "create",
TreePath: template,
ContentReader: strings.NewReader(message + " " + template),
}
}
repo, _, deferrer := CreateDeclarativeRepo(t, baseUser, "", nil, nil, changeOps)
return repo, deferrer
}
testPullPreview := func(t *testing.T, session *TestSession, user, repo, message string) {
t.Helper()
req := NewRequest(t, "GET", path.Join(user, repo))
resp := session.MakeRequest(t, req, http.StatusOK)
// Click the PR button to create a pull
htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find("#new-pull-request").Attr("href")
assert.True(t, exists, "The template has changed")
// Load the pull request preview
req = NewRequest(t, "GET", link)
resp = session.MakeRequest(t, req, http.StatusOK)
// Check that the message from the template is present.
htmlDoc = NewHTMLParser(t, resp.Body)
pullRequestMessage := htmlDoc.doc.Find("textarea[placeholder*='comment']").Text()
assert.Equal(t, message, pullRequestMessage)
}
for i, template := range templateCandidates {
t.Run(template, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Create the base repository, with the pull request template added.
message := fmt.Sprintf("TestPullCreateWithPullTemplate/%s", template)
baseRepo, deferrer := createBaseRepo(t, []string{template}, message)
defer deferrer()
// Fork the repository
session := loginUser(t, forkUser.Name)
testRepoFork(t, session, baseUser.Name, baseRepo.Name, forkUser.Name, baseRepo.Name)
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: forkUser.ID, Name: baseRepo.Name})
// Apply a change to the fork
err := createOrReplaceFileInBranch(forkUser, forkedRepo, "README.md", forkedRepo.DefaultBranch, fmt.Sprintf("Hello, World (%d)\n", i))
assert.NoError(t, err)
testPullPreview(t, session, forkUser.Name, forkedRepo.Name, message+" "+template)
})
}
t.Run("multiple template options", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Create the base repository, with the pull request template added.
message := "TestPullCreateWithPullTemplate/multiple"
baseRepo, deferrer := createBaseRepo(t, templateCandidates, message)
defer deferrer()
// Fork the repository
session := loginUser(t, forkUser.Name)
testRepoFork(t, session, baseUser.Name, baseRepo.Name, forkUser.Name, baseRepo.Name)
forkedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: forkUser.ID, Name: baseRepo.Name})
// Apply a change to the fork
err := createOrReplaceFileInBranch(forkUser, forkedRepo, "README.md", forkedRepo.DefaultBranch, "Hello, World (%d)\n")
assert.NoError(t, err)
// Unlike issues, where all candidates are considered and shown, for
// pull request, there's a priority: if there are multiple
// templates, only the highest priority one is used.
testPullPreview(t, session, forkUser.Name, forkedRepo.Name, message+" .forgejo/PULL_REQUEST_TEMPLATE.md")
})
})
}
func TestPullCreate_TitleEscape(t *testing.T) { func TestPullCreate_TitleEscape(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
session := loginUser(t, "user1") session := loginUser(t, "user1")