@@ -6,15 +6,20 @@ package models
6
6
7
7
import (
8
8
"fmt"
9
+ "io/ioutil"
9
10
"os"
11
+ "path"
10
12
"path/filepath"
11
13
"strconv"
12
14
"strings"
13
15
"time"
14
16
15
17
"code.gitea.io/gitea/modules/git"
16
18
"code.gitea.io/gitea/modules/log"
19
+ "code.gitea.io/gitea/modules/process"
20
+ "code.gitea.io/gitea/modules/util"
17
21
22
+ "github.com/gobwas/glob"
18
23
"github.com/unknwon/com"
19
24
)
20
25
@@ -36,8 +41,148 @@ func (gro GenerateRepoOptions) IsValid() bool {
36
41
return gro .GitContent || gro .Topics || gro .GitHooks || gro .Webhooks || gro .Avatar || gro .IssueLabels // or other items as they are added
37
42
}
38
43
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
+
39
184
// 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 ) {
41
186
tmpDir := filepath .Join (os .TempDir (), "gitea-" + repo .Name + "-" + com .ToStr (time .Now ().Nanosecond ()))
42
187
43
188
if err := os .MkdirAll (tmpDir , os .ModePerm ); err != nil {
@@ -50,7 +195,7 @@ func generateRepository(e Engine, repo, templateRepo *Repository) (err error) {
50
195
}
51
196
}()
52
197
53
- if err = generateRepoCommit (e , repo , templateRepo , tmpDir ); err != nil {
198
+ if err = generateRepoCommit (e , repo , templateRepo , generateRepo , tmpDir ); err != nil {
54
199
return fmt .Errorf ("generateRepoCommit: %v" , err )
55
200
}
56
201
@@ -95,7 +240,7 @@ func GenerateRepository(ctx DBContext, doer, owner *User, templateRepo *Reposito
95
240
96
241
// GenerateGitContent generates git content from a template repository
97
242
func 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 {
99
244
return err
100
245
}
101
246
@@ -210,3 +355,36 @@ func GenerateIssueLabels(ctx DBContext, templateRepo, generateRepo *Repository)
210
355
}
211
356
return nil
212
357
}
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