@@ -6,15 +6,20 @@ package models
66
77import (
88 "fmt"
9+ "io/ioutil"
910 "os"
11+ "path"
1012 "path/filepath"
1113 "strconv"
1214 "strings"
1315 "time"
1416
1517 "code.gitea.io/gitea/modules/git"
1618 "code.gitea.io/gitea/modules/log"
19+ "code.gitea.io/gitea/modules/process"
20+ "code.gitea.io/gitea/modules/util"
1721
22+ "github.com/gobwas/glob"
1823 "github.com/unknwon/com"
1924)
2025
@@ -36,8 +41,148 @@ func (gro GenerateRepoOptions) IsValid() bool {
3641 return gro .GitContent || gro .Topics || gro .GitHooks || gro .Webhooks || gro .Avatar || gro .IssueLabels // or other items as they are added
3742}
3843
44+ // GiteaTemplate holds information about a .gitea/template file
45+ type GiteaTemplate struct {
46+ Path string
47+ Content []byte
48+
49+ globs []glob.Glob
50+ }
51+
52+ // Globs parses the .gitea/template globs or returns them if they were already parsed
53+ func (gt GiteaTemplate ) Globs () []glob.Glob {
54+ if gt .globs != nil {
55+ return gt .globs
56+ }
57+
58+ gt .globs = make ([]glob.Glob , 0 )
59+ lines := strings .Split (string (util .NormalizeEOL (gt .Content )), "\n " )
60+ for _ , line := range lines {
61+ line = strings .TrimSpace (line )
62+ if line == "" || strings .HasPrefix (line , "#" ) {
63+ continue
64+ }
65+ g , err := glob .Compile (line , '/' )
66+ if err != nil {
67+ log .Info ("Invalid glob expression '%s' (skipped): %v" , line , err )
68+ continue
69+ }
70+ gt .globs = append (gt .globs , g )
71+ }
72+ return gt .globs
73+ }
74+
75+ func checkGiteaTemplate (tmpDir string ) (* GiteaTemplate , error ) {
76+ gtPath := filepath .Join (tmpDir , ".gitea" , "template" )
77+ if _ , err := os .Stat (gtPath ); os .IsNotExist (err ) {
78+ return nil , nil
79+ } else if err != nil {
80+ return nil , err
81+ }
82+
83+ content , err := ioutil .ReadFile (gtPath )
84+ if err != nil {
85+ return nil , err
86+ }
87+
88+ gt := & GiteaTemplate {
89+ Path : gtPath ,
90+ Content : content ,
91+ }
92+
93+ return gt , nil
94+ }
95+
96+ func generateRepoCommit (e Engine , repo , templateRepo , generateRepo * Repository , tmpDir string ) error {
97+ commitTimeStr := time .Now ().Format (time .RFC3339 )
98+ authorSig := repo .Owner .NewGitSig ()
99+
100+ // Because this may call hooks we should pass in the environment
101+ env := append (os .Environ (),
102+ "GIT_AUTHOR_NAME=" + authorSig .Name ,
103+ "GIT_AUTHOR_EMAIL=" + authorSig .Email ,
104+ "GIT_AUTHOR_DATE=" + commitTimeStr ,
105+ "GIT_COMMITTER_NAME=" + authorSig .Name ,
106+ "GIT_COMMITTER_EMAIL=" + authorSig .Email ,
107+ "GIT_COMMITTER_DATE=" + commitTimeStr ,
108+ )
109+
110+ // Clone to temporary path and do the init commit.
111+ templateRepoPath := templateRepo .repoPath (e )
112+ if err := git .Clone (templateRepoPath , tmpDir , git.CloneRepoOptions {
113+ Depth : 1 ,
114+ }); err != nil {
115+ return fmt .Errorf ("git clone: %v" , err )
116+ }
117+
118+ if err := os .RemoveAll (path .Join (tmpDir , ".git" )); err != nil {
119+ return fmt .Errorf ("remove git dir: %v" , err )
120+ }
121+
122+ // Variable expansion
123+ gt , err := checkGiteaTemplate (tmpDir )
124+ if err != nil {
125+ return fmt .Errorf ("checkGiteaTemplate: %v" , err )
126+ }
127+
128+ if err := os .Remove (gt .Path ); err != nil {
129+ return fmt .Errorf ("remove .giteatemplate: %v" , err )
130+ }
131+
132+ // Avoid walking tree if there are no globs
133+ if len (gt .Globs ()) > 0 {
134+ tmpDirSlash := strings .TrimSuffix (filepath .ToSlash (tmpDir ), "/" ) + "/"
135+ if err := filepath .Walk (tmpDirSlash , func (path string , info os.FileInfo , walkErr error ) error {
136+ if walkErr != nil {
137+ return walkErr
138+ }
139+
140+ if info .IsDir () {
141+ return nil
142+ }
143+
144+ base := strings .TrimPrefix (filepath .ToSlash (path ), tmpDirSlash )
145+ for _ , g := range gt .Globs () {
146+ if g .Match (base ) {
147+ content , err := ioutil .ReadFile (path )
148+ if err != nil {
149+ return err
150+ }
151+
152+ if err := ioutil .WriteFile (path ,
153+ []byte (generateExpansion (string (content ), templateRepo , generateRepo )),
154+ 0644 ); err != nil {
155+ return err
156+ }
157+ break
158+ }
159+ }
160+ return nil
161+ }); err != nil {
162+ return err
163+ }
164+ }
165+
166+ if err := git .InitRepository (tmpDir , false ); err != nil {
167+ return err
168+ }
169+
170+ repoPath := repo .repoPath (e )
171+ _ , stderr , err := process .GetManager ().ExecDirEnv (
172+ - 1 , tmpDir ,
173+ fmt .Sprintf ("generateRepoCommit(git remote add): %s" , repoPath ),
174+ env ,
175+ git .GitExecutable , "remote" , "add" , "origin" , repoPath ,
176+ )
177+ if err != nil {
178+ return fmt .Errorf ("git remote add: %v - %s" , err , stderr )
179+ }
180+
181+ return initRepoCommit (tmpDir , repo .Owner )
182+ }
183+
39184// generateRepository initializes repository from template
40- func generateRepository (e Engine , repo , templateRepo * Repository ) (err error ) {
185+ func generateRepository (e Engine , repo , templateRepo , generateRepo * Repository ) (err error ) {
41186 tmpDir := filepath .Join (os .TempDir (), "gitea-" + repo .Name + "-" + com .ToStr (time .Now ().Nanosecond ()))
42187
43188 if err := os .MkdirAll (tmpDir , os .ModePerm ); err != nil {
@@ -50,7 +195,7 @@ func generateRepository(e Engine, repo, templateRepo *Repository) (err error) {
50195 }
51196 }()
52197
53- if err = generateRepoCommit (e , repo , templateRepo , tmpDir ); err != nil {
198+ if err = generateRepoCommit (e , repo , templateRepo , generateRepo , tmpDir ); err != nil {
54199 return fmt .Errorf ("generateRepoCommit: %v" , err )
55200 }
56201
@@ -95,7 +240,7 @@ func GenerateRepository(ctx DBContext, doer, owner *User, templateRepo *Reposito
95240
96241// GenerateGitContent generates git content from a template repository
97242func GenerateGitContent (ctx DBContext , templateRepo , generateRepo * Repository ) error {
98- if err := generateRepository (ctx .e , generateRepo , templateRepo ); err != nil {
243+ if err := generateRepository (ctx .e , generateRepo , templateRepo , generateRepo ); err != nil {
99244 return err
100245 }
101246
@@ -210,3 +355,36 @@ func GenerateIssueLabels(ctx DBContext, templateRepo, generateRepo *Repository)
210355 }
211356 return nil
212357}
358+
359+ func generateExpansion (src string , templateRepo , generateRepo * Repository ) string {
360+ return os .Expand (src , func (key string ) string {
361+ switch key {
362+ case "REPO_NAME" :
363+ return generateRepo .Name
364+ case "TEMPLATE_NAME" :
365+ return templateRepo .Name
366+ case "REPO_DESCRIPTION" :
367+ return generateRepo .Description
368+ case "TEMPLATE_DESCRIPTION" :
369+ return templateRepo .Description
370+ case "REPO_OWNER" :
371+ return generateRepo .MustOwnerName ()
372+ case "TEMPLATE_OWNER" :
373+ return templateRepo .MustOwnerName ()
374+ case "REPO_LINK" :
375+ return generateRepo .Link ()
376+ case "TEMPLATE_LINK" :
377+ return templateRepo .Link ()
378+ case "REPO_HTTPS_URL" :
379+ return generateRepo .CloneLink ().HTTPS
380+ case "TEMPLATE_HTTPS_URL" :
381+ return templateRepo .CloneLink ().HTTPS
382+ case "REPO_SSH_URL" :
383+ return generateRepo .CloneLink ().SSH
384+ case "TEMPLATE_SSH_URL" :
385+ return templateRepo .CloneLink ().SSH
386+ default :
387+ return key
388+ }
389+ })
390+ }
0 commit comments