diff options
author | John Olheiser <42128690+jolheiser@users.noreply.github.com> | 2019-11-30 00:54:47 -0600 |
---|---|---|
committer | techknowlogick <techknowlogick@gitea.io> | 2019-11-30 01:54:47 -0500 |
commit | 15a5c10d33fd3c0b9483316b19712e981bf2391d (patch) | |
tree | 5eb71ae48739e3f3f0f1e36a5e81b71d3d4e37b0 /models/repo_generate.go | |
parent | c9d50bcab56780fc26ca69cc72197e62bdb02134 (diff) | |
download | gitea-15a5c10d33fd3c0b9483316b19712e981bf2391d.tar.gz gitea-15a5c10d33fd3c0b9483316b19712e981bf2391d.zip |
Variable expansion in repository templates (#9163)
* Start expansion
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* _template rather than .template
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Use ioutil
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Add descriptions to mapping
* Start globbing
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Tune globbing
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Re-arrange imports
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Don't expand git hooks
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Add glob tests for .giteatemplate
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Parse globs separately so they can be tested more easily
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Change template location and add docs
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* nit
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Update docs/content/doc/features/gitea-directory.md
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Update docs/content/doc/features/gitea-directory.md
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
* Add upper-lower case match
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Nits
Signed-off-by: jolheiser <john.olheiser@gmail.com>
* Update models/repo_generate.go
Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>
Diffstat (limited to 'models/repo_generate.go')
-rw-r--r-- | models/repo_generate.go | 184 |
1 files changed, 181 insertions, 3 deletions
diff --git a/models/repo_generate.go b/models/repo_generate.go index 234bdc27f2..56a3940ac1 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -6,7 +6,9 @@ package models import ( "fmt" + "io/ioutil" "os" + "path" "path/filepath" "strconv" "strings" @@ -14,7 +16,10 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/util" + "github.com/gobwas/glob" "github.com/unknwon/com" ) @@ -36,8 +41,148 @@ func (gro GenerateRepoOptions) IsValid() bool { return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks || gro.Avatar || gro.IssueLabels // or other items as they are added } +// GiteaTemplate holds information about a .gitea/template file +type GiteaTemplate struct { + Path string + Content []byte + + globs []glob.Glob +} + +// Globs parses the .gitea/template globs or returns them if they were already parsed +func (gt GiteaTemplate) Globs() []glob.Glob { + if gt.globs != nil { + return gt.globs + } + + gt.globs = make([]glob.Glob, 0) + lines := strings.Split(string(util.NormalizeEOL(gt.Content)), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + g, err := glob.Compile(line, '/') + if err != nil { + log.Info("Invalid glob expression '%s' (skipped): %v", line, err) + continue + } + gt.globs = append(gt.globs, g) + } + return gt.globs +} + +func checkGiteaTemplate(tmpDir string) (*GiteaTemplate, error) { + gtPath := filepath.Join(tmpDir, ".gitea", "template") + if _, err := os.Stat(gtPath); os.IsNotExist(err) { + return nil, nil + } else if err != nil { + return nil, err + } + + content, err := ioutil.ReadFile(gtPath) + if err != nil { + return nil, err + } + + gt := &GiteaTemplate{ + Path: gtPath, + Content: content, + } + + return gt, nil +} + +func generateRepoCommit(e Engine, repo, templateRepo, generateRepo *Repository, tmpDir string) error { + commitTimeStr := time.Now().Format(time.RFC3339) + authorSig := repo.Owner.NewGitSig() + + // Because this may call hooks we should pass in the environment + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+authorSig.Name, + "GIT_AUTHOR_EMAIL="+authorSig.Email, + "GIT_AUTHOR_DATE="+commitTimeStr, + "GIT_COMMITTER_NAME="+authorSig.Name, + "GIT_COMMITTER_EMAIL="+authorSig.Email, + "GIT_COMMITTER_DATE="+commitTimeStr, + ) + + // Clone to temporary path and do the init commit. + templateRepoPath := templateRepo.repoPath(e) + if err := git.Clone(templateRepoPath, tmpDir, git.CloneRepoOptions{ + Depth: 1, + }); err != nil { + return fmt.Errorf("git clone: %v", err) + } + + if err := os.RemoveAll(path.Join(tmpDir, ".git")); err != nil { + return fmt.Errorf("remove git dir: %v", err) + } + + // Variable expansion + gt, err := checkGiteaTemplate(tmpDir) + if err != nil { + return fmt.Errorf("checkGiteaTemplate: %v", err) + } + + if err := os.Remove(gt.Path); err != nil { + return fmt.Errorf("remove .giteatemplate: %v", err) + } + + // Avoid walking tree if there are no globs + if len(gt.Globs()) > 0 { + tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" + if err := filepath.Walk(tmpDirSlash, func(path string, info os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if info.IsDir() { + return nil + } + + base := strings.TrimPrefix(filepath.ToSlash(path), tmpDirSlash) + for _, g := range gt.Globs() { + if g.Match(base) { + content, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + if err := ioutil.WriteFile(path, + []byte(generateExpansion(string(content), templateRepo, generateRepo)), + 0644); err != nil { + return err + } + break + } + } + return nil + }); err != nil { + return err + } + } + + if err := git.InitRepository(tmpDir, false); err != nil { + return err + } + + repoPath := repo.repoPath(e) + _, stderr, err := process.GetManager().ExecDirEnv( + -1, tmpDir, + fmt.Sprintf("generateRepoCommit(git remote add): %s", repoPath), + env, + git.GitExecutable, "remote", "add", "origin", repoPath, + ) + if err != nil { + return fmt.Errorf("git remote add: %v - %s", err, stderr) + } + + return initRepoCommit(tmpDir, repo.Owner) +} + // generateRepository initializes repository from template -func generateRepository(e Engine, repo, templateRepo *Repository) (err error) { +func generateRepository(e Engine, repo, templateRepo, generateRepo *Repository) (err error) { tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { @@ -50,7 +195,7 @@ func generateRepository(e Engine, repo, templateRepo *Repository) (err error) { } }() - if err = generateRepoCommit(e, repo, templateRepo, tmpDir); err != nil { + if err = generateRepoCommit(e, repo, templateRepo, generateRepo, tmpDir); err != nil { return fmt.Errorf("generateRepoCommit: %v", err) } @@ -95,7 +240,7 @@ func GenerateRepository(ctx DBContext, doer, owner *User, templateRepo *Reposito // GenerateGitContent generates git content from a template repository func GenerateGitContent(ctx DBContext, templateRepo, generateRepo *Repository) error { - if err := generateRepository(ctx.e, generateRepo, templateRepo); err != nil { + if err := generateRepository(ctx.e, generateRepo, templateRepo, generateRepo); err != nil { return err } @@ -210,3 +355,36 @@ func GenerateIssueLabels(ctx DBContext, templateRepo, generateRepo *Repository) } return nil } + +func generateExpansion(src string, templateRepo, generateRepo *Repository) string { + return os.Expand(src, func(key string) string { + switch key { + case "REPO_NAME": + return generateRepo.Name + case "TEMPLATE_NAME": + return templateRepo.Name + case "REPO_DESCRIPTION": + return generateRepo.Description + case "TEMPLATE_DESCRIPTION": + return templateRepo.Description + case "REPO_OWNER": + return generateRepo.MustOwnerName() + case "TEMPLATE_OWNER": + return templateRepo.MustOwnerName() + case "REPO_LINK": + return generateRepo.Link() + case "TEMPLATE_LINK": + return templateRepo.Link() + case "REPO_HTTPS_URL": + return generateRepo.CloneLink().HTTPS + case "TEMPLATE_HTTPS_URL": + return templateRepo.CloneLink().HTTPS + case "REPO_SSH_URL": + return generateRepo.CloneLink().SSH + case "TEMPLATE_SSH_URL": + return templateRepo.CloneLink().SSH + default: + return key + } + }) +} |