mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-29 11:17:42 +01:00
Merge pull request 'Forgejo hard fork' (#2319) from earl-warren/forgejo:wip-forgejo into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2319
This commit is contained in:
commit
cf1c57b681
13 changed files with 298 additions and 43 deletions
|
@ -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:
|
||||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "manual-testing"]
|
||||||
|
path = manual-testing
|
||||||
|
url = https://codeberg.org/forgejo/forgejo-manual-testing
|
1
manual-testing
Submodule
1
manual-testing
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 877d11b403b2b573fe435b792245b403367a2bb2
|
|
@ -448,7 +448,7 @@ activate_email.text = Please click the following link to verify your email addre
|
||||||
|
|
||||||
admin.new_user.subject = New user %s just signed up
|
admin.new_user.subject = New user %s just signed up
|
||||||
admin.new_user.user_info = User Information
|
admin.new_user.user_info = User Information
|
||||||
admin.new_user.text = Please <a href="%s">click here</a> to manage the user from the admin panel.
|
admin.new_user.text = Please <a href="%s">click here</a> to manage this user from the admin panel.
|
||||||
|
|
||||||
register_notify = Welcome to Forgejo
|
register_notify = Welcome to Forgejo
|
||||||
register_notify.title = %[1]s, welcome to %[2]s
|
register_notify.title = %[1]s, welcome to %[2]s
|
||||||
|
@ -633,7 +633,7 @@ settings = User Settings
|
||||||
|
|
||||||
form.name_reserved = The username "%s" is reserved.
|
form.name_reserved = The username "%s" is reserved.
|
||||||
form.name_pattern_not_allowed = The pattern "%s" is not allowed in a username.
|
form.name_pattern_not_allowed = The pattern "%s" is not allowed in a username.
|
||||||
form.name_chars_not_allowed = User name "%s" contains invalid characters.
|
form.name_chars_not_allowed = Username "%s" contains invalid characters.
|
||||||
|
|
||||||
[settings]
|
[settings]
|
||||||
profile = Profile
|
profile = Profile
|
||||||
|
@ -658,7 +658,7 @@ blocked_users = Blocked Users
|
||||||
public_profile = Public Profile
|
public_profile = Public Profile
|
||||||
biography_placeholder = Tell us a little bit about yourself! (You can use Markdown)
|
biography_placeholder = Tell us a little bit about yourself! (You can use Markdown)
|
||||||
location_placeholder = Share your approximate location with others
|
location_placeholder = Share your approximate location with others
|
||||||
profile_desc = Control how your profile is show to other users. Your primary email address will be used for notifications, password recovery and web-based Git operations.
|
profile_desc = Control how your profile is shown to other users. Your primary email address will be used for notifications, password recovery and web-based Git operations.
|
||||||
password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details.
|
password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details.
|
||||||
full_name = Full Name
|
full_name = Full Name
|
||||||
website = Website
|
website = Website
|
||||||
|
@ -988,7 +988,7 @@ all_branches = All branches
|
||||||
fork_no_valid_owners = This repository can not be forked because there are no valid owners.
|
fork_no_valid_owners = This repository can not be forked because there are no valid owners.
|
||||||
use_template = Use this template
|
use_template = Use this template
|
||||||
clone_in_vsc = Clone in VS Code
|
clone_in_vsc = Clone in VS Code
|
||||||
clone_in_vscodium = Clone in VS Codium
|
clone_in_vscodium = Clone in VSCodium
|
||||||
download_zip = Download ZIP
|
download_zip = Download ZIP
|
||||||
download_tar = Download TAR.GZ
|
download_tar = Download TAR.GZ
|
||||||
download_bundle = Download BUNDLE
|
download_bundle = Download BUNDLE
|
||||||
|
@ -1027,7 +1027,7 @@ mirror_sync = synced
|
||||||
mirror_sync_on_commit = Sync when commits are pushed
|
mirror_sync_on_commit = Sync when commits are pushed
|
||||||
mirror_address = Clone From URL
|
mirror_address = Clone From URL
|
||||||
mirror_address_desc = Put any required credentials in the Authorization section.
|
mirror_address_desc = Put any required credentials in the Authorization section.
|
||||||
mirror_address_url_invalid = The provided URL is invalid. You must escape all components of the url correctly.
|
mirror_address_url_invalid = The provided URL is invalid. You must escape all components of the URL correctly.
|
||||||
mirror_address_protocol_invalid = The provided URL is invalid. Only http(s):// or git:// locations can be used for mirroring.
|
mirror_address_protocol_invalid = The provided URL is invalid. Only http(s):// or git:// locations can be used for mirroring.
|
||||||
mirror_lfs = Large File Storage (LFS)
|
mirror_lfs = Large File Storage (LFS)
|
||||||
mirror_lfs_desc = Activate mirroring of LFS data.
|
mirror_lfs_desc = Activate mirroring of LFS data.
|
||||||
|
@ -1789,7 +1789,7 @@ pulls.required_status_check_missing = Some required checks are missing.
|
||||||
pulls.required_status_check_administrator = As an administrator, you may still merge this pull request.
|
pulls.required_status_check_administrator = As an administrator, you may still merge this pull request.
|
||||||
pulls.blocked_by_approvals = "This pull request doesn't have enough approvals yet. %d of %d approvals granted."
|
pulls.blocked_by_approvals = "This pull request doesn't have enough approvals yet. %d of %d approvals granted."
|
||||||
pulls.blocked_by_rejection = "This pull request has changes requested by an official reviewer."
|
pulls.blocked_by_rejection = "This pull request has changes requested by an official reviewer."
|
||||||
pulls.blocked_by_official_review_requests = "This pull request has official review requests."
|
pulls.blocked_by_official_review_requests = "This pull request is blocked because it is missing approval from one or more official reviewers."
|
||||||
pulls.blocked_by_outdated_branch = "This pull request is blocked because it's outdated."
|
pulls.blocked_by_outdated_branch = "This pull request is blocked because it's outdated."
|
||||||
pulls.blocked_by_changed_protected_files_1= "This pull request is blocked because it changes a protected file:"
|
pulls.blocked_by_changed_protected_files_1= "This pull request is blocked because it changes a protected file:"
|
||||||
pulls.blocked_by_changed_protected_files_n= "This pull request is blocked because it changes protected files:"
|
pulls.blocked_by_changed_protected_files_n= "This pull request is blocked because it changes protected files:"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,18 +183,28 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Invalid", func(t *testing.T) {
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
config := make(map[string]any)
|
dirs := []string{".gitea", ".forgejo"}
|
||||||
config["blank_issues_enabled"] = "Test"
|
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)
|
||||||
|
}()
|
||||||
|
|
||||||
createIssueConfig(t, owner, repo, config)
|
config := make(map[string]any)
|
||||||
|
config["blank_issues_enabled"] = "Test"
|
||||||
|
|
||||||
req := NewRequest(t, "GET", urlStr)
|
createIssueConfigInDirectory(t, owner, repo, dir, config)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
var issueConfigValidation api.IssueConfigValidation
|
req := NewRequest(t, "GET", urlStr)
|
||||||
DecodeJSON(t, resp, &issueConfigValidation)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
assert.False(t, issueConfigValidation.Valid)
|
var issueConfigValidation api.IssueConfigValidation
|
||||||
assert.NotEmpty(t, issueConfigValidation.Message)
|
DecodeJSON(t, resp, &issueConfigValidation)
|
||||||
|
|
||||||
|
assert.False(t, issueConfigValidation.Valid)
|
||||||
|
assert.NotEmpty(t, issueConfigValidation.Message)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
114
tests/integration/api_issue_templates_test.go
Normal file
114
tests/integration/api_issue_templates_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue