mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-31 18:26:46 +00:00
fa3780268c
This PR adds support for file exclusion into `.gitea/template` Docs: https://gitea.com/gitea/docs/pulls/470 ### Usage Template owners add a `.gitea/template` file to their template repository: ``` # Files to include for variable expansion *.go text/*.txt **/modules/* # Files/directories to exclude from the generated repo [exclude] vendor/* node_modules/ src/build/ ``` Resolves #37202 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
295 lines
10 KiB
Go
295 lines
10 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repository
|
|
|
|
import (
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
repo_model "gitea.dev/models/repo"
|
|
"gitea.dev/modules/test"
|
|
"gitea.dev/modules/util"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestGiteaTemplate(t *testing.T) {
|
|
giteaTemplate := []byte(`
|
|
# Header
|
|
|
|
# All .go files
|
|
**.go
|
|
|
|
# All text files in /text/
|
|
text/*.txt
|
|
|
|
# All files in modules folders
|
|
**/modules/*
|
|
|
|
# Exclude some files
|
|
[exclude]
|
|
**/modules/*.tmp
|
|
`)
|
|
|
|
gt := newGiteaTemplateFileMatcher("", giteaTemplate)
|
|
assert.Len(t, gt.globsExpand, 3)
|
|
|
|
tt := []struct {
|
|
Path string
|
|
Expand bool
|
|
Exclude bool
|
|
}{
|
|
{Path: "main.go", Expand: true},
|
|
{Path: "sub/sub/foo.go", Expand: true},
|
|
|
|
{Path: "a.txt"},
|
|
{Path: "text/a.txt", Expand: true},
|
|
{Path: "sub/text/a.txt"},
|
|
{Path: "text/a.json"},
|
|
|
|
{Path: "a/b/c/modules/README.md", Expand: true},
|
|
{Path: "a/b/c/modules/README.md.tmp", Expand: true, Exclude: true},
|
|
{Path: "a/b/c/modules/d/README.md"},
|
|
}
|
|
|
|
for _, tc := range tt {
|
|
assert.Equal(t, tc.Expand, gt.matchRules(gt.globsExpand, tc.Path), "should expand: %s", tc.Path)
|
|
assert.Equal(t, tc.Exclude, gt.matchRules(gt.globsExclude, tc.Path), "should exclude: %s", tc.Path)
|
|
}
|
|
}
|
|
|
|
func TestFilePathSanitize(t *testing.T) {
|
|
// path clean
|
|
assert.Equal(t, "a", filePathSanitize("//a/"))
|
|
assert.Equal(t, "_a", filePathSanitize(`\a`))
|
|
assert.Equal(t, "__/a/__", filePathSanitize(".. /a/ .."))
|
|
assert.Equal(t, "__/a/_git/b_", filePathSanitize("./../a/.git/ b: "))
|
|
|
|
// Windows reserved names
|
|
assert.Equal(t, "_", filePathSanitize("CoN"))
|
|
assert.Equal(t, "_", filePathSanitize("LpT1"))
|
|
assert.Equal(t, "_", filePathSanitize("CoM1"))
|
|
assert.Equal(t, "test_CON", filePathSanitize("test_CON"))
|
|
assert.Equal(t, "test CON", filePathSanitize("test CON "))
|
|
|
|
// special chars
|
|
assert.Equal(t, "_", filePathSanitize("\u0000"))
|
|
assert.Equal(t, ".", filePathSanitize(""))
|
|
assert.Equal(t, ".", filePathSanitize("."))
|
|
assert.Equal(t, ".", filePathSanitize("/"))
|
|
}
|
|
|
|
func TestProcessGiteaTemplateFileGenerate(t *testing.T) {
|
|
tmpDir := filepath.Join(t.TempDir(), "gitea-template-test")
|
|
fh := test.NewFileHelper(tmpDir)
|
|
|
|
require.NoError(t, os.MkdirAll(tmpDir+"/.git", 0o755))
|
|
require.NoError(t, os.WriteFile(tmpDir+"/.git/config", []byte("git-config-dummy"), 0o644))
|
|
require.NoError(t, os.MkdirAll(tmpDir+"/.gitea", 0o755))
|
|
require.NoError(t, os.WriteFile(tmpDir+"/.gitea/template", []byte("*\ninclude/**"), 0o644))
|
|
require.NoError(t, os.MkdirAll(tmpDir+"/sub", 0o755))
|
|
require.NoError(t, os.MkdirAll(tmpDir+"/include/foo/bar", 0o755))
|
|
|
|
require.NoError(t, os.WriteFile(tmpDir+"/sub/link-target", []byte("link target content from ${TEMPLATE_NAME}"), 0o644))
|
|
require.NoError(t, os.WriteFile(tmpDir+"/include/foo/bar/test.txt", []byte("include subdir ${TEMPLATE_NAME}"), 0o644))
|
|
|
|
// case-1
|
|
{
|
|
require.NoError(t, os.WriteFile(tmpDir+"/normal", []byte("normal content"), 0o644))
|
|
require.NoError(t, os.WriteFile(tmpDir+"/template", []byte("template from ${TEMPLATE_NAME}"), 0o644))
|
|
}
|
|
|
|
// case-2
|
|
{
|
|
require.NoError(t, os.Symlink(tmpDir+"/sub/link-target", tmpDir+"/link"))
|
|
}
|
|
|
|
// case-3
|
|
{
|
|
require.NoError(t, os.WriteFile(tmpDir+"/subst-${REPO_NAME}", []byte("dummy subst repo name"), 0o644))
|
|
}
|
|
|
|
// case-4
|
|
assertSubstTemplateName := func(normalContent, toLinkContent, fromLinkContent string) {
|
|
// empty as non-existing
|
|
fh.AssertFileExists(t, "subst-${TEMPLATE_NAME}-normal", util.Iif(normalContent == "", nil, new(normalContent)))
|
|
fh.AssertFileExists(t, "subst-${TEMPLATE_NAME}-to-link", util.Iif(toLinkContent == "", nil, new(toLinkContent)))
|
|
fh.AssertFileExists(t, "subst-${TEMPLATE_NAME}-from-link", util.Iif(fromLinkContent == "", nil, new(fromLinkContent)))
|
|
}
|
|
|
|
// case-5
|
|
{
|
|
require.NoError(t, os.MkdirAll(tmpDir+"/real-dir", 0o755))
|
|
require.NoError(t, os.WriteFile(tmpDir+"/real-dir/real-file", []byte("origin content"), 0o644))
|
|
require.NoError(t, os.MkdirAll(tmpDir+"/include/subst-${TEMPLATE_NAME}-link-dir", 0o755))
|
|
require.NoError(t, os.WriteFile(tmpDir+"/include/subst-${TEMPLATE_NAME}-link-dir/real-file", []byte("template content"), 0o644))
|
|
require.NoError(t, os.Symlink(tmpDir+"/real-dir", tmpDir+"/include/subst-TemplateRepoName-link-dir"))
|
|
}
|
|
|
|
{
|
|
// will succeed
|
|
require.NoError(t, os.WriteFile(tmpDir+"/subst-${TEMPLATE_NAME}-normal", []byte("dummy subst template name normal"), 0o644))
|
|
// will be skipped if the path subst result is a link
|
|
require.NoError(t, os.WriteFile(tmpDir+"/subst-${TEMPLATE_NAME}-to-link", []byte("dummy subst template name to link"), 0o644))
|
|
require.NoError(t, os.Symlink(tmpDir+"/sub/link-target", tmpDir+"/subst-TemplateRepoName-to-link"))
|
|
// will be skipped since the source is a symlink
|
|
require.NoError(t, os.Symlink(tmpDir+"/sub/link-target", tmpDir+"/subst-${TEMPLATE_NAME}-from-link"))
|
|
// pre-check
|
|
assertSubstTemplateName("dummy subst template name normal", "dummy subst template name to link", "link target content from ${TEMPLATE_NAME}")
|
|
}
|
|
|
|
// process the template files
|
|
{
|
|
templateRepo := &repo_model.Repository{Name: "TemplateRepoName"}
|
|
generatedRepo := &repo_model.Repository{Name: "/../.gIt/name"}
|
|
fh.AssertFileContent(t, ".git/config", "git-config-dummy")
|
|
fileMatcher, _ := readGiteaTemplateFile(tmpDir)
|
|
skippedFiles, err := processGiteaTemplateFile(t.Context(), tmpDir, templateRepo, generatedRepo, fileMatcher)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{
|
|
"include/subst-${TEMPLATE_NAME}-link-dir/real-file",
|
|
"include/subst-TemplateRepoName-link-dir",
|
|
"link",
|
|
"subst-${TEMPLATE_NAME}-from-link",
|
|
"subst-${TEMPLATE_NAME}-to-link",
|
|
"subst-TemplateRepoName-to-link",
|
|
}, skippedFiles)
|
|
fh.AssertExists(t, ".git/config", false)
|
|
fh.AssertExists(t, ".gitea/template", false)
|
|
fh.AssertFileContent(t, "include/foo/bar/test.txt", "include subdir TemplateRepoName")
|
|
}
|
|
|
|
// the lin target should never be modified, and since it is in a subdirectory, it is not affected by the template either
|
|
fh.AssertFileContent(t, "sub/link-target", "link target content from ${TEMPLATE_NAME}")
|
|
|
|
// case-1
|
|
{
|
|
fh.AssertExists(t, "no-such", false)
|
|
fh.AssertFileContent(t, "normal", "normal content")
|
|
fh.AssertFileContent(t, "template", "template from TemplateRepoName")
|
|
}
|
|
|
|
// case-2
|
|
{
|
|
// symlink with templates should be preserved (not read or write)
|
|
fh.AssertSymLink(t, "link", "sub/link-target")
|
|
}
|
|
|
|
// case-3
|
|
{
|
|
fh.AssertExists(t, "subst-${REPO_NAME}", false)
|
|
fh.AssertFileContent(t, "subst-/__/_gIt/name", "dummy subst repo name")
|
|
}
|
|
|
|
// case-4
|
|
{
|
|
// the paths with templates should have been removed, subst to a regular file, succeed, the link is preserved
|
|
assertSubstTemplateName("", "", "link target content from ${TEMPLATE_NAME}")
|
|
fh.AssertFileContent(t, "subst-TemplateRepoName-normal", "dummy subst template name normal")
|
|
// subst to a link, skip, and the target is unchanged
|
|
fh.AssertSymLink(t, "subst-TemplateRepoName-to-link", "sub/link-target")
|
|
// subst from a link, skip, and the target is unchanged
|
|
fh.AssertSymLink(t, "subst-${TEMPLATE_NAME}-from-link", "sub/link-target")
|
|
}
|
|
|
|
// case-5
|
|
{
|
|
fh.AssertFileContent(t, "real-dir/real-file", "origin content")
|
|
}
|
|
}
|
|
|
|
func TestProcessGiteaTemplateFileRead(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
_ = os.Mkdir(tmpDir+"/.gitea", 0o755)
|
|
templateFilePath := tmpDir + "/.gitea/template"
|
|
_ = os.Remove(templateFilePath)
|
|
_, err := os.Lstat(templateFilePath)
|
|
require.ErrorIs(t, err, fs.ErrNotExist)
|
|
_, err = readGiteaTemplateFile(tmpDir) // no template file
|
|
require.ErrorIs(t, err, fs.ErrNotExist)
|
|
|
|
_ = os.WriteFile(templateFilePath+".target", []byte("test-data-target"), 0o644)
|
|
_ = os.Symlink(templateFilePath+".target", templateFilePath)
|
|
content, _ := os.ReadFile(templateFilePath)
|
|
require.Equal(t, "test-data-target", string(content))
|
|
_, err = readGiteaTemplateFile(tmpDir) // symlinked template file
|
|
require.ErrorIs(t, err, fs.ErrNotExist)
|
|
|
|
_ = os.Remove(templateFilePath)
|
|
_ = os.WriteFile(templateFilePath, []byte("test-data-regular"), 0o644)
|
|
content, _ = os.ReadFile(templateFilePath)
|
|
require.Equal(t, "test-data-regular", string(content))
|
|
fm, err := readGiteaTemplateFile(tmpDir) // regular template file
|
|
require.NoError(t, err)
|
|
assert.Len(t, fm.globsExpand, 1)
|
|
}
|
|
|
|
func TestProcessGiteaTemplateFileExclusion(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
fh := test.NewFileHelper(tmpDir)
|
|
|
|
require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, ".gitea"), 0o755))
|
|
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, ".gitea", "template"), []byte(`
|
|
[expand]
|
|
*.md
|
|
*.txt
|
|
|
|
[exclude]
|
|
*.log
|
|
`), 0o644))
|
|
fh.WriteFile(t, "test.md", "from ${TEMPLATE_NAME}")
|
|
fh.WriteFile(t, "test.go", "package test")
|
|
fh.WriteFile(t, "test.log", "log-content")
|
|
fh.MkdirAll(t, "subdir")
|
|
fh.WriteFile(t, "subdir/foo", "bar")
|
|
fh.Symlink(t, "test.go", "symlink.go")
|
|
fh.Symlink(t, "test.log", "symlink.log")
|
|
fh.Symlink(t, "subdir", "symlink-dir.log")
|
|
|
|
templateRepo := &repo_model.Repository{Name: "MyTemplate"}
|
|
generatedRepo := &repo_model.Repository{Name: "MyRepo"}
|
|
|
|
fm, err := readGiteaTemplateFile(tmpDir)
|
|
require.NoError(t, err)
|
|
require.Len(t, fm.globsExpand, 2)
|
|
require.Len(t, fm.globsExclude, 1)
|
|
|
|
_, err = processGiteaTemplateFile(t.Context(), tmpDir, templateRepo, generatedRepo, fm)
|
|
require.NoError(t, err)
|
|
|
|
fh.AssertFileContent(t, "test.md", "from MyTemplate")
|
|
fh.AssertFileContent(t, "test.go", "package test")
|
|
fh.AssertExists(t, "test.log", false)
|
|
fh.AssertFileContent(t, "subdir/foo", "bar")
|
|
fh.AssertSymLink(t, "symlink.go", "test.go")
|
|
fh.AssertExists(t, "symlink.log", false)
|
|
fh.AssertExists(t, "symlink-dir.log", false)
|
|
}
|
|
|
|
func TestTransformers(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
expected string
|
|
}{
|
|
{"SNAKE", "abc_def_xyz"},
|
|
{"KEBAB", "abc-def-xyz"},
|
|
{"CAMEL", "abcDefXyz"},
|
|
{"PASCAL", "AbcDefXyz"},
|
|
{"LOWER", "abc_def-xyz"},
|
|
{"UPPER", "ABC_DEF-XYZ"},
|
|
{"TITLE", "Abc_def-Xyz"},
|
|
}
|
|
|
|
input := "Abc_Def-XYZ"
|
|
assert.Len(t, globalVars().defaultTransformers, len(cases))
|
|
for i, c := range cases {
|
|
tf := globalVars().defaultTransformers[i]
|
|
require.Equal(t, c.name, tf.Name)
|
|
assert.Equal(t, c.expected, tf.Transform(input), "case %s", c.name)
|
|
}
|
|
}
|